From 7df353a6eba07ad94b90da3f01e80b195e0e7227 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sat, 10 Oct 2026 12:56:36 +0100 Subject: [PATCH 01/45] docs: Rust port plan (Nexus Terminal) --- docs/rust-port-plan.md | 464 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 464 insertions(+) create mode 100644 docs/rust-port-plan.md diff --git a/docs/rust-port-plan.md b/docs/rust-port-plan.md new file mode 100644 index 0000000..9965ce0 --- /dev/null +++ b/docs/rust-port-plan.md @@ -0,0 +1,464 @@ +# Rust port plan: Nexus Terminal, a native egui app with no Go and no webview + +Goal: rebuild Alchementary Link in Rust as **Nexus Terminal**, a single Rust application in the +style of Leviathan Desktop (`../leviathan/rust`). +- **UI:** the Wails webview (HTML/CSS/JS, xterm.js) is replaced by an **eframe/egui 0.36** shell with + Leviathan's design system: `Tokens` themes, Inter and JetBrains Mono, Phosphor icons, a custom title + bar, a navigator rail, document tabs, a dock, a status bar, and the Ctrl/Cmd+K palette over one `Cmd` + enum. Full functional parity is required. +- **Backend:** the Go core is ported to Rust. At the end, Go, Node and Wails are gone from the repo. +- **Rename:** back to **Nexus Terminal** (§7). Existing Alchementary Link data is migrated on first + run. +- **Removed:** + - the hidden **Leviathan** mode (`internal/leviathan/*`, `leviathan.js`, the Konami toggle). It now lives in Leviathan Desktop. + - the **VS Code bridge** (`cmd/vscode-bridge`). +- **New:** a Serial protocol (§4). +- **VNC is included, not deferred.** Rust has a usable RFB client crate (§6a). + +### Crates + +| Need | Crate | Notes | +|---|---|---| +| UI | `eframe`/`egui` 0.36, `egui-phosphor`, `rfd`, `image` | Same versions and features as Leviathan (`glow`, `wayland`, `x11`, `accesskit`, `persistence`) | +| Async runtime | `tokio` (multi-thread, 2 workers) | Network sessions only. The UI thread never blocks on it | +| SSH, SFTP | `russh`, `russh-sftp`, `ssh-key` | Strict-kex (Terrapin) support; client-side **agent forwarding** (`server_channel_open_agent_forward`); `direct-tcpip` for jump hosts; `tcpip-forward` for remote forwards | +| Terminal emulation | `alacritty_terminal` | VT parser, grid, scrollback, selection and regex search (the same engine Zed uses). We draw it with egui's painter (§6) | +| Local shell | `portable-pty` | `forkpty` on Unix, ConPTY on Windows | +| Serial | `serialport` | Baud/bits/parity/stop, RTS/CTS and XON/XOFF, DTR/RTS, break, port enumeration | +| IRC | `irc` (tokio, rustls) | Registration, PING, CTCP, NickServ via PRIVMSG; reconnect and channel-state persistence are ours | +| VNC | `vnc-rs` | RFB handshake, VNC auth, Raw/CopyRect/Tight/ZRLE → egui texture | +| HTTP (GitHub, updater) | `ureq` 3 | Blocking calls on `spawn_blocking` | +| SQLite | `rusqlite` (`bundled`) | SQLite compiled in, so no system library is needed | +| Vault, export | `argon2`, `aes-gcm`, `rand` | Native Argon2id at 128 MiB takes about 0.3 s | +| Keychain | `keyring` 3 | Must read entries written by go-keyring (§3) | +| TOTP | `totp-rs` | | +| JSON | `serde`, `serde_json` | Protocol configs stay JSON columns | +| Telnet, RAW, recordings | none (tokio `TcpStream`, serde) | Small ports of the Go code | + +There are **no system C libraries**. SQLite is compiled in, and TLS uses rustls. The Linux binary needs +only the X11/Wayland/GL libraries that eframe loads, as Leviathan does. + +## 1. Architecture + +Like Leviathan, the project is engine-first: a UI-free core crate, and the egui app is one client of it. + +``` +rust/ +├── core/ nexus-core: storage, secrets, models, protocol clients, SessionManager, Service. No egui. +└── app/ nexus: the egui shell +``` + +- **`Service`** keeps the **same method names** as today's `api.Service` (`connect_session`, + `sftp_list`, …), and **`Event`** is one enum whose variants match today's event names + (`SessionData`, `SessionState`, `SecretPrompt`, `HostKeyVerify`, …). The checklist in §10 refers to + these names. They are now plain Rust calls with no serialisation. +- **The UI thread** runs egui and owns all UI state. Each frame it drains a + `crossbeam_channel::Receiver`. `Service` methods that touch SQLite are fast and run inline + (rusqlite behind a `Mutex`, in WAL mode). +- **One tokio task per session** (SSH, Telnet, RAW, IRC, GitHub refresh, VNC). Serial and local-shell + sessions each get a blocking thread (`serialport` and `portable-pty` have blocking APIs). Each session + has an `mpsc` outbound queue (keystrokes, resize, break, DTR/RTS). Inbound bytes are batched per read + and sent as one `Event::SessionData`, followed by `ctx.request_repaint()`. Sending raw bytes instead + of base64 JSON is the main performance gain over the Wails app. +- **Credential prompts:** when a session task needs a secret or a host-key decision while connecting, + it sends `Event::SecretPrompt { reply: oneshot::Sender }` and awaits the answer. Closing the session + drops the receiver, which cancels the prompt. This is the same contract as the Go channel. +- **Long jobs** (SFTP transfers, archive download, `test_connection`, key generation) are tokio tasks + or `spawn_blocking`, and report progress through events. + +## 2. SSH (russh) + +| Feature | Approach | +|---|---| +| Interactive shell | `channel.request_pty(term type from the profile)`, env vars, `request_shell`, `window_change` | +| Auth | password, public key (files, with passphrase), agent (`SSH_AUTH_SOCK`; Pageant and the Windows OpenSSH pipe through russh's agent client), keyboard-interactive, with TOTP answering the prompts | +| Host keys | `Handler::check_server_key` against **our** SQLite known-hosts table (strict/ask/accept-new/insecure). Never `~/.ssh/known_hosts` | +| Keepalive | russh `keepalive_interval`/`keepalive_max` from the profile | +| Jump hosts | Hop N opens `channel_open_direct_tcpip`, and its stream is passed to hop N+1 as the transport (`connect_stream`). No loopback socket is needed | +| SOCKS5 and HTTP proxies | `tokio-socks` for SOCKS5; HTTP CONNECT is about 40 lines. The resulting stream goes to `connect_stream` | +| Local forwards | A `TcpListener` per forward → `channel_open_direct_tcpip` → `copy_bidirectional` | +| Remote forwards | `tcpip_forward`, plus `Handler::server_channel_open_forwarded_tcpip` | +| Agent forwarding | `agent_forward` request plus `server_channel_open_agent_forward`, bridged to the local agent. ⚠ Prove this in the phase 0 spike | +| SFTP | `russh-sftp` on a second channel: list/stat/mkdir/rename/remove/upload/download, with progress events | +| Key manager | `ssh-key`: generate ed25519, RSA 2048/4096 and ECDSA; write OpenSSH private keys (encrypted with bcrypt-pbkdf + aes256-ctr); public-key export; `file_is_private_key`. The key comment becomes `name@nexus-terminal` | +| `~/.ssh/config` import | Port of `sshconfig` (~300 lines of Go) | +| Compression | russh `zlib@openssh.com` when the profile enables it | + +The fallback, if a russh gap blocks the spike, is `ssh2` (libssh2 with vendored OpenSSL). It has no +API for accepting agent-forwarding channels, and it brings C back into the build. + +## 3. Porting the Go backend + +Sizes are Go lines, excluding tests. The Go tree is 41k lines. Leviathan (25.5k), VNC's Go side and the bridge are not +ported, which leaves about 15k lines to port. + +| Go package | Lines | Rust replacement | +|---|---|---| +| `storage` + `migrations/*.sql` | 949 | rusqlite. **Embed the same `.sql` files** with `include_str!` and keep the same migrations table, so existing databases open unchanged | +| `models`, `validation`, `preferences`, `audit`, `knownhosts`, `config` | ~1.5k | Plain modules. Protocol configs stay JSON columns, read as `serde_json::Value` or, where the UI edits them, as `#[derive(Deserialize)]` structs with `#[serde(default)]`. Leviathan preferences (`leviathanEnabled`, …) are ignored on read | +| `secrets` (vault + keychain) | 460 | Vault: `argon2` + `aes-gcm`. Keychain: `keyring`, with explicit target names to match go-keyring (see below) | +| `exportimport` | 209 | Same envelope and the same crypto; `.nexus` extension (§7) | +| `sessions` + `api` | ~6k | `SessionManager` + `Service` (§1): reconnect with attempts and backoff, login scripts, broadcast, recording hooks, secret resolution (saved / session-only / ask every time). The Leviathan methods are dropped | +| `sshclient`, `sftpclient`, `sshconfig`, `terminalenv` | ~1.7k | §2 | +| `telnetclient` | 603 | tokio `TcpStream` and the option state machine (echo, SGA, NAWS, TTYPE, BRK) | +| `rawclient` | 561 | tokio; history, capture, and hex/escaped encoders | +| `ircclient` (on `girc`) | 833 | `irc` crate plus our own state: rooms, NAMES, LIST, topic, reconnect, channel-state persistence (migration 006) | +| `githubclient` | 408 | `ureq` + serde; periodic refresh; archive download | +| `localshell` | 294 | `portable-pty` | +| `recording` | 538 | asciicast v2 writer and reader; the files are the same | +| `updater` | 303 | `ureq`; same manifest format; URLs per §7 | +| `vncclient` | 340 | §6a | +| `leviathan/*` | 25.5k | **Deleted.** | +| `cmd/vscode-bridge` | 357 | **Deleted.** | + +### Data compatibility (hard requirement) + +Existing users upgrade in place. **Before any Go is deleted**, generate fixtures from the Go code and +commit them under `rust/core/tests/fixtures/`: +- a database with every protocol, snippets and known hosts, migrated through 006 +- a vault file with a known password +- `.link` exports +- recordings +- keychain entries written by go-keyring, on each OS in CI + +Rust must read every fixture byte for byte. The formats are: +- **Vault:** JSON `{version, salt(b64, 32 bytes), secrets: key → b64(nonce12 ‖ ciphertext ‖ tag)}`; + Argon2id with t=3, m=128 MiB, lanes=4, and a 32-byte key. +- **Export:** `[1 byte version][32 bytes salt][12 bytes nonce][ciphertext]`, with the Argon2 parameters + from `exportimport.go`. Only the extension changes (`.link` → `.nexus`). +- **Keychain:** ⚠ go-keyring and `keyring` don't store entries the same way by default: + - **macOS:** both use a generic password stored under service and account, so they match. + - **Windows:** go-keyring's target is `service:user`, but `keyring` defaults to `user.service`. Use `Entry::new_with_target`. + - **Linux:** both use the Secret Service, but with different attribute sets. Search by `service` + `username`. + + The phase 0 spike proves all three against the fixtures. + +## 4. New protocol: Serial + +- **Library:** `serialport`. +- **Migration:** `007_serial_protocol.sql` adds `serial` to the protocol CHECK, plus a `serial_config` JSON column. +- **Config fields and defaults:** `baudRate 115200, dataBits 8, parity none|odd|even, stopBits 1|2, + flowControl none|xonxoff|rtscts, lineEnding cr|lf|crlf, localEcho false, dtrOnConnect true, + rtsOnConnect true, encoding, autoReconnect true, maxReconnectTries 5`. + Mark/space parity and 1.5 stop bits are left out because `serialport` doesn't support them. If they're needed, add them + through `termios`/`DCB` on the raw handle (about 30 lines per OS). +- **Device path:** stored in `host`, with `port` 0, so the sidebar, search and palette work unchanged. +- **Session:** a terminal session through `TermView`, so recording, triggers, broadcast, find, snippets + and split panes all work. Unplugging the adapter sets the session to disconnected, and auto-reconnect re-opens the path. + New service methods: `list_serial_ports`, `set_serial_lines(dtr, rts)`, `send_break`. +- **UI:** + - an editor page (device combo with refresh, line settings, behaviour settings) + - a Quick Connect entry + - Send Break and DTR/RTS toggles on the tab + - a badge colour + - error hints: the `dialout`/`uucp` group on Linux, `/dev/cu.*` on macOS, and busy ports + - a Help topic + +## 5. Toolchain and platforms + +- **Toolchain:** pin the same `rust-toolchain.toml` as Leviathan (`1.99.0`), edition 2024. +- **Targets:** keep the current release matrix in full: darwin amd64 and arm64 (one universal `.app`), + linux amd64 and arm64, and windows amd64 and arm64. +- **UI code from Leviathan:** copy these files into `app/src/` once, then let them evolve + independently. There is no dependency on the Leviathan repo: + - `theme.rs` (Tokens, fonts; 278 lines) + - `widgets.rs` (198 lines) + - the title bar, command palette and frame layout from `app.rs` + - `hexview.rs`, for the RAW hex view + - bundled fonts + - the `packaging/` layout + +## 5a. UI mapping: webview module → egui + +egui is immediate mode. Lists are virtualised with `ScrollArea::show_rows`, so the 1000/200/5000-row caps go away. +Terminal views are repainted only when their session receives data, and for input and cursor blink. + +| Current (JS) | egui replacement | +|---|---| +| `index.html` grid layout | Leviathan's frame: custom title bar (Nexus mark, menus, active-session title, ⌘K launcher, window controls); a **navigator rail** holding the connection sidebar (resizable 180–500); a central **tab strip + workspace** with an empty state; a right **dock** for SFTP and the profile editor; a **status bar** | +| Shortcuts (`app.js`) | The `Cmd` enum plus a shortcut table. `Modifiers::COMMAND` maps to ⌘ on macOS and Ctrl elsewhere. The palette enumerates every `Cmd` | +| `tabs.js` | A painted tab strip (Leviathan's tab style) with a protocol badge, status dot, activity marker, pin and 📁; pinned tabs first; drag to reorder; scroll with edge fades; context menu; tooltip with "Connected since"; close confirmation | +| `terminal.js` + xterm.js | `TermView` (§6) | +| `splitpane.js` | A binary tree of `Pane::{Leaf(session), Split{dir, ratio, a, b}}` laid out by rect, with draggable dividers (minimum 100 px). A placeholder leaf shows the connection picker. Child sessions get no tab | +| `sidebar.js` | Collapsing sections (Local Machine, ★ Favourites, Recent 5, folders, Ungrouped), a protocol dot and ★, Ctrl/Cmd-click multi-select, a context menu, a bulk-action bar, and fuzzy search | +| `profiles.js` drawer | A dock panel: protocol selector plus one section per protocol. List editors (jump hosts, forwards, RAW presets, login script) are `egui::Grid` rows with ▲▼✕ | +| `settings.js` | Settings window with tabs: General, Terminal, Security, Known Hosts, Help, About | +| `secrets.js` | Modal windows: secret prompt, master password (strength meter using the same formula), host-key dialog | +| `sshkeys.js` | SSH Keys window: list, generate, copy public key, delete | +| `sftp.js` | A dock panel per session: breadcrumb, table, bookmarks, and a transfer queue with progress bars. Dialogs via `rfd` | +| Drop files on terminal | `ctx.input(\|i\| i.raw.dropped_files)` gives real paths. The File Drop dialog stays. Upload by path, so the 768 KiB base64 chunking goes away | +| `raw.js` | Text view, hex view (the copied `hexview`, read-only), or split; a send bar (format combo, input, quick-byte buttons, presets); Save via `rfd` | +| `irc.js` | Room tabs + a read-only `TermView` fed by the ported mIRC→ANSI formatter + a composer + side lists (rooms, room list, users) | +| `github.js` | Painted repo cards under group headers; a filter; Refresh; ZIP and Visit buttons | +| `vnc.js` | `VncView` (§6a) | +| `commands.js` | The copied palette widget, with Nexus's sections and a port of `fuzzyMatch` | +| `snippets.js`, `triggers.js`, `recordings.js` | Windows. Playback uses its own `TermView`, with a scrubber, speed control and autoplay | +| `notifications.js` | A toast stack painted top-right in an `egui::Area`, auto-dismissed after 4 s (6 s for errors). About 60 lines; no crate needed | +| `themes.js` | App chrome uses `Tokens` (Pro/Midnight/Light). `terminal_themes.rs` is a const table of the 11 terminal palettes (charcoal … rose-pine) | +| Help tab HTML | The help topics minus Leviathan, as `include_str!` text with simple heading and paragraph styling | +| `BrowserOpenURL`, clipboard, fullscreen | `ctx.open_url`, `ctx.copy_text` and egui paste events, `ViewportCommand::Fullscreen` | + +**App theme:** every widget is drawn by egui, so the theme applies everywhere. + +## 6. TermView (the critical path) + +- **Model:** one `alacritty_terminal::Term` per view, behind a `FairMutex`, fed by + `vte::ansi::Processor`. Session data events are fed straight in as bytes; UTF-8 is the parser's job. +- **Drawing:** for each visible row, group cells into runs with the same attributes, then draw one + `painter.text` call per run, plus background rectangles, cursor, selection and search highlights. + Cache each row's layout by a hash of its content. Use the profile's font, falling back to the + preferences (JetBrains Mono is bundled). Wide and emoji cells take the terminal's cell width. +- **Scrollback:** `Config.scrolling_history`, from the profile, falling back to `defaultScrollback` + (0–100000). +- **Input:** + - egui `Key` events map to escape sequences, respecting the terminal mode (application cursor keys, keypad). + - `Event::Text` and `Event::Ime(Commit)` send the text, so IME input works. + - Paste is bracketed when the application enables it. + - Output goes to `write_session` and to the broadcast targets. +- **Mouse:** + - When the application enables mouse reporting, send SGR/X10 reports. + - Otherwise: drag selects, double-click selects a word, triple-click selects a line. + - The wheel scrolls the scrollback. On the alternate screen it sends ↑/↓ once per notch (the existing tmux behaviour). + - Ctrl/Cmd-click on a URL opens it. +- **Behaviours to keep:** + - **Clipboard:** Ctrl/Cmd+C copies when there is a selection and otherwise sends SIGINT; Ctrl/Cmd+V pastes. + - **Resize:** debounced; recompute columns and rows, then `term.resize` and `resize_session`. + - **Right-click menu:** all 11 items. + - **Find bar:** alacritty `RegexSearch`, with case and regex options, "i of N", Enter and Shift+Enter. + - **Overlays:** the disconnect overlay with Reconnect, and the local shell's "[Exited - press enter to restart]". + - **Cursor:** style (block, underline or bar) and blink. +- **Performance target:** `cat` of a 50 MB file stays responsive, at parity with or better than xterm.js. + Parsing runs on the UI thread inside the event drain. If profiling shows that's too slow, feed `Term` on the session + thread and keep only drawing on the UI thread (the `FairMutex` already allows this). +- **Reference:** `egui_term` (alacritty_terminal + egui) is prior art. Read it, but don't depend on it, + because it lags egui releases. + +## 6a. VNC + +- `vnc-rs` on a tokio task, with an optional SSH tunnel through §2's `direct-tcpip`. +- Decoded rectangles are applied to an RGBA buffer, and dirty frames update a `TextureHandle`, scaled to fit. +- Input: pointer → `PointerEvent`; keys → X11 keysyms (port the keysym map from `vnc.js`). +- If `vnc-rs` doesn't work with the servers users run, the fallback is our own RFB client: Raw, CopyRect and ZRLE + in about 600 lines, plus Tight's JPEG via the `image` crate. + +## 7. Rename to Nexus Terminal and migrate data + +The rebrand to Alchementary Link (commit `1a380a0`) renamed every path and **migrated nothing**. This rename +undoes it and migrates the data. + +| Item | Alchementary Link (now) | Nexus Terminal | +|---|---|---| +| Product and window title | Alchementary Link | Nexus Terminal | +| Binary, bundle and app id | `AlchementaryLink` | `NexusTerminal` / `nexus-terminal` | +| Config dir | `…/AlchementaryLink` | `…/NexusTerminal` | +| Database and log | `link.db`, `link.log` | `nexus.db`, `nexus.log` | +| Keychain service | `AlchementaryLink` | `NexusTerminal` | +| Export file | `.link`, "Link Export" | `.nexus`, "Nexus Export" (import accepts both) | +| Updater User-Agent and GitHub fallback | `AlchementaryLink-UpdateChecker`, `alchementary-link` | `NexusTerminal-UpdateChecker`, `nexus-terminal` | +| Update and release URLs | `alchementary.co.uk/download/link/…` | ⚠ **Decision needed:** the new URL for the manifest and downloads | +| Icons and brand mark | `alchementary-link-*.png`, ">_ AlchementaryLink" | Restore the `nexus-terminal-*` icons from git history (pre-`1a380a0`), redrawn for the egui title bar | + +**First-run migration** (in `nexus-core`, before the database is opened): +1. If `NexusTerminal/nexus.db` exists, use it. This covers users from before the rebrand, whose data was never moved. +2. Otherwise, if `AlchementaryLink/link.db` exists, **copy** (don't move) `link.db` → `nexus.db`, plus the + `vault/` directory and the recordings, into `NexusTerminal/`. Write a `migrated-from` marker. + The old directory is left untouched, so the Wails build still works if a user rolls back. +3. **Keychain:** when a read under `NexusTerminal` misses, try `AlchementaryLink`. If it's found there, write it under + `NexusTerminal`. Old entries are not deleted. +4. If both directories hold a database (a user who ran both builds), Nexus wins, and the toast says so. + +The old update channel must point existing Alchementary Link installs at the first Nexus release. That's one +manifest entry on the update server, so existing users get the Rust build. + +## 8. Phases and milestones + +Each phase ends with a runnable build and its slice of the §10 checklist ticked off. **The Wails app keeps +shipping until phase 7**, and Go is deleted only then. + +| # | Phase | Delivers | Milestone | +|---|---|---|---| +| 0 | Fixtures and spikes | The Go-generated fixtures (§3). Spikes, each answering one question: (a) a russh PTY shell against OpenSSH with an ed25519 key, strict-kex, a jump host and **agent forwarding**; (b) `TermView` on alacritty_terminal with vim, tmux, htop, IME input, and `cat` of 50 MB; (c) `keyring` reads go-keyring entries on all three OSes; (d) `vnc-rs` against TigerVNC and macOS Screen Sharing | Go/no-go on each risk in §2, §3 and §6a | +| 1 | Core foundation | `nexus-core`: storage and migrations, models, validation, preferences, audit, known hosts, vault, keychain, export/import, `Service`/`Event`, and the **§7 migration**. App skeleton in Leviathan's frame: rail, sidebar, tabs, status bar, toasts, and the secret, master-password and host-key modals | Opens an existing Alchementary Link user's data as Nexus; profiles listed; lock and unlock | +| 2 | Terminal | `TermView` (§6), terminal themes, local shell, Telnet, **Serial**, state handling (reconnect toast, error classifier, "Connection Already Open") | A usable local, Telnet and serial terminal | +| 3 | SSH | §2 in full, plus SFTP and the key manager | Usable as a daily SSH client | +| 4 | Forms | Profile editor (every field in §10.3), Quick Connect, Settings, Known Hosts, SSH Keys, encrypted export/import, update check | All profile CRUD and settings | +| 5 | Power features | Split panes, broadcast, snippets, triggers, recording and playback, port-forward dialog, SFTP dock and file drop, palette, shortcuts, fullscreen | Every terminal-session feature | +| 6 | RAW, IRC, GitHub, VNC | Rust clients and their views | Every protocol | +| 7 | Ship; delete Go | Packaging and CI (§9). Delete `go.mod`, `go.sum`, `main.go`, `internal/`, `cmd/`, `frontend/`, `wails.json`, the Wails `build/` assets, and the Go build scripts (`build*.sh`, `build.ps1`, `Makefile`, `build-all.sh`). Rewrite the docs, README and `.github/copilot-instructions.md` as Nexus Terminal. Publish the update-server entry that moves users across (§7) | Nexus Terminal released from a tag; no Go or JS left | + +## 9. Build and release + +- **Build:** `cargo build --release --target …`. + - Version: `CARGO_PKG_VERSION`, set by `release.sh` in `Cargo.toml`. + - Update URL: `option_env!("NEXUS_UPDATE_URL")`, defaulting to the URL decided in §7. This replaces Go's `-ldflags -X`. +- **Packaging:** Leviathan's `packaging/` layout, renamed: + - **macOS:** a universal `NexusTerminal.app` via `build-app.sh`, with Developer ID signing and notarisation. + - **Windows:** an `.exe` with an icon and version info from `build.rs`/`winresource`, using the GUI subsystem. + - **Linux:** a tarball, plus `install.sh` and a `.desktop` file. +- **CI:** rewrite `release-builds.yml` with `dtolnay/rust-toolchain` and a native runner per OS and arch. + Drop Go, Node, Wails and `libwebkit2gtk`. Run `cargo test` on every platform. +- **Unchanged:** the update server (`updates/index.php`) and the manifest format. `release.sh` gets the new names. + +## 10. Parity checklist + +This is the acceptance list, built from a full read of `frontend/src/js/*`. A feature is done when it works in the native build. + +### 10.1 Shell, tabs, sessions +- [ ] Layout: sidebar, tab strip (+ New, Broadcast: left-click toggles, right-click picks targets), workspace empty state (New Connection, Quick Connect), status bar +- [ ] Sidebar footer: New, ⌘K, Import, Export, Recordings, Settings +- [ ] Shortcuts: ⌘K, ⌘N, ⌘W, ⌘Tab/⇧⌘Tab (wrap), ⌘1–9, ⌘, (Settings), ⌘F, ⇧⌘D, ⇧⌘E, ⇧⌘R, ⇧⌘B, ⇧⌘S, ⇧⌘Q, F11; Esc closes palette and exits fullscreen +- [ ] Events handled: `session:data`, `session:state` (with attempt/maxRetries/delaySec toast), `session:closed`, `session:raw:data`, `session:github:*`, `session:irc:event`, `session:forward:started/stopped/error` toasts, `session:recording:started/stopped`, `session:sftp:progress`, `session:secret:prompt`, `session:hostkey:verify`, `app:open-about` +- [ ] Connect flow: "Connection Already Open" (Cancel / New Connection / Use Existing), "Connecting to X…" toast, refit and resize at 50/300/800 ms after connect +- [ ] Error classifier: auth errors → Close / Edit Profile / Retry; host-key errors also offer Known Hosts +- [ ] Tabs: + - [ ] Visuals: protocol badge, status dot, activity marker cleared on activate, pinned-first order + - [ ] Interaction: drag reorder; wheel scroll with overflow fades; scroll the active tab into view; hover hint (name + "Connected since"); close activates the nearest neighbour + - [ ] Context menu: Rename, Duplicate, Pin/Unpin, Move Left/Right, Reconnect, Close + - [ ] Close confirm when the pref is on (not for GitHub tabs) +- [ ] Session close is idempotent and cleans up splits, SFTP, triggers, recordings and viewers +- [ ] Local shell: `ConnectLocal`, exit and restart-on-Enter +- [ ] Quick Connect: SSH/Telnet/RAW/IRC/Serial, default ports, username (SSH/IRC), nick (IRC), Enter/Esc +- [ ] Broadcast: toggle toast, target picker with Select All (all selected means null, i.e. all sessions) +- [ ] Status bar latency: <100 good, <250 warn, else bad; stops on error +- [ ] Toasts: 4 types, × dismiss, 4 s / 6 s auto-dismiss + +### 10.2 Terminal +- [ ] §6 in full; 11 themes; switching theme updates every open terminal +- [ ] Find bar (case, regex, count, next/prev, clear highlights) +- [ ] Context menu: all 11 items, with the shortcut labels for the platform +- [ ] File drop: Paste contents / Upload to current directory (SSH only) via the directory picker (breadcrumb, filter, Up, Refresh, READ ONLY / NO READ badges, "Use this folder" disabled when the directory isn't writable, keyboard navigation) +- [ ] Port Forwards dialog: list, Stop, add (L/R, bind, dest, label) +- [ ] Split panes: split the focused pane right/down, placeholder picker (Local Machine + profiles), divider drag with live refit, per-pane ✕ promotes the sibling, late connects to a closed pane are disconnected, closing the tab closes child sessions +- [ ] Recording toggle and a visible tab indicator + +### 10.3 Profiles, sidebar, settings, security +- [ ] Sidebar: + - [ ] Sections: Local Machine, Favourites, Recent 5, folders (sorted), Ungrouped / "All Connections"; collapsible + - [ ] Search: fuzzy over name, host, tags and protocol; empty states + - [ ] Mouse: click connects; Ctrl/Cmd-click multi-selects; double-click edits + - [ ] Context menu: Connect, Edit, Duplicate, Favourite toggle, Delete (confirm) + - [ ] Bulk bar: Connect all, Disconnect (confirm), Tags add/replace, Move Group, Clear +- [ ] Profile editor, common: + - [ ] Protocol switch keeps common fields + - [ ] Validation: host, port, IRC nick and rooms, IRC SSH tunnel + - [ ] Buttons: Test (Reachable — N ms), Delete (confirm), Create/Save, Save & Connect + - [ ] General fields: Display Name, Host, Port, Username, Group (autocomplete), Description + - [ ] Terminal fields: font, size 8–36, scrollback 0–100000, cursor style + - [ ] Tags and Notes +- [ ] SSH fields: + - [ ] Password mode, auth methods + - [ ] Key path with `ListSSHKeys` autocomplete, Browse, and checks (reject `~`, `FileExists`, `FileIsPrivateKey`); passphrase mode + - [ ] Host key policy (strict/ask/accept-new/insecure) + - [ ] Jump hosts list, port forwards list + - [ ] Startup command, env KEY=VALUE, login script (wait/send, timeout 1–120, reorder) + - [ ] Agent auth and forwarding + - [ ] TOTP: show/hide, Test (code shown 5 s), Save/Delete, placeholder when a secret is saved + - [ ] Proxy (none/socks5/http, host, port 1080, user, password) + - [ ] Advanced: terminalType, keepalive interval/count, compression, autoReconnect, maxReconnectTries 1–20 +- [ ] Telnet fields: localEcho, lineMode, terminalType, encoding, line ending, negotiationDebug, login script, reconnect +- [ ] RAW fields: encoding, line ending, showTimestamps, showDirection, payload presets (name/payload/format), timeouts, autoReconnect +- [ ] GitHub fields: API base URL, token storage, refresh interval 60–3600 +- [ ] IRC fields: nick, realName, TLS, server password and mode, NickServ, auto-join rooms, SSH tunnel, reconnect +- [ ] VNC fields: every current field, saved round-trip +- [ ] Settings: + - [ ] General: theme, default protocol, confirm on close/disconnect, status bar, sidebar width, local shell (Browse) + - [ ] Terminal: theme, font, size, scrollback, cursor + - [ ] Security: init/unlock/lock + - [ ] Known Hosts: table, Remove + - [ ] Help: 13 topics + - [ ] About: version, Check for Updates, View release, Import ~/.ssh/config +- [ ] Master password dialog: strength meter (Weak/Fair/Good/Strong), confirm match +- [ ] Secret prompt (Esc cancels → `cancelled:true`) +- [ ] Host key dialog (Reject / Accept & Save) +- [ ] SSH Key Manager: list (name, type, bits), copy public key, delete, generate (ed25519/rsa 2048|4096/ecdsa, passphrase) +- [ ] Encrypted export (`.nexus`; import also accepts `.link`) (profile checkboxes, password + confirm) and import (password, skip/overwrite/duplicate) +- [ ] Command palette: + - [ ] Sections: Connections, Actions, Tabs, Application, Security (lock/unlock), Terminal (Trigger Rules; split/clear/reset when a session is active) + - [ ] Fuzzy ranking, ↑↓ Enter Esc +- [ ] Snippets: search, CRUD (name, description, command, folder), send to the active connected session, `{{hostname|date|time|timestamp}}` built-ins plus prompted variables +- [ ] Triggers: CRUD; name, pattern, regex, case-sensitive, enabled; actions notify/highlight/both/sound; line-buffered matching with CSI stripped +- [ ] Recordings: + - [ ] Browser: list, storage info, delete, cleanup by age (7/30/90) and size (1 GB / 5 GB) + - [ ] Playback: play/pause, restart, scrubber seek (replay to the target), m:ss, speed 0.5–10×, autoplay + +### 10.4 SFTP, RAW, IRC, GitHub, VNC +- [ ] SFTP panel: + - [ ] Layout: toggle, resizable, breadcrumb, Go to path, New folder, Upload, Refresh, Close + - [ ] File list: folders first, `..` entry, extension icons, size with modified-time hint, loading/empty/error + Retry + - [ ] Actions: multi-select, Download (files), Rename (single), Delete (confirm) + - [ ] Bookmarks + - [ ] Transfer queue with progress; refresh when an upload is done; toast when the panel is closed +- [ ] RAW: + - [ ] Views: Text/Hex/Split, Pause/Resume, Clear, Save capture, byte counter, 5000-entry buffer + - [ ] Prefixes: timestamps and direction per the profile + - [ ] Send: Text/Hex/Escaped formats, NUL/CR/LF/ETX/EOT, presets, Enter sends, local echo of the sent entry +- [ ] IRC: + - [ ] Room tabs: Server tab, unread markers, 2000-line buffers + - [ ] Composer: /commands via `IRCHandleInput`, Tab nick completion, click a nick to insert `@nick` + - [ ] Room lists: join box, joined rooms with topic hint, room list (filter, click to join) + - [ ] Users list with prefixes stripped + - [ ] Formatting: all event types formatted as today, nick colour hash, full mIRC formatting (^B ^] ^^ ^_ ^V ^O ^C fg,bg ^D hex) +- [ ] GitHub: + - [ ] Status line, filter, Refresh + - [ ] Ordering: owner groups ordered by newest repo (own login first on ties) + - [ ] Card contents: Private/Fork/Archived badges, last commit and committer, description, language colour, stars, relative pushed time + - [ ] Actions: ZIP (save dialog → `DownloadGitHubRepoArchiveToPath`), Visit +- [ ] VNC: connect (direct or SSH-tunnelled), scaled framebuffer, mouse and keyboard, clipboard, disconnect overlay + +### 10.4a Serial +- [ ] Profiles: create, edit and duplicate, and export/import round-trip including `serialConfig` +- [ ] Editor: device list and refresh +- [ ] Line settings: all baud, data-bit, parity, stop-bit and flow-control options applied +- [ ] Session behaviour: + - [ ] data in the terminal + - [ ] local echo, line ending and encoding + - [ ] DTR/RTS on connect and as toggles + - [ ] Send Break + - [ ] XON/XOFF and RTS/CTS flow control honoured + - [ ] unplug → disconnected overlay; auto-reconnect when the device returns +- [ ] Quick Connect Serial; recording, triggers, broadcast, snippets, find and split panes all work on serial sessions +- [ ] Permission-denied and busy-port hints + +### 10.5 Rename and migration +- [ ] Every user-visible string, window title, bundle, binary and icon says Nexus Terminal +- [ ] First run migrates `AlchementaryLink/` → `NexusTerminal/`, copying the database, vault and recordings, and leaves the old directory intact +- [ ] Pre-rebrand `NexusTerminal/nexus.db` is used as-is +- [ ] Keychain entries under `AlchementaryLink` are found and re-saved under `NexusTerminal` +- [ ] Import accepts `.link` and `.nexus`; export writes `.nexus` +- [ ] An existing Alchementary Link install updates to Nexus through the update server + + +## 11. Existing quirks: default is to fix during the port + +| Quirk | Native default | +|---|---| +| RAW Save passes the capture content as a file path (`raw.js:217` → `SaveSessionCapture(sid, path)`) | Save dialog, pass the path (fixes the bug) | +| Rename prompt's OK button says "Connect" | "Rename" | +| Recording tab dot never shows (wrong selectors) | Indicator drawn by the tab strip | +| `#status-protocol` never filled | Show the active session's protocol | +| Adding or removing a jump host, forward or preset discards unsaved edits | Grid editing keeps them | +| `folder` dropped when switching protocol | Keep it | +| RAW decodes received data as Latin-1 | Decode as UTF-8 in text view (hex view unchanged) | +| SFTP bookmarks keyed by session id (lost on disconnect) | Key by profile id | +| Help says sidebar supports drag between groups (it doesn't) | Fix the Help text; use "Move Group" | +| Trigger "sound" uses a WebAudio 880 Hz beep | the system bell | + +Client-side state that is **not migrated**: trigger rules and SFTP bookmarks live in WebKit localStorage. Users recreate them; everything else is in the SQLite DB, the vault and the keychain, which are read as-is (§3). + +## 12. Tests + +- **Before Go is deleted:** the §3 fixtures. +- **`cargo test` in `nexus-core`:** + - **Compatibility:** every §3 fixture read and round-tripped: database, vault, `.link` exports, recordings, and the keychain on each OS. + - **Migration (§7):** old dir only → copied; new dir only → untouched; both → Nexus wins; keychain fallback. + - **Ported Go test tables:** `validation`, `secrets`, `storage`, `exportimport`, `sshconfig`, `knownhosts`, + `terminalenv`, `rawclient` encoding and history, `models` defaults, and `updater` manifest parsing. + - **Ported JS tests:** IRC mIRC→ANSI (`test-irc.js`), and split-tree lifecycle with late-connect + rejection (`test-session-lifecycle.js`). + - **Pure logic:** fuzzy match, snippet variables, trigger line buffering, and password strength. + - **Serial:** a pty pair on Linux, covering data, echo, line endings and XON/XOFF. +- **SSH integration:** a CI job against an `openssh-server` container, covering password, key, + keyboard-interactive with TOTP, jump host, local and remote forwards, agent forwarding, an SFTP round trip, + and a host-key mismatch. +- **Screenshots:** copy Leviathan's `--screenshot out.png` flag, and check each view under `xvfb-run` in CI. +- `test-regressions.js` is retired; §10 replaces it. From f588a797f9d0bd20396067bf030391ef684d30c1 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sat, 10 Oct 2026 12:58:10 +0100 Subject: [PATCH 02/45] rust: Go-generated compatibility fixtures (db, vault, export, recording) --- cmd/genfixtures/main.go | 89 ++++++++++++++++++ rust/core/tests/fixtures/profiles.link | Bin 0 -> 8222 bytes rust/core/tests/fixtures/recordings/rec1.cast | 3 + 3 files changed, 92 insertions(+) create mode 100644 cmd/genfixtures/main.go create mode 100644 rust/core/tests/fixtures/profiles.link create mode 100644 rust/core/tests/fixtures/recordings/rec1.cast diff --git a/cmd/genfixtures/main.go b/cmd/genfixtures/main.go new file mode 100644 index 0000000..19f118c --- /dev/null +++ b/cmd/genfixtures/main.go @@ -0,0 +1,89 @@ +// Command genfixtures writes the Go-generated compatibility fixtures that the +// Rust port must read unchanged. Run from the repo root: +// +// go run ./cmd/genfixtures rust/core/tests/fixtures +package main + +import ( + "log" + "os" + "path/filepath" + "time" + + "github.com/andydixon/link/internal/exportimport" + "github.com/andydixon/link/internal/models" + "github.com/andydixon/link/internal/recording" + "github.com/andydixon/link/internal/secrets" + "github.com/andydixon/link/internal/storage" +) + +func must(err error) { + if err != nil { + log.Fatal(err) + } +} + +func main() { + out := os.Args[1] + must(os.RemoveAll(out)) + must(os.MkdirAll(out, 0o755)) + + store, err := storage.NewStore(filepath.Join(out, "link.db")) + must(err) + ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) + var profiles []models.Profile + for i, proto := range []models.Protocol{models.ProtocolSSH, models.ProtocolTelnet, models.ProtocolRAW, models.ProtocolGitHub, models.ProtocolVNC, models.ProtocolIRC} { + p := models.NewProfile(proto) + p.ID = "00000000-0000-0000-0000-00000000000" + string(rune('1'+i)) + p.DisplayName = "Fixture " + string(proto) + p.Host = "host-" + string(proto) + ".example" + p.Tags = []string{"fixture", string(proto)} + p.Folder = "Fixtures" + p.Notes = "notes ✓" + p.CreatedAt, p.UpdatedAt = ts, ts + switch proto { + case models.ProtocolSSH: + p.Username = "alice" + p.Favourite = true + p.LastConnected = &ts + p.SSH.PrivateKeyPath = "/home/alice/.ssh/id_ed25519" + p.SSH.JumpHosts = []models.JumpHost{{Host: "jump.example", Port: 2222, Username: "bob", AuthMethod: "publickey", SecretMode: models.SecretModeSaved}} + p.SSH.PortForwards = []models.PortForward{{ID: "pf1", Direction: models.PortForwardLocal, BindHost: "127.0.0.1", BindPort: 8080, DestHost: "localhost", DestPort: 80, Enabled: true, Label: "web"}} + p.SSH.EnvVars = map[string]string{"LANG": "en_GB.UTF-8"} + p.SSH.LoginScripts = []models.LoginScriptStep{{Type: "wait", Value: "$ ", Timeout: 5}, {Type: "send", Value: "uptime"}} + p.SSH.ProxyType = "socks5" + p.SSH.ProxyHost = "proxy.example" + p.SSH.ProxyPort = 1080 + p.SSH.AutoReconnect = true + case models.ProtocolRAW: + p.RAW.PayloadPresets = []models.PayloadPreset{{ID: "pp1", Name: "hello", Payload: "aGVsbG8=", Format: "text"}} + case models.ProtocolIRC: + p.IRC.AutoJoinRooms = []string{"#rust", "#go"} + p.IRC.UseTLS = true + } + must(store.CreateProfile(&p)) + profiles = append(profiles, p) + } + must(store.SaveKnownHost(&models.KnownHost{ID: "kh1", Host: "host-ssh.example", Port: 22, KeyType: "ssh-ed25519", Fingerprint: "SHA256:abc", PublicKey: "AAAA", FirstSeen: ts, LastSeen: ts, Trusted: true})) + must(store.CreateSnippet(&models.Snippet{ID: "sn1", Name: "uptime", Command: "uptime", Description: "load", Folder: "ops", Tags: []string{"x"}, CreatedAt: ts, UpdatedAt: ts})) + must(store.SaveIRCLastRooms(profiles[5].ID, []string{"#rust", "#nexus"})) + prefs := models.DefaultAppPreferences() + prefs.TerminalTheme = "dracula" + prefs.SidebarWidth = 320 + must(store.SavePreferences(&prefs)) + must(store.Close()) + + vault := secrets.NewSecretStore(filepath.Join(out, "vault")) + must(vault.Initialise("correct horse battery staple")) + must(vault.SetSecret("profile:00000000-0000-0000-0000-000000000001:password", "s3cret-ssh")) + must(vault.SetSecret("profile:00000000-0000-0000-0000-000000000001:totp", "JBSWY3DPEHPK3PXP")) + + must(exportimport.EncryptAndExport(profiles, "export-pass", filepath.Join(out, "profiles.link"))) + + rec, err := recording.NewRecorder(filepath.Join(out, "recordings"), "rec1", "Fixture — host", 80, 24) + must(err) + rec.WriteOutput([]byte("hello \x1b[1mworld\x1b[0m\r\n")) + rec.WriteInput([]byte("ls\r")) + must(rec.Close()) + log.Println("fixtures written to", out) +} diff --git a/rust/core/tests/fixtures/profiles.link b/rust/core/tests/fixtures/profiles.link new file mode 100644 index 0000000000000000000000000000000000000000..cf045f9f13e5b9059ff9e8241db378376582cae3 GIT binary patch literal 8222 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 Date: Sun, 11 Oct 2026 16:05:13 +0100 Subject: [PATCH 03/45] rust: nexus-core storage, vault, keychain, export/import, data-dir migration --- rust/.gitignore | 1 + rust/Cargo.lock | 1939 +++++++++++++++++ rust/Cargo.toml | 13 + rust/app/Cargo.toml | 7 + rust/app/assets/fonts/Inter-Regular.ttf | Bin 0 -> 409636 bytes rust/app/assets/fonts/Inter-SemiBold.ttf | Bin 0 -> 417740 bytes .../assets/fonts/JetBrainsMono-Regular.ttf | Bin 0 -> 270184 bytes rust/app/assets/fonts/OFL-Inter.txt | 95 + rust/app/assets/fonts/OFL-JetBrainsMono.txt | 96 + rust/app/src/main.rs | 1 + rust/core/Cargo.toml | 16 + rust/core/migrations/001_initial.sql | 63 + rust/core/migrations/002_github_protocol.sql | 59 + rust/core/migrations/003_snippets.sql | 15 + rust/core/migrations/004_vnc_protocol.sql | 105 + rust/core/migrations/005_irc_protocol.sql | 105 + .../core/migrations/006_irc_channel_state.sql | 15 + rust/core/migrations/007_serial_protocol.sql | 108 + rust/core/src/config.rs | 128 ++ rust/core/src/exportimport.rs | 90 + rust/core/src/lib.rs | 11 + rust/core/src/models.rs | 913 ++++++++ rust/core/src/secrets.rs | 257 +++ rust/core/src/storage.rs | 500 +++++ rust/rust-toolchain.toml | 2 + 25 files changed, 4539 insertions(+) create mode 100644 rust/.gitignore create mode 100644 rust/Cargo.lock create mode 100644 rust/Cargo.toml create mode 100644 rust/app/Cargo.toml create mode 100644 rust/app/assets/fonts/Inter-Regular.ttf create mode 100644 rust/app/assets/fonts/Inter-SemiBold.ttf create mode 100644 rust/app/assets/fonts/JetBrainsMono-Regular.ttf create mode 100644 rust/app/assets/fonts/OFL-Inter.txt create mode 100644 rust/app/assets/fonts/OFL-JetBrainsMono.txt create mode 100644 rust/app/src/main.rs create mode 100644 rust/core/Cargo.toml create mode 100644 rust/core/migrations/001_initial.sql create mode 100644 rust/core/migrations/002_github_protocol.sql create mode 100644 rust/core/migrations/003_snippets.sql create mode 100644 rust/core/migrations/004_vnc_protocol.sql create mode 100644 rust/core/migrations/005_irc_protocol.sql create mode 100644 rust/core/migrations/006_irc_channel_state.sql create mode 100644 rust/core/migrations/007_serial_protocol.sql create mode 100644 rust/core/src/config.rs create mode 100644 rust/core/src/exportimport.rs create mode 100644 rust/core/src/lib.rs create mode 100644 rust/core/src/models.rs create mode 100644 rust/core/src/secrets.rs create mode 100644 rust/core/src/storage.rs create mode 100644 rust/rust-toolchain.toml diff --git a/rust/.gitignore b/rust/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/rust/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000..cb8cc7d --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,1939 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures", + "password-hash", +] + +[[package]] +name = "async-broadcast" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435a87a52755b8f27fcf321ac4f04b2802e337c8c4872923137471ec39c37532" +dependencies = [ + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-process" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" +dependencies = [ + "async-channel", + "async-io", + "async-lock", + "async-signal", + "async-task", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "rustix", +] + +[[package]] +name = "async-recursion" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f8abc12baad266b1c8cec146854c195b5864b4221d4b2ca7296a7ae82d9e451" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "async-signal" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485" +dependencies = [ + "async-io", + "async-lock", + "atomic-waker", + "cfg-if", + "futures-core", + "futures-io", + "rustix", + "signal-hook-registry", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" +dependencies = [ + "generic-array", +] + +[[package]] +name = "blocking" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa" +dependencies = [ + "async-channel", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cbc" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" +dependencies = [ + "cipher", +] + +[[package]] +name = "cc" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "dbus-secret-service" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708b509edf7889e53d7efb0ffadd994cc6c2345ccb62f55cfd6b0682165e4fa6" +dependencies = [ + "aes", + "block-padding", + "cbc", + "dbus", + "fastrand", + "hkdf", + "num", + "once_cell", + "sha2", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "endi" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66b7e2430c6dff6a955451e2cfc438f09cea1965a9d6f87f7e3b90decc014099" + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", + "serde", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "event-listener" +version = "5.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2" +dependencies = [ + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + +[[package]] +name = "fallible-iterator" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "hermit-abi" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "block-padding", + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "js-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keyring" +version = "3.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" +dependencies = [ + "byteorder", + "dbus-secret-service", + "log", + "secret-service", + "security-framework 2.11.1", + "security-framework 3.7.0", + "windows-sys 0.60.2", + "zbus", + "zeroize", +] + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "133c182a6a2c87864fe97778797e46c7e999672690dc9fa3ee8e241aa4a9c13f" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mio" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "nexus" +version = "0.1.0" +dependencies = [ + "nexus-core", +] + +[[package]] +name = "nexus-core" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "argon2", + "base64", + "chrono", + "keyring", + "rand", + "rusqlite", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "nix" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", + "memoffset", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "ordered-stream" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa2b01e1d916879f73a53d01d1d6cee68adbb31d6d9177a8cfce093cced1d50" +dependencies = [ + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rusqlite" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" +dependencies = [ + "bitflags", + "fallible-iterator", + "fallible-streaming-iterator", + "hashlink", + "libsqlite3-sys", + "smallvec", +] + +[[package]] +name = "rustix" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "secret-service" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4d35ad99a181be0a60ffcbe85d680d98f87bdc4d7644ade319b87076b9dbfd4" +dependencies = [ + "aes", + "cbc", + "futures-util", + "generic-array", + "hkdf", + "num", + "once_cell", + "rand", + "serde", + "sha2", + "zbus", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "serde_json" +version = "1.0.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa62079d232416ecf7b2f938d389e28bbcd3677bcdb67b42e2f29c2433552176" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "sha1" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d62a2e0561533f2ca2561d0cf27fd9fedb640a1bf2616ff5d5c80d99017faadc" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio" +version = "1.53.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "toml_datetime" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b86d767906c6c42421dcba507eb9d203e779497710a47782a224bb871653053" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.17+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3641d5bbb5349a79e1020a242d251efbc546ad8048d133958323ce9c40a9c9c" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baa693a8032d7e1cada7d0041e96126df243179ff061456783ac7f12bda4744c" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "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.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "uds_windows" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f6fb2847f6742cd76af783a2a2c49e9375d0a111c7bef6f71cd9e738c72d6e" +dependencies = [ + "memoffset", + "tempfile", + "windows-sys 0.61.2", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + +[[package]] +name = "uuid" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.7", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "xdg-home" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1cdab258fb55c0da61328dc52c8764709b249011b2cad0454c72f0bf10a1f6" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "zbus" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725" +dependencies = [ + "async-broadcast", + "async-executor", + "async-fs", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-sink", + "futures-util", + "hex", + "nix", + "ordered-stream", + "rand", + "serde", + "serde_repr", + "sha1", + "static_assertions", + "tokio", + "tracing", + "uds_windows", + "windows-sys 0.52.0", + "xdg-home", + "zbus_macros", + "zbus_names", + "zvariant", +] + +[[package]] +name = "zbus_macros" +version = "4.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "267db9407081e90bbfa46d841d3cbc60f59c0351838c4bc65199ecd79ab1983e" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils", +] + +[[package]] +name = "zbus_names" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" +dependencies = [ + "serde", + "static_assertions", + "zvariant", +] + +[[package]] +name = "zerocopy" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86502bf56ac7c77571a32e2647bb2a15894565e981fb2a48d7bde2d91c965a9d" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.62" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5457206954b06561e2608c7e19cf58b1926586d999c246eebe4502f7e2039d1a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13084392c5e4bc371903e2935a5eaeed24905a7511356b883835e18a78f6879" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" + +[[package]] +name = "zvariant" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2084290ab9a1c471c38fc524945837734fbf124487e105daec2bb57fd48c81fe" +dependencies = [ + "endi", + "enumflags2", + "serde", + "static_assertions", + "zvariant_derive", +] + +[[package]] +name = "zvariant_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73e2ba546bda683a90652bac4a279bc146adad1386f25379cf73200d2002c449" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", + "zvariant_utils", +] + +[[package]] +name = "zvariant_utils" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51bcff7cc3dbb5055396bcf774748c3dab426b4b8659046963523cee4808340" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000..620c57a --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] +resolver = "3" +members = ["core", "app"] + +[workspace.package] +version = "0.1.0" +edition = "2024" + +[profile.dev.package."*"] +opt-level = 2 + +[profile.release] +lto = "thin" diff --git a/rust/app/Cargo.toml b/rust/app/Cargo.toml new file mode 100644 index 0000000..f2783e9 --- /dev/null +++ b/rust/app/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "nexus" +version.workspace = true +edition.workspace = true + +[dependencies] +nexus-core = { path = "../core" } diff --git a/rust/app/assets/fonts/Inter-Regular.ttf b/rust/app/assets/fonts/Inter-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..ec7fdfee319332dd8b747b7c3c70bfe372826b11 GIT binary patch literal 409636 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 z�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
p@DrMXxxnakx`=i22u=FZJ^ z&2`W9%=O9j&kf35og1DTl^dJ8IX5wPSFSoYE%yL&!*U~YV{+qi6LOPtQ*w2=8M)cH z`ME{8rMcy~Rk?M!4Y^IZExE0^9k~y4jk$xlBb7#F$S|D-hPIf_?qcd(XAfUdn9g3l zqcELD#`>DhKE9l70lsK9oxk!;v&jgNuR9MaxQs2dce;N97ur{YkJ�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 literal 0 HcmV?d00001 diff --git a/rust/app/assets/fonts/Inter-SemiBold.ttf b/rust/app/assets/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000000000000000000000000000000000000..254cc56c8de42207cb69cb7d742b8b6078f377f4 GIT binary patch literal 417740 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, +} + +/// `~/Library/Application Support` on macOS, `%APPDATA%` on Windows, `$XDG_CONFIG_HOME` or +/// `~/.config` elsewhere — the same bases the Go app used. +pub fn base_dir() -> PathBuf { + let home = || std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" }).map(PathBuf::from).unwrap_or_default(); + if cfg!(target_os = "macos") { + home().join("Library/Application Support") + } else if cfg!(windows) { + std::env::var_os("APPDATA").map(PathBuf::from).unwrap_or_else(|| home().join("AppData").join("Roaming")) + } else { + std::env::var_os("XDG_CONFIG_HOME").map(PathBuf::from).unwrap_or_else(|| home().join(".config")) + } +} + +impl AppConfig { + pub fn in_dir(data_dir: PathBuf) -> AppConfig { + AppConfig { + db_path: data_dir.join("nexus.db"), + vault_dir: data_dir.join("vault"), + recordings_dir: data_dir.join("recordings"), + log_path: data_dir.join("nexus.log"), + data_dir, + migrated_from: None, + } + } + + /// Resolves the data directory, migrating legacy data first, and creates the directories. + pub fn load() -> std::io::Result { + let base = base_dir(); + let mut cfg = AppConfig::in_dir(base.join(APP_DIR)); + cfg.migrated_from = migrate_legacy(&base.join(LEGACY_APP_DIR), &cfg)?; + for d in [&cfg.data_dir, &cfg.vault_dir] { + create_private_dir(d)?; + } + Ok(cfg) + } +} + +fn create_private_dir(d: &Path) -> std::io::Result<()> { + std::fs::create_dir_all(d)?; + #[cfg(unix)] + std::fs::set_permissions(d, std::os::unix::fs::PermissionsExt::from_mode(0o700))?; + Ok(()) +} + +/// Copies (never moves) `AlchementaryLink/{link.db, vault/, recordings/}` into the Nexus +/// directory when Nexus has no database yet. A pre-rebrand `NexusTerminal/nexus.db` wins as-is. +pub fn migrate_legacy(legacy: &Path, cfg: &AppConfig) -> std::io::Result> { + let legacy_db = legacy.join("link.db"); + if cfg.db_path.exists() || !legacy_db.exists() { + return Ok(None); + } + create_private_dir(&cfg.data_dir)?; + // Fold the WAL into the main file so the copy is complete. + if let Ok(c) = rusqlite::Connection::open(&legacy_db) { + let _ = c.pragma_update(None, "wal_checkpoint", "TRUNCATE"); + } + std::fs::copy(&legacy_db, &cfg.db_path)?; + copy_dir(&legacy.join("vault"), &cfg.vault_dir)?; + copy_dir(&legacy.join("recordings"), &cfg.recordings_dir)?; + std::fs::write(cfg.data_dir.join("migrated-from"), legacy.to_string_lossy().as_bytes())?; + Ok(Some(legacy.to_path_buf())) +} + +fn copy_dir(from: &Path, to: &Path) -> std::io::Result<()> { + let Ok(rd) = std::fs::read_dir(from) else { return Ok(()) }; + create_private_dir(to)?; + for e in rd.flatten() { + if e.file_type()?.is_file() && !to.join(e.file_name()).exists() { + std::fs::copy(e.path(), to.join(e.file_name()))?; + } + } + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn tmp(name: &str) -> PathBuf { + let d = std::env::temp_dir().join(format!("nexus-cfg-{name}-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&d); + d + } + + #[test] + fn migrates_legacy_once_and_keeps_source() { + let base = tmp("mig"); + let legacy = base.join(LEGACY_APP_DIR); + std::fs::create_dir_all(legacy.join("vault")).unwrap(); + std::fs::write(legacy.join("link.db"), b"db").unwrap(); + std::fs::write(legacy.join("vault/vault.json"), b"{}").unwrap(); + let cfg = AppConfig::in_dir(base.join(APP_DIR)); + assert_eq!(migrate_legacy(&legacy, &cfg).unwrap(), Some(legacy.clone())); + assert_eq!(std::fs::read(&cfg.db_path).unwrap(), b"db"); + assert!(cfg.vault_dir.join("vault.json").exists()); + assert!(legacy.join("link.db").exists()); + // Second run: Nexus already has a database, nothing happens. + std::fs::write(legacy.join("link.db"), b"newer").unwrap(); + assert_eq!(migrate_legacy(&legacy, &cfg).unwrap(), None); + assert_eq!(std::fs::read(&cfg.db_path).unwrap(), b"db"); + std::fs::remove_dir_all(base).unwrap(); + } + + #[test] + fn no_legacy_no_migration() { + let base = tmp("none"); + let cfg = AppConfig::in_dir(base.join(APP_DIR)); + assert_eq!(migrate_legacy(&base.join(LEGACY_APP_DIR), &cfg).unwrap(), None); + } +} diff --git a/rust/core/src/exportimport.rs b/rust/core/src/exportimport.rs new file mode 100644 index 0000000..b640a50 --- /dev/null +++ b/rust/core/src/exportimport.rs @@ -0,0 +1,90 @@ +//! Password-encrypted profile exports: `[version 1][salt 32][nonce 12][AES-256-GCM ciphertext]` +//! over a JSON bundle, with the vault's Argon2id parameters. `.link` files from Alchementary +//! Link are the same format. + +use crate::models::Profile; +use crate::secrets::{derive_key, random_bytes}; +use crate::Result; +use aes_gcm::aead::{Aead, KeyInit}; +use aes_gcm::{Aes256Gcm, Nonce}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +const VERSION: u8 = 1; +const MAX_IMPORT_BYTES: u64 = 10 * 1024 * 1024; +pub const EXTENSION: &str = "nexus"; + +#[derive(Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct Bundle { + version: i64, + exported_at: String, + #[serde(default)] + profiles: Vec, + count: usize, +} + +pub fn encrypt_and_export(profiles: &[Profile], password: &str, path: &Path) -> Result<()> { + if password.is_empty() { + return Err("exportimport: password cannot be empty".into()); + } + if profiles.is_empty() { + return Err("exportimport: no profiles to export".into()); + } + let bundle = Bundle { + version: 1, + exported_at: chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true), + profiles: profiles.to_vec(), + count: profiles.len(), + }; + let plain = serde_json::to_vec_pretty(&bundle)?; + let salt = random_bytes::<32>(); + let nonce = random_bytes::<12>(); + let key = derive_key(password, &salt)?; + let ct = Aes256Gcm::new((&key).into()).encrypt(Nonce::from_slice(&nonce), plain.as_slice()).map_err(|e| e.to_string())?; + let mut out = vec![VERSION]; + out.extend(salt); + out.extend(nonce); + out.extend(ct); + crate::secrets::write_private(path, &out) +} + +pub fn decrypt_and_import(path: &Path, password: &str) -> Result> { + if password.is_empty() { + return Err("exportimport: password cannot be empty".into()); + } + if std::fs::metadata(path)?.len() > MAX_IMPORT_BYTES { + return Err("exportimport: file is too large (max 10 MiB)".into()); + } + let data = std::fs::read(path)?; + if data.len() < 1 + 32 + 12 + 1 + 16 { + return Err("exportimport: file is too small or corrupt".into()); + } + if data[0] != VERSION { + return Err(format!("exportimport: unsupported format version {}", data[0]).into()); + } + let key = derive_key(password, &data[1..33])?; + let plain = Aes256Gcm::new((&key).into()) + .decrypt(Nonce::from_slice(&data[33..45]), &data[45..]) + .map_err(|_| "exportimport: decryption failed — wrong password or corrupt file")?; + let bundle: Bundle = serde_json::from_slice(&plain).map_err(|e| format!("exportimport: invalid bundle JSON: {e}"))?; + Ok(bundle.profiles) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reads_go_export_and_round_trips() { + let fx = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/profiles.link"); + assert!(decrypt_and_import(&fx, "nope").is_err()); + let ps = decrypt_and_import(&fx, "export-pass").unwrap(); + assert_eq!(ps.len(), 6); + assert_eq!(ps[0].ssh.as_ref().unwrap().jump_hosts[0].port, 2222); + let out = std::env::temp_dir().join(format!("nexus-export-{}.nexus", std::process::id())); + encrypt_and_export(&ps, "pw", &out).unwrap(); + assert_eq!(decrypt_and_import(&out, "pw").unwrap(), ps); + std::fs::remove_file(out).unwrap(); + } +} diff --git a/rust/core/src/lib.rs b/rust/core/src/lib.rs new file mode 100644 index 0000000..e17c290 --- /dev/null +++ b/rust/core/src/lib.rs @@ -0,0 +1,11 @@ +//! nexus-core: everything in Nexus Terminal that isn't drawing — storage, secrets, protocol +//! clients and the session manager. The egui app is one client of this crate. + +pub mod config; +pub mod exportimport; +pub mod models; +pub mod secrets; +pub mod storage; + +pub type Error = Box; +pub type Result = std::result::Result; diff --git a/rust/core/src/models.rs b/rust/core/src/models.rs new file mode 100644 index 0000000..a51a790 --- /dev/null +++ b/rust/core/src/models.rs @@ -0,0 +1,913 @@ +//! Data model shared by storage, the session manager and the UI. Field names serialise exactly as +//! the Go structs did, so database JSON columns and `.link`/`.nexus` exports round-trip unchanged. + +use chrono::{DateTime, FixedOffset, Local}; +use serde::{Deserialize, Deserializer, Serialize}; +use std::collections::BTreeMap; + +pub const DEFAULT_SSH_PORT: u16 = 22; +pub const DEFAULT_TELNET_PORT: u16 = 23; +pub const DEFAULT_RAW_PORT: u16 = 23; +pub const DEFAULT_GITHUB_PORT: u16 = 443; +pub const DEFAULT_VNC_PORT: u16 = 5900; +pub const DEFAULT_IRC_PORT: u16 = 6667; +pub const DEFAULT_IRC_TLS_PORT: u16 = 6697; +pub const DEFAULT_CONNECT_TIMEOUT: i64 = 30; +pub const DEFAULT_SCROLLBACK: i64 = 10000; +pub const DEFAULT_FONT_SIZE: i64 = 14; +pub const DEFAULT_FONT_FAMILY: &str = "JetBrains Mono, Menlo, Monaco, Consolas, monospace"; +pub const DEFAULT_CURSOR_STYLE: &str = "block"; +pub const DEFAULT_TERMINAL_TYPE: &str = "xterm-256color"; +pub const DEFAULT_KEEPALIVE_INTERVAL: i64 = 30; +pub const DEFAULT_KEEPALIVE_COUNT_MAX: i64 = 3; + +pub type Time = DateTime; + +pub fn now() -> Time { + Local::now().fixed_offset() +} + +/// Go's zero `time.Time`, which is what a missing timestamp decodes to. +pub fn zero_time() -> Time { + DateTime::parse_from_rfc3339("0001-01-01T00:00:00Z").unwrap() +} + +/// Go writes nil slices and maps as `null`; treat that as empty. +fn nd<'de, D: Deserializer<'de>, T: Default + Deserialize<'de>>(d: D) -> Result { + Ok(Option::::deserialize(d)?.unwrap_or_default()) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum Protocol { + #[default] + Ssh, + Telnet, + Raw, + Github, + Vnc, + Irc, + Serial, + Local, +} + +impl Protocol { + pub const SAVED: [Protocol; 7] = + [Protocol::Ssh, Protocol::Telnet, Protocol::Raw, Protocol::Github, Protocol::Vnc, Protocol::Irc, Protocol::Serial]; + pub fn id(self) -> &'static str { + match self { + Protocol::Ssh => "ssh", + Protocol::Telnet => "telnet", + Protocol::Raw => "raw", + Protocol::Github => "github", + Protocol::Vnc => "vnc", + Protocol::Irc => "irc", + Protocol::Serial => "serial", + Protocol::Local => "local", + } + } + pub fn from_id(s: &str) -> Option { + [Protocol::Local].into_iter().chain(Self::SAVED).find(|p| p.id() == s) + } + pub fn label(self) -> &'static str { + match self { + Protocol::Ssh => "SSH", + Protocol::Telnet => "Telnet", + Protocol::Raw => "RAW TCP", + Protocol::Github => "GitHub", + Protocol::Vnc => "VNC", + Protocol::Irc => "IRC", + Protocol::Serial => "Serial", + Protocol::Local => "Local", + } + } + /// Protocols whose sessions render in a terminal view. + pub fn is_terminal(self) -> bool { + matches!(self, Protocol::Ssh | Protocol::Telnet | Protocol::Serial | Protocol::Local) + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +pub enum HostKeyPolicy { + #[serde(rename = "strict")] + Strict, + #[default] + #[serde(rename = "ask")] + Ask, + #[serde(rename = "accept-new")] + AcceptNew, + #[serde(rename = "insecure")] + Insecure, +} + +impl HostKeyPolicy { + pub const ALL: [HostKeyPolicy; 4] = [Self::Strict, Self::Ask, Self::AcceptNew, Self::Insecure]; + pub fn label(self) -> &'static str { + match self { + Self::Strict => "Strict (known hosts only)", + Self::Ask => "Ask on new or changed keys", + Self::AcceptNew => "Accept new, reject changed", + Self::Insecure => "Insecure (accept anything)", + } + } +} + +/// How a credential is obtained. Unknown strings (and the legacy "prompt") are kept as +/// ask-every-time, matching Go's normaliseSecretMode. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Default)] +pub enum SecretMode { + #[default] + #[serde(rename = "saved")] + Saved, + #[serde(rename = "session-only")] + SessionOnly, + #[serde(rename = "ask-every-time")] + AskEveryTime, +} + +impl<'de> Deserialize<'de> for SecretMode { + fn deserialize>(d: D) -> Result { + Ok(match Option::::deserialize(d)?.as_deref().unwrap_or("") { + "saved" | "" => SecretMode::Saved, + "session-only" => SecretMode::SessionOnly, + _ => SecretMode::AskEveryTime, + }) + } +} + +impl SecretMode { + pub const ALL: [SecretMode; 3] = [Self::Saved, Self::SessionOnly, Self::AskEveryTime]; + pub fn label(self) -> &'static str { + match self { + Self::Saved => "Save securely", + Self::SessionOnly => "Remember for this session", + Self::AskEveryTime => "Ask every time", + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Default)] +#[serde(rename_all = "lowercase")] +pub enum LineEnding { + Lf, + Cr, + #[default] + Crlf, + None, +} + +impl LineEnding { + pub const ALL: [LineEnding; 4] = [Self::Crlf, Self::Lf, Self::Cr, Self::None]; + pub fn bytes(self) -> &'static [u8] { + match self { + Self::Lf => b"\n", + Self::Cr => b"\r", + Self::Crlf => b"\r\n", + Self::None => b"", + } + } + pub fn label(self) -> &'static str { + match self { + Self::Lf => "LF (\\n)", + Self::Cr => "CR (\\r)", + Self::Crlf => "CRLF (\\r\\n)", + Self::None => "None", + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Profile { + #[serde(default)] + pub id: String, + #[serde(default)] + pub display_name: String, + #[serde(default)] + pub description: String, + #[serde(default)] + pub protocol: Protocol, + #[serde(default)] + pub host: String, + #[serde(default)] + pub port: i64, + #[serde(default)] + pub username: String, + #[serde(default, deserialize_with = "nd")] + pub tags: Vec, + #[serde(default)] + pub folder: String, + #[serde(default)] + pub environment: String, + #[serde(default)] + pub notes: String, + #[serde(default)] + pub favourite: bool, + #[serde(default)] + pub last_connected: Option

/.cast` and writes the header. + pub fn new(dir: &Path, id: &str, title: &str, cols: i64, rows: i64) -> Result { + Self::with_limit(dir, id, title, cols, rows, MAX_RECORDING_BYTES) + } + + fn with_limit(dir: &Path, id: &str, title: &str, cols: i64, rows: i64, max_bytes: u64) -> Result { + mkdir_private(dir).map_err(|e| format!("recording: failed to create directory: {e}"))?; + let path = dir.join(format!("{id}.cast")); + let mut f = File::create(&path).map_err(|e| format!("recording: failed to create file: {e}"))?; + let start = Instant::now(); + let hdr = Header { + version: 2, + width: cols, + height: rows, + timestamp: chrono::Utc::now().timestamp(), + title: title.into(), + env: BTreeMap::from([("TERM".into(), "xterm-256color".into())]), + }; + let line = json_line(&hdr)?; + if let Err(e) = f.write_all(&line) { + drop(f); + let _ = std::fs::remove_file(&path); + return Err(format!("recording: failed to write header: {e}").into()); + } + Ok(Recorder { + inner: Mutex::new(Inner { file: Some(f), closed: false, event_count: 0, bytes_written: line.len() as u64 }), + start, + file_path: path, + max_bytes, + }) + } + + /// Records data from the remote host. + pub fn write_output(&self, data: &[u8]) { + self.write_event(EVENT_OUTPUT, data) + } + + /// Records data from the user. + pub fn write_input(&self, data: &[u8]) { + self.write_event(EVENT_INPUT, data) + } + + fn write_event(&self, kind: &str, data: &[u8]) { + let mut g = self.inner.lock().unwrap(); + if g.closed || g.file.is_none() { + return; + } + let elapsed = self.start.elapsed().as_secs_f64(); + let Ok(line) = json_line(&(elapsed, kind, String::from_utf8_lossy(data))) else { return }; + if self.max_bytes > 0 && g.bytes_written + line.len() as u64 > self.max_bytes { + g.closed = true; + if let Some(f) = g.file.take() { + let _ = f.sync_all(); + } + return; + } + if g.file.as_mut().unwrap().write_all(&line).is_ok() { + g.bytes_written += line.len() as u64; + g.event_count += 1; + } + } + + /// Flushes and closes the file. Idempotent. + pub fn close(&self) -> Result<()> { + let mut g = self.inner.lock().unwrap(); + if g.closed { + return Ok(()); + } + g.closed = true; + if let Some(f) = g.file.take() { + f.sync_all().map_err(|e| format!("recording: sync failed: {e}"))?; + } + Ok(()) + } + + /// Seconds since recording started. + pub fn duration(&self) -> f64 { + self.start.elapsed().as_secs_f64() + } + + pub fn file_path(&self) -> &Path { + &self.file_path + } + + pub fn event_count(&self) -> usize { + self.inner.lock().unwrap().event_count + } +} + +/// The recordings directory: listing, playback, deletion and retention. +#[derive(Clone, Debug)] +pub struct Store { + dir: PathBuf, +} + +impl Store { + pub fn new(dir: impl Into) -> Store { + Store { dir: dir.into() } + } + + pub fn dir(&self) -> &Path { + &self.dir + } + + /// Metadata for every readable `.cast` file, newest first. + pub fn list(&self) -> Result> { + mkdir_private(&self.dir)?; + let entries = std::fs::read_dir(&self.dir).map_err(|e| format!("recording: failed to read directory: {e}"))?; + let mut out: Vec = entries + .flatten() + .filter(|e| !e.file_type().is_ok_and(|t| t.is_dir())) + .filter_map(|e| e.file_name().into_string().ok()) + .filter(|n| n.ends_with(".cast")) + .filter_map(|n| self.read_meta(&n).ok()) // skip unreadable files + .collect(); + out.sort_by_key(|m| std::cmp::Reverse(m.created_at)); + Ok(out) + } + + /// Reads the full recording (header + events). + pub fn get(&self, id: &str) -> Result<(Header, Vec)> { + self.get_with_limit(id, MAX_PLAYBACK_BYTES) + } + + fn get_with_limit(&self, id: &str, max_bytes: u64) -> Result<(Header, Vec)> { + let path = self.recording_path(id)?; + let size = std::fs::metadata(&path).map_err(|e| format!("recording: failed to stat file: {e}"))?.len(); + if max_bytes > 0 && size > max_bytes { + return Err(format!("recording: file is too large for playback (max {max_bytes} bytes)").into()); + } + let raw = std::fs::read(&path).map_err(|e| format!("recording: failed to open file: {e}"))?; + let text = String::from_utf8_lossy(&raw); + let mut lines = text.lines(); + let Some(first) = lines.next() else { return Err("recording: empty file".into()) }; + if first.len() > MAX_PLAYBACK_LINE_BYTES { + return Err("recording: failed to scan header: token too long".into()); + } + let hdr: Header = serde_json::from_str(first.trim()).map_err(|e| format!("recording: invalid header: {e}"))?; + let mut events = Vec::new(); + for line in lines { + if line.len() > MAX_PLAYBACK_LINE_BYTES { + return Err("recording: failed to scan events: token too long".into()); + } + let line = line.trim(); + if line.is_empty() { + continue; + } + if let Ok((elapsed, kind, data)) = serde_json::from_str::<(f64, String, String)>(line) { + events.push(Event { elapsed, kind, data }); + } + } + Ok((hdr, events)) + } + + pub fn delete(&self, id: &str) -> Result<()> { + let path = self.recording_path(id)?; + std::fs::remove_file(path).map_err(|e| format!("recording: failed to delete: {e}"))?; + Ok(()) + } + + pub fn storage_info(&self) -> Result { + let metas = self.list()?; + Ok(StorageInfo { + count: metas.len(), + total_bytes: metas.iter().map(|m| m.size).sum(), + oldest_created_at: metas.iter().map(|m| m.created_at).min(), + newest_created_at: metas.iter().map(|m| m.created_at).max(), + max_recording_bytes: MAX_RECORDING_BYTES, + max_playback_bytes: MAX_PLAYBACK_BYTES, + }) + } + + /// Removes recordings older than `max_age_days`, then the oldest until the total is at most + /// `max_total_bytes`. Zero disables either limit. + pub fn cleanup(&self, max_age_days: u32, max_total_bytes: u64) -> Result { + let mut metas = self.list()?; + let mut result = CleanupResult::default(); + let mut deleted = std::collections::HashSet::new(); + let mut total: u64 = metas.iter().map(|m| m.size).sum(); + let mut delete = |m: &RecordingMeta, total: &mut u64| { + if !deleted.contains(&m.id) && self.delete(&m.id).is_ok() { + deleted.insert(m.id.clone()); + result.deleted += 1; + result.freed_bytes += m.size; + *total -= m.size; + } + }; + if max_age_days > 0 { + let cutoff = chrono::Utc::now() - chrono::Duration::days(max_age_days.into()); + for m in &metas { + if m.created_at < cutoff { + delete(m, &mut total); + } + } + } + if max_total_bytes > 0 && total > max_total_bytes { + metas.sort_by_key(|m| m.created_at); + for m in &metas { + if total <= max_total_bytes { + break; + } + delete(m, &mut total); + } + } + Ok(result) + } + + fn recording_path(&self, id: &str) -> Result { + let id = id.trim(); + if id.is_empty() || id == "." || id == ".." || id.contains(['/', '\\']) { + return Err("recording: invalid recording ID".into()); + } + Ok(self.dir.join(format!("{id}.cast"))) + } + + /// Header fields plus the last event's timestamp as the duration. + fn read_meta(&self, filename: &str) -> Result { + let path = self.dir.join(filename); + let mut f = File::open(&path)?; + let size = f.metadata()?.len(); + let mut raw = Vec::new(); + f.read_to_end(&mut raw)?; + let mut values = serde_json::Deserializer::from_slice(&raw).into_iter::(); + let hdr: Header = serde_json::from_value(values.next().ok_or("recording: empty file")??)?; + let mut duration = 0.0; + for v in values { + let Ok(v) = v else { break }; + if let Some(e) = v.get(0).and_then(|e| e.as_f64()) { + duration = e; + } + } + let id = filename.trim_end_matches(".cast").to_string(); + let created_at = chrono::DateTime::from_timestamp(hdr.timestamp, 0) + .ok_or("recording: invalid timestamp")? + .with_timezone(&chrono::Local) + .fixed_offset(); + Ok(RecordingMeta { + session_id: id.clone(), // recording ID matches session ID by default + id, + title: hdr.title, + width: hdr.width, + height: hdr.height, + duration, + size, + created_at, + file_path: path, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn tmp(name: &str) -> PathBuf { + let d = std::env::temp_dir().join(format!("nexus-rec-{name}-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&d); + d + } + + #[test] + fn reads_go_fixture() { + let store = Store::new(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/recordings")); + let (hdr, events) = store.get("rec1").unwrap(); + assert_eq!((hdr.version, hdr.width, hdr.height, hdr.timestamp), (2, 80, 24, 1791633486)); + assert_eq!(hdr.title, "Fixture — host"); + assert_eq!(hdr.env["TERM"], "xterm-256color"); + assert_eq!(events.len(), 2); + assert_eq!((events[0].kind.as_str(), events[0].data.as_str()), ("o", "hello \x1b[1mworld\x1b[0m\r\n")); + assert_eq!((events[1].kind.as_str(), events[1].data.as_str()), ("i", "ls\r")); + let metas = store.list().unwrap(); + assert_eq!(metas.len(), 1); + assert_eq!(metas[0].duration, 0.000212149); + } + + #[test] + fn write_and_close() { + let dir = tmp("write"); + let rec = Recorder::new(&dir, "test-session-001", "Test Recording", 80, 24).unwrap(); + rec.write_output(b"Welcome to the server\r\n"); + rec.write_output(b"$ "); + rec.write_input(b"ls -la\r\n"); + rec.write_output(b"total 42\r\n"); + assert_eq!(rec.event_count(), 4); + rec.close().unwrap(); + rec.close().unwrap(); // double close is a no-op + let text = std::fs::read_to_string(dir.join("test-session-001.cast")).unwrap(); + let lines: Vec<&str> = text.trim().lines().collect(); + assert_eq!(lines.len(), 5); + let hdr: Header = serde_json::from_str(lines[0]).unwrap(); + assert_eq!((hdr.version, hdr.width, hdr.height), (2, 80, 24)); + let (_, kind, _): (f64, String, String) = serde_json::from_str(lines[1]).unwrap(); + assert_eq!(kind, "o"); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn list_get_delete() { + let dir = tmp("list"); + let rec = Recorder::new(&dir, "session-abc", "My Session", 120, 40).unwrap(); + rec.write_output(b"hello world\r\n"); + rec.write_output(b"done\r\n"); + rec.close().unwrap(); + let store = Store::new(&dir); + let metas = store.list().unwrap(); + assert_eq!(metas.len(), 1); + assert_eq!((metas[0].id.as_str(), metas[0].width, metas[0].height), ("session-abc", 120, 40)); + let (hdr, events) = store.get("session-abc").unwrap(); + assert_eq!(hdr.title, "My Session"); + assert_eq!(events.len(), 2); + store.delete("session-abc").unwrap(); + assert!(store.list().unwrap().is_empty()); + assert!(store.delete("../outside").is_err()); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn stops_at_size_limit() { + let dir = tmp("limit"); + let rec = Recorder::with_limit(&dir, "limited", "Limited", 80, 24, 512).unwrap(); + for _ in 0..50 { + rec.write_output("x".repeat(80).as_bytes()); + } + assert!(rec.event_count() < 50); + rec.close().unwrap(); + assert!(std::fs::metadata(dir.join("limited.cast")).unwrap().len() <= 512); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn rejects_oversized_playback() { + let dir = tmp("huge"); + std::fs::create_dir_all(&dir).unwrap(); + let body = format!("{{\"version\":2,\"width\":80,\"height\":24,\"timestamp\":1}}\n{}", "x".repeat(256)); + std::fs::write(dir.join("huge.cast"), body).unwrap(); + let err = Store::new(&dir).get_with_limit("huge", 128).unwrap_err(); + assert!(err.to_string().contains("too large")); + std::fs::remove_dir_all(dir).unwrap(); + } + + #[test] + fn cleanup_by_age_and_size() { + let dir = tmp("cleanup"); + let make = |id: &str, age_secs: i64, payload: &str| { + let rec = Recorder::new(&dir, id, id, 80, 24).unwrap(); + rec.write_output(payload.as_bytes()); + rec.close().unwrap(); + let path = dir.join(format!("{id}.cast")); + let text = std::fs::read_to_string(&path).unwrap(); + let (first, rest) = text.split_once('\n').unwrap(); + let mut hdr: Header = serde_json::from_str(first).unwrap(); + hdr.timestamp = chrono::Utc::now().timestamp() - age_secs; + let mut out = json_line(&hdr).unwrap(); + out.extend(rest.as_bytes()); + std::fs::write(&path, out).unwrap(); + }; + make("old", 45 * 86400, "old"); + make("recent", 2 * 3600, &"recent".repeat(50)); + let store = Store::new(&dir); + assert_eq!(store.cleanup(30, 0).unwrap().deleted, 1); + assert!(!dir.join("old.cast").exists()); + let info = store.storage_info().unwrap(); + assert_eq!(info.count, 1); + assert_eq!(store.cleanup(0, info.total_bytes - 1).unwrap().deleted, 1); + std::fs::remove_dir_all(dir).unwrap(); + } +} diff --git a/rust/core/src/sshconfig.rs b/rust/core/src/sshconfig.rs new file mode 100644 index 0000000..f3cdcfb --- /dev/null +++ b/rust/core/src/sshconfig.rs @@ -0,0 +1,281 @@ +//! Parses OpenSSH `~/.ssh/config` Host blocks into SSH profiles. Supports HostName, Port, User, +//! IdentityFile, ProxyJump (first hop), ForwardAgent and Compression. Match blocks, wildcard-only +//! hosts, Include and `%h`-style tokens are ignored, as in the Go app. + +use crate::Result; +use crate::models::{HostKeyPolicy, JumpHost, Profile, Protocol, SecretMode, SshConfig}; +use std::path::{Path, PathBuf}; + +const MAX_CONFIG_FILE_BYTES: u64 = 5 * 1024 * 1024; +const MAX_CONFIG_LINE_BYTES: usize = 256 * 1024; +const MAX_HOST_BLOCKS: usize = 5000; + +#[derive(Clone, Debug, PartialEq, Default)] +pub struct HostBlock { + pub aliases: Vec, + pub host_name: String, + pub port: i64, + pub user: String, + pub identity_file: String, + pub proxy_jump: String, + pub forward_agent: bool, + pub compression: bool, +} + +pub fn parse_file(path: &Path) -> Result> { + let p = path.display(); + let meta = std::fs::metadata(path).map_err(|e| format!("sshconfig: failed to open {p}: {e}"))?; + if meta.is_dir() { + return Err(format!("sshconfig: {p} is a directory").into()); + } + if meta.len() > MAX_CONFIG_FILE_BYTES { + return Err(format!( + "sshconfig: config file is too large: {} bytes exceeds {MAX_CONFIG_FILE_BYTES} bytes", + meta.len() + ) + .into()); + } + let raw = std::fs::read(path).map_err(|e| format!("sshconfig: failed to open {p}: {e}"))?; + parse(&String::from_utf8_lossy(&raw)) +} + +fn parse(text: &str) -> Result> { + let mut blocks = Vec::new(); + let mut current: Option = None; + let mut commit = |cur: &mut Option| -> Result<()> { + if let Some(b) = cur.take() { + if blocks.len() >= MAX_HOST_BLOCKS { + return Err(format!("sshconfig: too many Host blocks (max {MAX_HOST_BLOCKS})").into()); + } + blocks.push(b); + } + Ok(()) + }; + for line in text.lines() { + if line.len() > MAX_CONFIG_LINE_BYTES { + return Err("sshconfig: read error: token too long".into()); + } + let line = line.trim(); + if line.is_empty() || line.starts_with('#') { + continue; + } + let (key, value) = split_directive(line); + match key.to_lowercase().as_str() { + "host" => { + commit(&mut current)?; + let aliases: Vec = value.split_whitespace().map(String::from).collect(); + // Skip wildcard-only hosts (e.g. "Host *"). + let wildcard = aliases.iter().any(|a| a.contains(['*', '?'])); + if !(wildcard && aliases.len() == 1) { + current = Some(HostBlock { aliases, port: 22, ..Default::default() }); + } + } + "match" => commit(&mut current)?, + key => { + let Some(b) = current.as_mut() else { continue }; + match key { + "hostname" => b.host_name = value.into(), + "port" => { + if let Ok(p) = value.parse() { + b.port = p + } + } + "user" => b.user = value.into(), + "identityfile" => b.identity_file = expand_tilde(value), + "proxyjump" => b.proxy_jump = value.into(), + "forwardagent" => b.forward_agent = is_trueish(value), + "compression" => b.compression = is_trueish(value), + _ => {} + } + } + } + } + commit(&mut current)?; + Ok(blocks) +} + +/// `~/.ssh/config`, if the home directory is known. +pub fn default_config_path() -> Option { + std::env::home_dir().map(|h| h.join(".ssh").join("config")) +} + +/// One SSH profile per block; the first alias becomes the display name. +pub fn to_profiles(blocks: &[HostBlock], folder: &str) -> Vec { + blocks + .iter() + .filter(|b| !b.aliases.is_empty()) + .map(|b| { + let mut ssh = SshConfig { + auth_methods: vec!["publickey".into(), "password".into()], + secret_mode: SecretMode::AskEveryTime, + host_key_policy: HostKeyPolicy::Ask, + terminal_type: "xterm-256color".into(), + agent_auth: b.forward_agent, + agent_forwarding: b.forward_agent, + compression: b.compression, + private_key_path: b.identity_file.clone(), + ..Default::default() + }; + if !b.proxy_jump.is_empty() && b.proxy_jump != "none" { + ssh.jump_hosts = parse_proxy_jump(&b.proxy_jump).into_iter().collect(); + } + let mut p = Profile::new(Protocol::Ssh); + p.display_name = b.aliases[0].clone(); + p.host = if b.host_name.is_empty() { b.aliases[0].clone() } else { b.host_name.clone() }; + p.port = b.port; + p.username = b.user.clone(); + p.folder = folder.into(); + p.ssh = Some(ssh); + p.tags = vec!["ssh-config-import".into()]; + p + }) + .collect() +} + +/// `[user@]host[:port]`; only the first hop of a comma-separated chain. +fn parse_proxy_jump(value: &str) -> Option { + let mut value = value.split(',').next().unwrap_or("").trim(); + if value.is_empty() || value == "none" { + return None; + } + let mut jh = + JumpHost { port: 22, auth_method: "publickey".into(), secret_mode: SecretMode::AskEveryTime, ..Default::default() }; + if let Some((user, rest)) = value.split_once('@') { + jh.username = user.into(); + value = rest; + } + if let Some((host, port)) = value.rsplit_once(':') + && let Ok(p) = port.parse() + { + jh.port = p; + value = host; + } + jh.host = value.into(); + Some(jh) +} + +/// `Key Value` or `Key=Value`. +fn split_directive(line: &str) -> (&str, &str) { + if let Some((k, v)) = line.split_once('=') { + return (k.trim(), v.trim()); + } + match line.split_once(' ').or_else(|| line.split_once('\t')) { + Some((k, v)) => (k.trim(), v.trim()), + None => (line, ""), + } +} + +fn expand_tilde(path: &str) -> String { + match (path.strip_prefix("~/"), std::env::home_dir()) { + (Some(rest), Some(home)) => home.join(rest).to_string_lossy().into_owned(), + _ => path.into(), + } +} + +fn is_trueish(v: &str) -> bool { + matches!(v.to_lowercase().as_str(), "yes" | "true" | "on" | "1") +} + +#[cfg(test)] +mod tests { + use super::*; + + const TEST_CONFIG: &str = "# Test SSH config + +Host * + ServerAliveInterval 60 + +Host webserver + HostName web.example.com + Port 2222 + User deploy + IdentityFile ~/.ssh/deploy_key + ForwardAgent yes + Compression yes + +Host dbserver + HostName db.internal.example.com + User postgres + ProxyJump bastion@jump.example.com:2200 + +Host simple + HostName 10.0.0.5 + +Host wildcard-* + User admin +"; + + #[test] + fn parses_file() { + let path = std::env::temp_dir().join(format!("nexus-sshconfig-{}", std::process::id())); + std::fs::write(&path, TEST_CONFIG).unwrap(); + let blocks = parse_file(&path).unwrap(); + std::fs::remove_file(&path).unwrap(); + assert_eq!(blocks.len(), 3); + let wb = &blocks[0]; + assert_eq!(wb.aliases[0], "webserver"); + assert_eq!((wb.host_name.as_str(), wb.port, wb.user.as_str()), ("web.example.com", 2222, "deploy")); + assert!(wb.forward_agent && wb.compression); + assert!(wb.identity_file.ends_with(".ssh/deploy_key") && !wb.identity_file.starts_with('~')); + assert_eq!(blocks[1].aliases[0], "dbserver"); + assert_eq!(blocks[1].proxy_jump, "bastion@jump.example.com:2200"); + assert_eq!((blocks[2].host_name.as_str(), blocks[2].port), ("10.0.0.5", 22)); + } + + #[test] + fn rejects_oversized_file() { + let path = std::env::temp_dir().join(format!("nexus-sshconfig-big-{}", std::process::id())); + std::fs::File::create(&path).unwrap().set_len(MAX_CONFIG_FILE_BYTES + 1).unwrap(); + let err = parse_file(&path).unwrap_err(); + std::fs::remove_file(&path).unwrap(); + assert!(err.to_string().contains("too large")); + } + + #[test] + fn rejects_too_many_host_blocks() { + let text: String = (0..=MAX_HOST_BLOCKS).map(|i| format!("Host host{i}\n HostName example.com\n")).collect(); + assert!(parse(&text).unwrap_err().to_string().contains("too many Host blocks")); + } + + #[test] + fn converts_to_profiles() { + let blocks = vec![ + HostBlock { + aliases: vec!["myserver".into()], + host_name: "srv.example.com".into(), + port: 22, + user: "admin".into(), + forward_agent: true, + ..Default::default() + }, + HostBlock { + aliases: vec!["jumpbox".into()], + host_name: "jump.example.com".into(), + port: 2200, + user: "jumper".into(), + proxy_jump: "gateway@gw.example.com".into(), + ..Default::default() + }, + ]; + let ps = to_profiles(&blocks, "Imported"); + assert_eq!(ps.len(), 2); + assert_eq!((ps[0].display_name.as_str(), ps[0].host.as_str()), ("myserver", "srv.example.com")); + assert_eq!(ps[0].folder, "Imported"); + assert!(ps[0].ssh.as_ref().unwrap().agent_auth); + let jh = &ps[1].ssh.as_ref().unwrap().jump_hosts[0]; + assert_eq!((jh.host.as_str(), jh.username.as_str()), ("gw.example.com", "gateway")); + } + + #[test] + fn parses_proxy_jump() { + for (input, host, user, port) in [ + ("bastion.example.com", "bastion.example.com", "", 22), + ("user@bastion.example.com", "bastion.example.com", "user", 22), + ("user@bastion.example.com:2200", "bastion.example.com", "user", 2200), + ("host:443", "host", "", 443), + ] { + let jh = parse_proxy_jump(input).unwrap(); + assert_eq!((jh.host.as_str(), jh.username.as_str(), jh.port), (host, user, port), "{input}"); + } + } +} diff --git a/rust/core/src/terminalenv.rs b/rust/core/src/terminalenv.rs new file mode 100644 index 0000000..b43ee4e --- /dev/null +++ b/rust/core/src/terminalenv.rs @@ -0,0 +1,109 @@ +//! Environment variables for terminal sessions: TERM and a UTF-8 character locale. + +use std::collections::BTreeMap; + +const LANG: &str = "LANG"; +const LC_ALL: &str = "LC_ALL"; +const LC_CTYPE: &str = "LC_CTYPE"; +const TERM: &str = "TERM"; +const DEFAULT_TERM: &str = "xterm-256color"; + +/// `KEY=VALUE` entries for a local interactive PTY. +pub fn local_shell_env(env: Vec) -> Vec { + ensure_utf8_character_locale(set_env(env, TERM, DEFAULT_TERM), std::env::consts::OS) +} + +/// Variables to request for an SSH session. A locale set by the profile is kept as is; otherwise +/// a UTF-8 `LC_CTYPE` is added. Keys are trimmed and empty keys dropped. Iteration order is sorted. +pub fn ssh_env_vars(profile_env: &BTreeMap) -> BTreeMap { + let mut out: BTreeMap = profile_env + .iter() + .map(|(k, v)| (k.trim().to_string(), v.clone())) + .filter(|(k, _)| !k.is_empty()) + .collect(); + if ![LC_ALL, LC_CTYPE, LANG].iter().any(|k| out.contains_key(*k)) { + out.insert(LC_CTYPE.into(), default_utf8_locale(std::env::consts::OS).into()); + } + out +} + +/// A UTF-8 locale broadly available on `os` (Go's `darwin` or Rust's `macos` get `en_US.UTF-8`). +pub fn default_utf8_locale(os: &str) -> &'static str { + if os == "darwin" || os == "macos" { "en_US.UTF-8" } else { "C.UTF-8" } +} + +fn ensure_utf8_character_locale(env: Vec, os: &str) -> Vec { + // The first of LC_ALL, LC_CTYPE, LANG that is set decides the character locale. + let effective = [LC_ALL, LC_CTYPE, LANG].iter().find_map(|k| get_env(&env, k)); + if effective.is_some_and(is_utf8_locale) { + return env; + } + let locale = default_utf8_locale(os); + if get_env(&env, LC_ALL).is_some() { + set_env(env, LC_ALL, locale) + } else if get_env(&env, LC_CTYPE).is_some() || get_env(&env, LANG).is_some() { + set_env(env, LC_CTYPE, locale) + } else { + set_env(env, LANG, locale) + } +} + +fn is_utf8_locale(v: &str) -> bool { + v.replace('-', "").to_lowercase().contains("utf8") +} + +fn get_env<'a>(env: &'a [String], key: &str) -> Option<&'a str> { + env.iter().rev().find_map(|e| e.strip_prefix(key)?.strip_prefix('=')) +} + +fn set_env(env: Vec, key: &str, value: &str) -> Vec { + let mut out: Vec = env.into_iter().filter(|e| get_env(std::slice::from_ref(e), key).is_none()).collect(); + out.push(format!("{key}={value}")); + out +} + +#[cfg(test)] +mod tests { + use super::*; + + fn s(v: &[&str]) -> Vec { + v.iter().map(|x| x.to_string()).collect() + } + + #[test] + fn local_env_sets_term_and_utf8_when_missing() { + let got = local_shell_env(s(&["SHELL=/bin/zsh"])); + assert_eq!(get_env(&got, TERM), Some(DEFAULT_TERM)); + assert!(get_env(&got, LANG).is_some_and(is_utf8_locale)); + } + + #[test] + fn local_env_uses_lc_ctype_when_lang_is_not_utf8() { + let got = ensure_utf8_character_locale(s(&["LANG=C"]), "linux"); + assert_eq!(get_env(&got, LANG), Some("C")); + assert_eq!(get_env(&got, LC_CTYPE), Some("C.UTF-8")); + } + + #[test] + fn local_env_overrides_non_utf8_lc_all() { + let got = ensure_utf8_character_locale(s(&["LC_ALL=C", "LANG=C"]), "darwin"); + assert_eq!(get_env(&got, LC_ALL), Some("en_US.UTF-8")); + } + + #[test] + fn ssh_env_preserves_profile_locale() { + let got = ssh_env_vars(&BTreeMap::from([("LANG".into(), "de_DE.UTF-8".into()), ("FOO".into(), "bar".into())])); + assert_eq!((got["LANG"].as_str(), got["FOO"].as_str()), ("de_DE.UTF-8", "bar")); + assert!(!got.contains_key(LC_CTYPE)); + let got = ssh_env_vars(&BTreeMap::from([("LANG".into(), "C".into())])); + assert_eq!(got["LANG"], "C"); + assert!(!got.contains_key(LC_CTYPE)); + } + + #[test] + fn ssh_env_adds_utf8_lc_ctype_by_default() { + let got = ssh_env_vars(&BTreeMap::from([("FOO".into(), "bar".into())])); + assert_eq!(got["FOO"], "bar"); + assert!(is_utf8_locale(&got[LC_CTYPE])); + } +} diff --git a/rust/core/src/updater.rs b/rust/core/src/updater.rs new file mode 100644 index 0000000..4e64099 --- /dev/null +++ b/rust/core/src/updater.rs @@ -0,0 +1,302 @@ +//! Update checks against a self-hosted manifest (or, as a fallback, GitHub's latest release). +//! Opt-in and check-only: nothing is downloaded or installed. Calls block; run them off the UI +//! thread. + +use crate::Result; +use serde::{Deserialize, Serialize}; +use std::io::Read; +use std::time::Duration; + +/// Manifest URL template; expand with [`expand_update_url`]. +pub const DEFAULT_UPDATE_URL: &str = "https://alchementary.co.uk/download/link/manifest-{platform}-{arch}.json"; +const GITHUB_OWNER: &str = "nexus-terminal"; +const GITHUB_REPO: &str = "nexus-terminal"; +const USER_AGENT: &str = "NexusTerminal-UpdateChecker"; +const REQUEST_TIMEOUT: Duration = Duration::from_secs(10); +const MAX_UPDATE_RESPONSE_BYTES: usize = 1 << 20; + +#[derive(Clone, Debug, PartialEq, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateInfo { + pub available: bool, + pub current_version: String, + pub latest_version: String, + #[serde(rename = "releaseURL")] + pub release_url: String, + pub release_name: String, + pub release_notes: String, + pub published_at: String, +} + +#[derive(Deserialize, Default)] +#[serde(default)] +struct Release { + tag_name: String, + name: String, + html_url: String, + published_at: String, + body: String, + prerelease: bool, + draft: bool, +} + +#[derive(Deserialize, Default)] +#[serde(default, rename_all = "camelCase")] +struct Manifest { + latest_version: String, + version: String, + #[serde(rename = "releaseURL")] + release_url: String, + release_name: String, + release_notes: String, + published_at: String, +} + +/// Fills `{platform}`/`{os}` and `{arch}` with Go's GOOS/GOARCH names, which the published +/// manifest file names use. +pub fn expand_update_url(template: &str) -> String { + let os = match std::env::consts::OS { + "macos" => "darwin", + o => o, + }; + let arch = match std::env::consts::ARCH { + "x86_64" => "amd64", + "aarch64" => "arm64", + "x86" => "386", + a => a, + }; + template.replace("{platform}", os).replace("{os}", os).replace("{arch}", arch) +} + +/// GET with the updater headers; the body is read (bounded) only on 200. +fn fetch(url: &str, accept: &str) -> Result<(u16, Vec)> { + let agent: ureq::Agent = ureq::Agent::config_builder() + .timeout_global(Some(REQUEST_TIMEOUT)) + .http_status_as_error(false) + .build() + .into(); + let resp = agent + .get(url) + .header("Accept", accept) + .header("User-Agent", USER_AGENT) + .call() + .map_err(|e| format!("updater: request failed: {e}"))?; + let status = resp.status().as_u16(); + if status != 200 { + return Ok((status, vec![])); + } + let mut body = Vec::new(); + resp.into_body() + .into_reader() + .take(MAX_UPDATE_RESPONSE_BYTES as u64 + 1) + .read_to_end(&mut body) + .map_err(|e| format!("updater: failed to read response: {e}"))?; + if body.len() > MAX_UPDATE_RESPONSE_BYTES { + return Err(format!("updater: failed to read response: response exceeds {MAX_UPDATE_RESPONSE_BYTES} bytes").into()); + } + Ok((status, body)) +} + +/// Checks a manifest URL. The body is either a JSON manifest (`latestVersion` or `version`, plus +/// optional `releaseURL`, `releaseName`, `releaseNotes`, `publishedAt`) or a bare version string. +pub fn check_url(url: &str, current_version: &str) -> Result { + if url.is_empty() { + return Err("updater: update manifest URL is empty".into()); + } + let current = normalise_version(current_version); + let (status, body) = fetch(url, "application/json, text/plain")?; + if status != 200 { + return Err(format!("updater: update server returned {status}").into()); + } + if let Some(info) = parse_manifest(&body, ¤t) { + return Ok(info); + } + let latest = normalise_version(&String::from_utf8_lossy(&body)); + if latest.is_empty() { + return Err("updater: update manifest is empty".into()); + } + Ok(UpdateInfo { available: is_newer(&latest, ¤t), current_version: current, latest_version: latest, ..Default::default() }) +} + +/// Checks the latest GitHub release of `nexus-terminal/nexus-terminal`. +pub fn check_github(current_version: &str) -> Result { + let current = normalise_version(current_version); + let url = format!("https://api.github.com/repos/{GITHUB_OWNER}/{GITHUB_REPO}/releases/latest"); + let (status, body) = fetch(&url, "application/vnd.github+json")?; + if status == 404 { + // No releases yet. + return Ok(UpdateInfo { latest_version: current.clone(), current_version: current, ..Default::default() }); + } + if status != 200 { + return Err(format!("updater: GitHub API returned {status}").into()); + } + let release: Release = + serde_json::from_slice(&body).map_err(|e| format!("updater: failed to decode response: {e}"))?; + let latest = normalise_version(&release.tag_name); + if release.draft || release.prerelease { + return Ok(UpdateInfo { current_version: current, latest_version: latest, ..Default::default() }); + } + Ok(UpdateInfo { + available: is_newer(&latest, ¤t), + current_version: current, + latest_version: latest, + release_url: release.html_url, + release_name: release.name, + release_notes: truncate_notes(&release.body, 500), + published_at: release.published_at, + }) +} + +fn normalise_version(v: &str) -> String { + let v = v.trim(); + v.strip_prefix('v').unwrap_or(v).to_string() +} + +fn parse_manifest(body: &[u8], current: &str) -> Option { + let m: Manifest = serde_json::from_slice(body).ok()?; + let latest = normalise_version(if m.latest_version.is_empty() { &m.version } else { &m.latest_version }); + if latest.is_empty() { + return None; + } + Some(UpdateInfo { + available: is_newer(&latest, current), + current_version: current.into(), + latest_version: latest, + release_url: m.release_url, + release_name: m.release_name, + release_notes: m.release_notes, + published_at: m.published_at, + }) +} + +/// Compares dot-separated numeric parts (leading integer of each part, else 0). Dev builds and an +/// empty current version never see updates. +fn is_newer(latest: &str, current: &str) -> bool { + if current.is_empty() || current == "dev" { + return false; + } + let l: Vec<&str> = latest.split('.').collect(); + let c: Vec<&str> = current.split('.').collect(); + for i in 0..l.len().max(c.len()) { + let (lv, cv) = (leading_int(l.get(i)), leading_int(c.get(i))); + if lv != cv { + return lv > cv; + } + } + false +} + +/// Go's `fmt.Sscanf(s, "%d")`: optional sign then digits; anything else is 0. +fn leading_int(s: Option<&&str>) -> i64 { + let s = s.map_or("", |s| s.trim_start()); + let end = s + .char_indices() + .find(|&(i, c)| !(c.is_ascii_digit() || (i == 0 && (c == '+' || c == '-')))) + .map_or(s.len(), |(i, _)| i); + s[..end].parse().unwrap_or(0) +} + +/// Cuts at `max` bytes (backing off to a char boundary) and appends "…". +fn truncate_notes(body: &str, max: usize) -> String { + if body.len() <= max { + return body.into(); + } + let mut i = max; + while !body.is_char_boundary(i) { + i -= 1; + } + format!("{}…", &body[..i]) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Write; + + /// Serves one HTTP response on a random local port; returns its URL. + fn serve_once(content_type: &'static str, body: Vec) -> String { + let l = std::net::TcpListener::bind("127.0.0.1:0").unwrap(); + let url = format!("http://{}/", l.local_addr().unwrap()); + std::thread::spawn(move || { + let (mut s, _) = l.accept().unwrap(); + let mut req: Vec = Vec::new(); + let mut buf = [0u8; 1024]; + while !req.windows(4).any(|w| w == b"\r\n\r\n") { + let n = s.read(&mut buf).unwrap(); + if n == 0 { + break; + } + req.extend(&buf[..n]); + } + let head = format!( + "HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", + body.len() + ); + let _ = s.write_all(head.as_bytes()); + let _ = s.write_all(&body); + }); + url + } + + #[test] + fn normalises_versions() { + for (i, w) in [("v1.2.3", "1.2.3"), ("1.2.3", "1.2.3"), (" v0.5.0 ", "0.5.0"), ("dev", "dev")] { + assert_eq!(normalise_version(i), w); + } + } + + #[test] + fn compares_versions() { + for (l, c, w) in [ + ("1.1.0", "1.0.0", true), + ("2.0.0", "1.9.9", true), + ("1.0.1", "1.0.0", true), + ("1.0.0", "1.0.0", false), + ("0.9.0", "1.0.0", false), + ("1.0.0", "dev", false), + ("1.0.0", "", false), + ("1.2", "1.1.0", true), + ("1.0.0", "1.0.0.1", false), + ("1.3.0-beta", "1.2.9", true), + ] { + assert_eq!(is_newer(l, c), w, "{l} vs {c}"); + } + } + + #[test] + fn truncates_notes() { + assert_eq!(truncate_notes("Short note", 100), "Short note"); + let got = truncate_notes(&"x".repeat(600), 500); + assert!(got.ends_with('…')); + assert_eq!(got.chars().count(), 501); + assert_eq!(truncate_notes("aé", 2), "a…"); + } + + #[test] + fn expands_url_template() { + let u = expand_update_url(DEFAULT_UPDATE_URL); + assert!(!u.contains('{') && !u.contains("macos") && !u.contains("x86_64"), "{u}"); + } + + #[test] + fn url_manifest_json() { + let url = serve_once("application/json", br#"{"latestVersion":"1.2.3","releaseURL":"https://example.com/release"}"#.to_vec()); + let info = check_url(&url, "1.2.0").unwrap(); + assert!(info.available); + assert_eq!(info.latest_version, "1.2.3"); + assert_eq!(info.release_url, "https://example.com/release"); + } + + #[test] + fn url_plain_text() { + let url = serve_once("text/plain", b"2.0.0".to_vec()); + assert!(check_url(&url, "1.9.0").unwrap().available); + } + + #[test] + fn url_rejects_oversized_manifest() { + let url = serve_once("text/plain", vec![b'x'; MAX_UPDATE_RESPONSE_BYTES + 1]); + let err = check_url(&url, "1.9.0").unwrap_err(); + assert!(err.to_string().contains("response exceeds"), "{err}"); + } +} diff --git a/rust/core/src/validation.rs b/rust/core/src/validation.rs new file mode 100644 index 0000000..f5866f1 --- /dev/null +++ b/rust/core/src/validation.rs @@ -0,0 +1,573 @@ +//! Field-level validation of profiles and their protocol configs. Enum-typed fields (secret modes, +//! host-key policy, line endings, forward direction, protocol) can't hold invalid values in Rust, +//! so the Go checks for those are enforced by the types instead. + +use crate::models::{GitHubConfig, IrcConfig, Profile, Protocol, RawConfig, SerialConfig, SshConfig, TelnetConfig, VncConfig}; +use serde::Serialize; +use std::fmt; + +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct ValidationError { + pub field: String, + pub message: String, +} + +impl fmt::Display for ValidationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}: {}", self.field, self.message) + } +} +impl std::error::Error for ValidationError {} + +#[derive(Clone, Debug, PartialEq, Default, Serialize)] +pub struct ValidationErrors { + pub errors: Vec, +} + +impl fmt::Display for ValidationErrors { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let msgs: Vec = self.errors.iter().map(|e| e.to_string()).collect(); + write!(f, "validation failed: {}", msgs.join("; ")) + } +} +impl std::error::Error for ValidationErrors {} + +impl ValidationErrors { + pub fn add(&mut self, field: impl Into, message: impl Into) { + self.errors.push(ValidationError { field: field.into(), message: message.into() }); + } + fn into_result(self) -> Result<(), ValidationErrors> { + if self.errors.is_empty() { Ok(()) } else { Err(self) } + } + fn merge(&mut self, r: Result<(), ValidationErrors>) { + if let Err(e) = r { + self.errors.extend(e.errors); + } + } + /// Adds `r`'s message (if any) under `field`. + fn check(&mut self, field: &str, r: Result<(), ValidationError>) { + if let Err(e) = r { + self.add(field, e.message); + } + } +} + +fn err(field: &str, message: &str) -> Result<(), ValidationError> { + Err(ValidationError { field: field.into(), message: message.into() }) +} + +pub fn validate_profile(p: &Profile) -> Result<(), ValidationErrors> { + let mut errs = ValidationErrors::default(); + if p.id.trim().is_empty() { + errs.add("id", "profile ID must not be empty"); + } + if p.display_name.trim().is_empty() { + errs.add("displayName", "display name must not be empty"); + } + + match p.protocol { + Protocol::Ssh => { + host_port(&mut errs, &p.host, p.port); + if p.username.trim().is_empty() { + errs.add("username", "username is required for SSH connections"); + } else { + errs.check("username", validate_username(&p.username)); + } + match &p.ssh { + None => errs.add("ssh", "SSH configuration is required for SSH protocol"), + Some(c) => errs.merge(validate_ssh_config(c)), + } + } + Protocol::Telnet => { + host_port(&mut errs, &p.host, p.port); + match &p.telnet { + None => errs.add("telnet", "Telnet configuration is required for Telnet protocol"), + Some(c) => errs.merge(validate_telnet_config(c)), + } + } + Protocol::Raw => { + host_port(&mut errs, &p.host, p.port); + match &p.raw { + None => errs.add("raw", "RAW configuration is required for RAW protocol"), + Some(c) => errs.merge(validate_raw_config(c)), + } + } + Protocol::Github => match &p.github { + None => errs.add("github", "GitHub configuration is required for GitHub protocol"), + Some(c) => errs.merge(validate_github_config(c)), + }, + Protocol::Vnc => match &p.vnc { + None => errs.add("vnc", "VNC configuration is required for VNC protocol"), + Some(c) => errs.merge(validate_vnc_config(&p.host, c)), + }, + Protocol::Irc => { + host_port(&mut errs, &p.host, p.port); + match &p.irc { + None => errs.add("irc", "IRC configuration is required for IRC protocol"), + Some(c) => errs.merge(validate_irc_config(c)), + } + } + Protocol::Serial => { + if p.host.trim().is_empty() { + errs.add("host", "serial device path must not be empty"); + } + match &p.serial { + None => errs.add("serial", "Serial configuration is required for Serial protocol"), + Some(c) => errs.merge(validate_serial_config(c)), + } + } + Protocol::Local => errs.add("protocol", format!("unsupported protocol {:?}", p.protocol.id())), + } + + if p.connect_timeout < 0 { + errs.add("connectTimeout", "connect timeout must not be negative"); + } + if p.read_timeout < 0 { + errs.add("readTimeout", "read timeout must not be negative"); + } + if !(1..=72).contains(&p.terminal_font_size) { + errs.add("terminalFontSize", "font size must be between 1 and 72"); + } + if p.terminal_scrollback < 0 { + errs.add("terminalScrollback", "scrollback must not be negative"); + } + errs.into_result() +} + +fn host_port(errs: &mut ValidationErrors, host: &str, port: i64) { + errs.check("host", validate_host(host)); + errs.check("port", validate_port(port)); +} + +/// Non-empty, no whitespace, no URI scheme, no leading hyphen. +pub fn validate_host(host: &str) -> Result<(), ValidationError> { + let h = host.trim(); + if h.is_empty() { + return err("host", "host must not be empty"); + } + if h.contains([' ', '\t', '\n', '\r']) { + return err("host", "host must not contain whitespace"); + } + if h.contains("://") { + return err("host", "host must not include a URI scheme"); + } + // An IP address never starts with '-', so this only rejects hostnames (as in Go). + if h.starts_with('-') { + return err("host", "host must not begin with a hyphen"); + } + Ok(()) +} + +pub fn validate_port(port: i64) -> Result<(), ValidationError> { + if !(1..=65535).contains(&port) { + return err("port", "port must be between 1 and 65535"); + } + Ok(()) +} + +/// Non-empty and free of control characters. +pub fn validate_username(username: &str) -> Result<(), ValidationError> { + let u = username.trim(); + if u.is_empty() { + return err("username", "username must not be empty"); + } + if u.chars().any(is_control) { + return err("username", "username must not contain control characters"); + } + Ok(()) +} + +fn is_control(c: char) -> bool { + c < '\x20' || c == '\x7f' +} + +pub fn validate_ssh_config(cfg: &SshConfig) -> Result<(), ValidationErrors> { + let mut errs = ValidationErrors::default(); + if cfg.auth_methods.is_empty() { + errs.add("ssh.authMethods", "at least one authentication method is required"); + } + for m in &cfg.auth_methods { + if !matches!(m.as_str(), "password" | "publickey" | "keyboard-interactive") { + errs.add("ssh.authMethods", format!("unsupported authentication method {m:?}")); + } + } + if cfg.keepalive_interval < 0 { + errs.add("ssh.keepaliveInterval", "keepalive interval must not be negative"); + } + if cfg.keepalive_count_max < 0 { + errs.add("ssh.keepaliveCountMax", "keepalive count max must not be negative"); + } + for (i, jh) in cfg.jump_hosts.iter().enumerate() { + let prefix = format!("ssh.jumpHosts[{i}]"); + if jh.host.trim().is_empty() { + errs.add(format!("{prefix}.host"), "jump host address must not be empty"); + } + if !(1..=65535).contains(&jh.port) { + errs.add(format!("{prefix}.port"), "jump host port must be between 1 and 65535"); + } + if jh.username.trim().is_empty() { + errs.add(format!("{prefix}.username"), "jump host username must not be empty"); + } + } + for (i, pf) in cfg.port_forwards.iter().enumerate() { + let prefix = format!("ssh.portForwards[{i}]"); + if !(1..=65535).contains(&pf.bind_port) { + errs.add(format!("{prefix}.bindPort"), "bind port must be between 1 and 65535"); + } + if !(1..=65535).contains(&pf.dest_port) { + errs.add(format!("{prefix}.destPort"), "destination port must be between 1 and 65535"); + } + if pf.dest_host.trim().is_empty() { + errs.add(format!("{prefix}.destHost"), "destination host must not be empty"); + } + } + errs.into_result() +} + +pub fn validate_telnet_config(cfg: &TelnetConfig) -> Result<(), ValidationErrors> { + let mut errs = ValidationErrors::default(); + if cfg.encoding.trim().is_empty() { + errs.add("telnet.encoding", "encoding must not be empty"); + } + errs.into_result() +} + +pub fn validate_raw_config(cfg: &RawConfig) -> Result<(), ValidationErrors> { + let mut errs = ValidationErrors::default(); + if cfg.encoding.trim().is_empty() { + errs.add("raw.encoding", "encoding must not be empty"); + } + for (i, preset) in cfg.payload_presets.iter().enumerate() { + let prefix = format!("raw.payloadPresets[{i}]"); + if preset.name.trim().is_empty() { + errs.add(format!("{prefix}.name"), "payload preset name must not be empty"); + } + if !matches!(preset.format.as_str(), "text" | "hex" | "escaped") { + errs.add(format!("{prefix}.format"), format!("unsupported payload format {:?}", preset.format)); + } + } + errs.into_result() +} + +/// The API base URL must be absolute HTTPS (HTTP only to loopback) without credentials. +pub fn validate_github_config(cfg: &GitHubConfig) -> Result<(), ValidationErrors> { + let mut errs = ValidationErrors::default(); + match split_url(cfg.base_url.trim()) { + None => errs.add("github.baseURL", "API base URL must be an absolute HTTPS URL"), + Some((scheme, has_user, hostname)) => { + if has_user { + errs.add("github.baseURL", "API base URL must not include credentials"); + } + if scheme != "https" && !(scheme == "http" && is_loopback_host(hostname)) { + errs.add("github.baseURL", "API base URL must use HTTPS unless it points to loopback"); + } + } + } + if cfg.refresh_interval_secs != 0 && cfg.refresh_interval_secs < 60 { + errs.add("github.refreshIntervalSecs", "refresh interval must be at least 60 seconds"); + } + errs.into_result() +} + +/// (lowercased scheme, has userinfo, hostname without port/brackets) of an absolute URL. +fn split_url(s: &str) -> Option<(String, bool, &str)> { + let (scheme, rest) = s.split_once("://")?; + if scheme.is_empty() { + return None; + } + let authority = &rest[..rest.find(['/', '?', '#']).unwrap_or(rest.len())]; + let (has_user, host) = match authority.rsplit_once('@') { + Some((_, h)) => (true, h), + None => (false, authority), + }; + if host.is_empty() { + return None; + } + let hostname = match host.strip_prefix('[') { + Some(h) => h.split(']').next().unwrap_or(h), + None => host.rsplit_once(':').map_or(host, |(h, _)| h), + }; + Some((scheme.to_ascii_lowercase(), has_user, hostname)) +} + +fn is_loopback_host(host: &str) -> bool { + let h = host.trim().to_lowercase(); + h == "localhost" || h.parse::().is_ok_and(|ip| ip.to_canonical().is_loopback()) +} + +/// The VNC host falls back to the profile host. +pub fn validate_vnc_config(profile_host: &str, cfg: &VncConfig) -> Result<(), ValidationErrors> { + let mut errs = ValidationErrors::default(); + let host = if cfg.vnc_host.trim().is_empty() { profile_host } else { &cfg.vnc_host }; + errs.check("vnc.vncHost", validate_host(host)); + errs.check("vnc.vncPort", validate_port(cfg.vnc_port)); + if !(0..=9).contains(&cfg.quality) { + errs.add("vnc.quality", "quality must be between 0 and 9"); + } + if cfg.ssh_tunnel { + ssh_tunnel(&mut errs, "vnc", &cfg.ssh_host, cfg.ssh_port, &cfg.ssh_username, &cfg.ssh_auth_method); + } + errs.into_result() +} + +fn ssh_tunnel(errs: &mut ValidationErrors, p: &str, host: &str, port: i64, user: &str, auth: &str) { + errs.check(&format!("{p}.sshHost"), validate_host(host)); + errs.check(&format!("{p}.sshPort"), validate_port(port)); + if user.trim().is_empty() { + errs.add(format!("{p}.sshUsername"), "SSH username is required when tunnelling"); + } else { + errs.check(&format!("{p}.sshUsername"), validate_username(user)); + } + if auth != "password" && auth != "publickey" { + errs.add(format!("{p}.sshAuthMethod"), format!("unsupported SSH auth method {auth:?}")); + } +} + +pub fn validate_irc_config(cfg: &IrcConfig) -> Result<(), ValidationErrors> { + let mut errs = ValidationErrors::default(); + let nick = cfg.nick.trim(); + if nick.is_empty() { + errs.add("irc.nick", "nickname is required"); + } else if !is_valid_nick(nick) { + errs.add("irc.nick", "nickname is not valid for IRC"); + } + if cfg.real_name.contains(['\r', '\n', '\0']) { + errs.add("irc.realName", "real name must not contain control characters"); + } + if cfg.nick_serv_identify && cfg.nick_serv_account.chars().any(is_control) { + errs.add("irc.nickServAccount", "NickServ account must not contain control characters"); + } + let mut seen = std::collections::HashSet::new(); + for (i, room) in cfg.auto_join_rooms.iter().enumerate() { + let field = format!("irc.autoJoinRooms[{i}]"); + let name = room.trim(); + if name.is_empty() { + errs.add(field, "room name must not be empty"); + continue; + } + if !is_valid_channel(name) { + errs.add(field.clone(), "room name must start with a valid IRC channel prefix"); + } + if !seen.insert(name.to_lowercase()) { + errs.add(field, "room is listed more than once"); + } + } + if cfg.ssh_tunnel { + ssh_tunnel(&mut errs, "irc", &cfg.ssh_host, cfg.ssh_port, &cfg.ssh_username, &cfg.ssh_auth_method); + } + if cfg.max_reconnect_tries < 0 { + errs.add("irc.maxReconnectTries", "max reconnect attempts must not be negative"); + } + errs.into_result() +} + +/// Device path lives in `Profile::host` and is checked by `validate_profile`. +pub fn validate_serial_config(cfg: &SerialConfig) -> Result<(), ValidationErrors> { + let mut errs = ValidationErrors::default(); + if cfg.baud_rate == 0 { + errs.add("serial.baudRate", "baud rate must be greater than 0"); + } + if !(5..=8).contains(&cfg.data_bits) { + errs.add("serial.dataBits", "data bits must be between 5 and 8"); + } + if cfg.stop_bits != 1 && cfg.stop_bits != 2 { + errs.add("serial.stopBits", "stop bits must be 1 or 2"); + } + errs.into_result() +} + +/// girc's IsValidNick: first byte in 'A'..='}' or '?', the rest also digits or '-'. +fn is_valid_nick(nick: &str) -> bool { + let b = nick.as_bytes(); + let Some((&first, rest)) = b.split_first() else { return false }; + ((b'A'..=b'}').contains(&first) || first == b'?') + && rest.iter().all(|c| (b'A'..=b'}').contains(c) || c.is_ascii_digit() || *c == b'-') +} + +/// girc's IsValidChannel. +fn is_valid_channel(ch: &str) -> bool { + let b = ch.as_bytes(); + if b.len() <= 1 || b.len() > 50 || !b"!#&*~+".contains(&b[0]) { + return false; + } + // !<5-char ID> + if b[0] == b'!' && (b.len() < 7 || !b[1..6].iter().all(|c| c.is_ascii_digit() || c.is_ascii_uppercase())) { + return false; + } + !b[1..].iter().any(|c| [0x00, 0x07, 0x0d, 0x0a, 0x20, 0x2c, 0x3a].contains(c)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::models::{JumpHost, PayloadPreset}; + + #[test] + fn hosts() { + for (h, ok) in [ + ("example.com", true), + ("192.168.1.1", true), + ("::1", true), + ("", false), + (" ", false), + ("host name", false), + ("host\tname", false), + ("ssh://example.com", false), + ("-bad-host", false), + ("server.internal.corp", true), + ] { + assert_eq!(validate_host(h).is_ok(), ok, "{h:?}"); + } + } + + #[test] + fn ports() { + for (p, ok) in [(1, true), (22, true), (443, true), (65535, true), (0, false), (-1, false), (65536, false), (99999, false)] { + assert_eq!(validate_port(p).is_ok(), ok, "{p}"); + } + } + + #[test] + fn usernames() { + for (u, ok) in [ + ("admin", true), + ("", false), + (" ", false), + ("my user", true), + ("abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789", true), + ("bad\x01user", false), + ("bad\x7fuser", false), + ] { + assert_eq!(validate_username(u).is_ok(), ok, "{u:?}"); + } + } + + fn ssh() -> Profile { + let mut p = Profile::new(Protocol::Ssh); + p.display_name = "Test SSH".into(); + p.host = "example.com".into(); + p.username = "root".into(); + p + } + + #[test] + fn profiles() { + let mut telnet = Profile::new(Protocol::Telnet); + telnet.display_name = "Test Telnet".into(); + telnet.host = "switch.local".into(); + let mut raw = Profile::new(Protocol::Raw); + raw.display_name = "Test RAW".into(); + raw.host = "device.local".into(); + let mut vnc = Profile::new(Protocol::Vnc); + vnc.display_name = "Test VNC".into(); + vnc.vnc.as_mut().unwrap().vnc_host = "desktop.local".into(); + for p in [ssh(), telnet, raw, vnc] { + assert!(validate_profile(&p).is_ok(), "{:?}", validate_profile(&p)); + } + + let bad: [fn(&mut Profile); 7] = [ + |p| p.host.clear(), + |p| p.port = 0, + |p| p.protocol = Protocol::Local, + |p| p.username.clear(), + |p| p.ssh = None, + |p| p.connect_timeout = -1, + |p| p.terminal_font_size = 0, + ]; + for (i, f) in bad.iter().enumerate() { + let mut p = ssh(); + f(&mut p); + assert!(validate_profile(&p).is_err(), "case {i}"); + } + } + + #[test] + fn serial_profiles() { + let mut p = Profile::new(Protocol::Serial); + p.display_name = "Console".into(); + p.host = "/dev/ttyUSB0".into(); + assert!(validate_profile(&p).is_ok()); + let bad: [fn(&mut Profile); 6] = [ + |p| p.host = " ".into(), + |p| p.serial = None, + |p| p.serial.as_mut().unwrap().baud_rate = 0, + |p| p.serial.as_mut().unwrap().data_bits = 4, + |p| p.serial.as_mut().unwrap().data_bits = 9, + |p| p.serial.as_mut().unwrap().stop_bits = 3, + ]; + for (i, f) in bad.iter().enumerate() { + let mut q = p.clone(); + f(&mut q); + assert!(validate_profile(&q).is_err(), "case {i}"); + } + } + + #[test] + fn github() { + let cfg = |url: &str, secs| GitHubConfig { base_url: url.into(), refresh_interval_secs: secs, ..Default::default() }; + for (c, ok) in [ + (cfg("https://api.github.com", 300), true), + (cfg("http://localhost:3000/api/v3", 60), true), + (cfg("http://[::1]:3000/api/v3", 60), true), + (cfg("http://127.0.0.1/api/v3", 0), true), + (cfg("http://evil.example/api/v3", 300), false), + (cfg("https://token@example.com/api/v3", 300), false), + (cfg("https://api.github.com", 1), false), + (cfg("api.github.com", 300), false), + ] { + assert_eq!(validate_github_config(&c).is_ok(), ok, "{c:?}"); + } + } + + #[test] + fn vnc() { + let base = VncConfig { vnc_port: 5900, quality: 6, ..Default::default() }; + let named = VncConfig { vnc_host: "desktop.local".into(), ..base.clone() }; + assert!(validate_vnc_config("", &named).is_ok()); + assert!(validate_vnc_config("desktop.local", &base).is_ok()); + assert!(validate_vnc_config("", &base).is_err()); + let tunnel = VncConfig { + ssh_tunnel: true, + ssh_host: "jump.local".into(), + ssh_port: 22, + ssh_username: "admin".into(), + ssh_auth_method: "token".into(), + ..base + }; + assert!(validate_vnc_config("desktop.local", &tunnel).is_err()); + } + + #[test] + fn ssh_configs() { + let valid = || SshConfig { auth_methods: vec!["password".into()], keepalive_interval: 30, keepalive_count_max: 3, ..Default::default() }; + assert!(validate_ssh_config(&valid()).is_ok()); + assert!(validate_ssh_config(&SshConfig { auth_methods: vec![], ..valid() }).is_err()); + assert!(validate_ssh_config(&SshConfig { auth_methods: vec!["oauth".into()], ..valid() }).is_err()); + assert!(validate_ssh_config(&SshConfig { jump_hosts: vec![JumpHost::default()], ..valid() }).is_err()); + } + + #[test] + fn telnet_and_raw() { + assert!(validate_telnet_config(&TelnetConfig { encoding: "utf-8".into(), ..Default::default() }).is_ok()); + assert!(validate_telnet_config(&TelnetConfig::default()).is_err()); + let raw = |presets| RawConfig { encoding: "utf-8".into(), payload_presets: presets, ..Default::default() }; + let preset = |n: &str, f: &str| PayloadPreset { name: n.into(), format: f.into(), ..Default::default() }; + assert!(validate_raw_config(&raw(vec![])).is_ok()); + assert!(validate_raw_config(&RawConfig::default()).is_err()); + assert!(validate_raw_config(&raw(vec![preset("ping", "binary")])).is_err()); + assert!(validate_raw_config(&raw(vec![preset("", "text")])).is_err()); + } + + #[test] + fn irc() { + let ok = IrcConfig { nick: "nexus".into(), auto_join_rooms: vec!["#rust".into(), "&local".into()], ..Default::default() }; + assert!(validate_irc_config(&ok).is_ok()); + for nick in ["", "1abc", "bad nick"] { + assert!(validate_irc_config(&IrcConfig { nick: nick.into(), ..ok.clone() }).is_err(), "{nick:?}"); + } + for rooms in [vec!["rust"], vec!["#"], vec!["#a,b"], vec!["#rust", "#RUST"], vec![" "]] { + let c = IrcConfig { auto_join_rooms: rooms.iter().map(|r| r.to_string()).collect(), ..ok.clone() }; + assert!(validate_irc_config(&c).is_err(), "{rooms:?}"); + } + } +} From 25e5f67a4de4360b6239022dd3e2352f23718874 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:17:44 +0100 Subject: [PATCH 08/45] rust: commit DB and vault fixtures (were gitignored) --- .gitignore | 3 +++ rust/core/tests/fixtures/link.db | Bin 0 -> 65536 bytes rust/core/tests/fixtures/vault/vault.json | 8 ++++++++ 3 files changed, 11 insertions(+) create mode 100644 rust/core/tests/fixtures/link.db create mode 100644 rust/core/tests/fixtures/vault/vault.json diff --git a/.gitignore b/.gitignore index 39f702f..2ead8f3 100644 --- a/.gitignore +++ b/.gitignore @@ -72,3 +72,6 @@ vault.json # Builds build/releases/v* build/releases/manifest* + +# Rust port compatibility fixtures (generated by cmd/genfixtures) +!rust/core/tests/fixtures/** diff --git a/rust/core/tests/fixtures/link.db b/rust/core/tests/fixtures/link.db new file mode 100644 index 0000000000000000000000000000000000000000..ecc833df73e3ad080eb14bfb539665bcefbf9359 GIT binary patch literal 65536 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 + +/// TCP dial shared by the Telnet and RAW clients: Go's `net.DialTimeout` with a 30 s default. +/// Returns the stream and the `host:port` string used in error messages. +pub(crate) fn dial(host: &str, port: i64, timeout_secs: i64) -> (String, std::io::Result) { + use std::net::{TcpStream, ToSocketAddrs}; + let timeout = std::time::Duration::from_secs(if timeout_secs > 0 { timeout_secs as u64 } else { 30 }); + // Go's net.JoinHostPort brackets IPv6 literals. + let addr = if host.contains(':') { format!("[{host}]:{port}") } else { format!("{host}:{port}") }; + let result = (|| { + let mut last = std::io::Error::new(std::io::ErrorKind::NotFound, "no addresses found"); + for sa in addr.to_socket_addrs()? { + match TcpStream::connect_timeout(&sa, timeout) { + Ok(s) => return Ok(s), + Err(e) => last = e, + } + } + Err(last) + })(); + (addr, result) +} + +/// The TCP connection lifecycle shared by the Telnet and RAW clients: one writer-side stream behind +/// a lock, a reader thread that feeds `on_read`, and Go's error/close semantics (a read error or EOF +/// reports `read error: …` then closes; `close` is idempotent and fires `on_close` once). +pub(crate) struct TcpConn { + stream: std::sync::Mutex>, + on_error: ErrorFn, + on_close: CloseFn, +} + +impl TcpConn { + pub(crate) fn new(on_error: ErrorFn, on_close: CloseFn) -> TcpConn { + TcpConn { stream: std::sync::Mutex::new(None), on_error, on_close } + } + + pub(crate) fn connect( + self: &Arc, + stream: std::net::TcpStream, + mut on_read: impl FnMut(&[u8]) + Send + 'static, + ) -> crate::Result<()> { + let mut reader = stream.try_clone()?; + { + let mut guard = self.stream.lock().unwrap(); + if guard.is_some() { + return Err("already connected".into()); + } + *guard = Some(stream); + } + let me = self.clone(); + std::thread::spawn(move || { + use std::io::Read; + let mut buf = [0u8; 4096]; + loop { + let err = match reader.read(&mut buf) { + Ok(0) => "EOF".to_string(), + Ok(n) => { + on_read(&buf[..n]); + continue; + } + Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue, + Err(e) => e.to_string(), + }; + // A local close also ends the read; Go reported that as an error too, we don't. + if me.is_connected() { + (me.on_error)(format!("read error: {err}")); + me.close(); + } + return; + } + }); + Ok(()) + } + + pub(crate) fn write(&self, data: &[u8]) -> crate::Result<()> { + use std::io::Write; + let guard = self.stream.lock().unwrap(); + let mut s = guard.as_ref().ok_or("not connected")?; + s.write_all(data)?; + Ok(()) + } + + pub(crate) fn is_connected(&self) -> bool { + self.stream.lock().unwrap().is_some() + } + + pub(crate) fn close(&self) { + let taken = self.stream.lock().unwrap().take(); + if let Some(s) = taken { + let _ = s.shutdown(std::net::Shutdown::Both); + (self.on_close)(); + } + } +} + +/// True for the encoding names Go mapped to ISO 8859-1. +pub(crate) fn is_latin1(encoding: &str) -> bool { + matches!(encoding.trim().to_ascii_lowercase().as_str(), "iso-8859-1" | "latin1" | "latin-1") +} + +/// UTF-8 → ISO 8859-1. Like Go's charmap encoder, input that isn't valid UTF-8 or holds a +/// character above U+00FF is returned unchanged. +pub(crate) fn utf8_to_latin1(data: &[u8]) -> Vec { + std::str::from_utf8(data) + .ok() + .and_then(|s| s.chars().map(|c| u8::try_from(c).ok()).collect()) + .unwrap_or_else(|| data.to_vec()) +} + +/// ISO 8859-1 → UTF-8 (every byte maps to the code point of the same value). +pub(crate) fn latin1_to_utf8(data: &[u8]) -> String { + data.iter().map(|&b| b as char).collect() +} diff --git a/rust/core/src/clients/raw.rs b/rust/core/src/clients/raw.rs new file mode 100644 index 0000000..a003afa --- /dev/null +++ b/rust/core/src/clients/raw.rs @@ -0,0 +1,431 @@ +//! RAW TCP client (port of Go `internal/rawclient`): binary-safe send/receive, a capped history +//! of sent ("send") and received ("recv") frames, JSON capture, and the hex/escape encoders the +//! RAW view reuses. + +use std::collections::VecDeque; +use std::sync::{Arc, Mutex}; + +use base64::Engine; +use serde::Serialize; + +use super::{CloseFn, ErrorFn, TcpConn}; +use crate::models::{LineEnding, Profile, Time}; + +pub const MAX_HISTORY_ENTRIES: usize = 10_000; +pub const MAX_HISTORY_BYTES: usize = 10 * 1024 * 1024; + +pub const SEND: &str = "send"; +pub const RECV: &str = "recv"; + +/// `(data, direction, timestamp in unix ms)`; direction is [`SEND`] or [`RECV`]. +pub type RawDataFn = Arc; + +/// One sent or received frame. Serialises like Go's (RFC 3339 timestamp, base64 data). +#[derive(Clone, Debug, PartialEq, Serialize)] +pub struct HistoryEntry { + pub timestamp: Time, + pub direction: String, + #[serde(serialize_with = "ser_b64")] + pub data: Vec, +} + +fn ser_b64(d: &[u8], s: S) -> Result { + s.serialize_str(&base64::engine::general_purpose::STANDARD.encode(d)) +} + +pub struct RawClient { + inner: Arc, +} + +struct Inner { + conn: Arc, + host: String, + port: i64, + connect_timeout: i64, + line_ending: LineEnding, + on_data: RawDataFn, + history: Mutex, +} + +#[derive(Default)] +struct History { + entries: VecDeque, + bytes: usize, +} + +impl RawClient { + pub fn new(profile: &Profile, on_data: RawDataFn, on_error: ErrorFn, on_close: CloseFn) -> RawClient { + RawClient { + inner: Arc::new(Inner { + conn: Arc::new(TcpConn::new(on_error, on_close)), + host: profile.host.clone(), + port: profile.port, + connect_timeout: profile.connect_timeout, + line_ending: profile.raw.as_ref().map(|r| r.line_ending).unwrap_or_default(), + on_data, + history: Mutex::default(), + }), + } + } + + pub fn connect(&self) -> crate::Result<()> { + let inner = &self.inner; + if inner.conn.is_connected() { + return Err("already connected".into()); + } + if inner.port == 0 { + return Err("port must be specified for raw TCP connections".into()); + } + let (addr, res) = super::dial(&inner.host, inner.port, inner.connect_timeout); + let stream = res.map_err(|e| format!("raw TCP connection to {addr} failed: {e}"))?; + let me = inner.clone(); + inner.conn.connect(stream, move |d| me.record(RECV, d)) + } + + /// Sends bytes as-is, then records them and reports them through `on_data` as "send". + pub fn write(&self, data: &[u8]) -> crate::Result<()> { + self.inner.conn.write(data)?; + self.inner.record(SEND, data); + Ok(()) + } + + /// Sends text with the configured line ending appended (unless it already ends with it). + pub fn write_text(&self, text: &str) -> crate::Result<()> { + self.write(&apply_line_ending(text, self.inner.line_ending)) + } + + /// Sends hex such as "48656c6c6f", "48 65 6c" or "0x48 0x65". + pub fn write_hex(&self, hex: &str) -> crate::Result<()> { + let data = parse_hex_string(hex).map_err(|e| format!("invalid hex input: {e}"))?; + self.write(&data) + } + + /// Sends text after processing C-style escapes (see [`parse_escaped`]). + pub fn write_escaped(&self, text: &str) -> crate::Result<()> { + let data = parse_escaped(text).map_err(|e| format!("escape processing failed: {e}"))?; + self.write(&data) + } + + pub fn close(&self) { + self.inner.conn.close(); + } + + pub fn is_connected(&self) -> bool { + self.inner.conn.is_connected() + } + + pub fn history(&self) -> Vec { + self.inner.history.lock().unwrap().entries.iter().cloned().collect() + } + + pub fn clear_history(&self) { + *self.inner.history.lock().unwrap() = History::default(); + } + + /// Writes the history to `path` as indented JSON (mode 0600 on Unix), as Go did. + pub fn save_capture(&self, path: &std::path::Path) -> crate::Result<()> { + let json = serde_json::to_vec_pretty(&self.history()).map_err(|e| format!("failed to marshal capture data: {e}"))?; + let mut opts = std::fs::OpenOptions::new(); + opts.write(true).create(true).truncate(true); + #[cfg(unix)] + std::os::unix::fs::OpenOptionsExt::mode(&mut opts, 0o600); + std::io::Write::write_all(&mut opts.open(path)?, &json)?; + Ok(()) + } +} + +impl Inner { + fn record(&self, direction: &str, data: &[u8]) { + let timestamp = crate::models::now(); + let ms = timestamp.timestamp_millis(); + self.append_history(HistoryEntry { timestamp, direction: direction.into(), data: data.to_vec() }); + (self.on_data)(data, direction, ms); + } + + fn append_history(&self, mut entry: HistoryEntry) { + if entry.data.len() > MAX_HISTORY_BYTES { + entry.data.drain(..entry.data.len() - MAX_HISTORY_BYTES); + } + let mut h = self.history.lock().unwrap(); + h.bytes += entry.data.len(); + h.entries.push_back(entry); + while h.entries.len() > MAX_HISTORY_ENTRIES || h.bytes > MAX_HISTORY_BYTES { + let Some(old) = h.entries.pop_front() else { break }; + h.bytes -= old.data.len(); + } + } +} + +fn apply_line_ending(text: &str, le: LineEnding) -> Vec { + let suffix = le.bytes(); + let mut out = text.as_bytes().to_vec(); + if !suffix.is_empty() && !out.ends_with(suffix) { + out.extend_from_slice(suffix); + } + out +} + +/// Parses hex with optional whitespace and 0x/0X prefixes into bytes. +pub fn parse_hex_string(s: &str) -> crate::Result> { + let s = s.trim(); + if s.is_empty() { + return Err("empty hex string".into()); + } + let s = s.replace("0x", "").replace("0X", ""); + let mut digits = Vec::new(); + for c in s.chars().filter(|c| !c.is_whitespace()) { + let d = c.to_digit(16).ok_or_else(|| format!("invalid character in hex string: {c:?}"))?; + digits.push(d as u8); + } + if digits.len() % 2 != 0 { + return Err("hex string has odd number of digits".into()); + } + Ok(digits.chunks(2).map(|p| (p[0] << 4) | p[1]).collect()) +} + +/// Space-separated lowercase hex, e.g. "48 65 6c 6c 6f". +pub fn format_hex(data: &[u8]) -> String { + data.iter().map(|b| format!("{b:02x}")).collect::>().join(" ") +} + +/// Classic 16-bytes-per-line hex dump with offset, hex and ASCII columns. +pub fn format_hex_dump(data: &[u8]) -> String { + use std::fmt::Write; + let mut sb = String::new(); + for (line, chunk) in data.chunks(16).enumerate() { + let _ = write!(sb, "{:08x} ", line * 16); + for i in 0..16 { + if i == 8 { + sb.push(' '); + } + match chunk.get(i) { + Some(b) => { + let _ = write!(sb, "{b:02x} "); + } + None => sb.push_str(" "), + } + } + sb.push_str(" |"); + sb.extend(chunk.iter().map(|&b| if (0x20..=0x7e).contains(&b) { b as char } else { '.' })); + sb.extend(std::iter::repeat_n(' ', 16 - chunk.len())); + sb.push_str("|\n"); + } + sb +} + +/// Processes C-style escapes: `\r \n \t \\ \0 \xNN` (and `\XNN`); anything else is an error. +pub fn parse_escaped(s: &str) -> crate::Result> { + let b = s.as_bytes(); + let mut out = Vec::with_capacity(b.len()); + let mut i = 0; + while i < b.len() { + if b[i] != b'\\' { + out.push(b[i]); + i += 1; + continue; + } + let Some(&next) = b.get(i + 1) else { + return Err(format!("trailing backslash at position {i}").into()); + }; + let simple = match next { + b'r' => Some(b'\r'), + b'n' => Some(b'\n'), + b't' => Some(b'\t'), + b'\\' => Some(b'\\'), + b'0' => Some(0), + _ => None, + }; + if let Some(v) = simple { + out.push(v); + i += 2; + } else if next == b'x' || next == b'X' { + if i + 3 >= b.len() { + return Err(format!("incomplete \\x escape at position {i}").into()); + } + let hex = &b[i + 2..i + 4]; + let v = std::str::from_utf8(hex) + .ok() + .filter(|h| h.bytes().all(|c| c.is_ascii_hexdigit())) + .and_then(|h| u8::from_str_radix(h, 16).ok()) + .ok_or_else(|| format!("invalid \\x escape at position {i}: {}", String::from_utf8_lossy(hex)))?; + out.push(v); + i += 4; + } else { + let c = s[i + 1..].chars().next().unwrap_or('?'); + return Err(format!("unknown escape sequence \\{c} at position {i}").into()); + } + } + Ok(out) +} + +/// Bytes → display text: "ascii" (non-printables except \n \r \t become '.'), "iso-8859-1" / +/// "latin1" / "latin-1", otherwise UTF-8 with invalid bytes replaced by U+FFFD. +pub fn to_display_encoding(data: &[u8], encoding: &str) -> String { + match encoding.trim().to_ascii_lowercase().as_str() { + "ascii" => data + .iter() + .map(|&b| if (0x20..=0x7e).contains(&b) || matches!(b, b'\n' | b'\r' | b'\t') { b as char } else { '.' }) + .collect(), + e if super::is_latin1(e) => super::latin1_to_utf8(data), + _ => String::from_utf8_lossy(data).into_owned(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Read, Write}; + use std::net::TcpListener; + use std::sync::mpsc; + use std::time::Duration; + + fn client() -> RawClient { + RawClient::new(&Profile::new(crate::models::Protocol::Raw), Arc::new(|_, _, _| {}), Arc::new(|_| {}), Arc::new(|| {})) + } + + #[test] + fn parse_hex_string_cases() { + let ok: [(&str, &[u8]); 5] = [ + ("48656c6c6f", b"Hello"), + ("48 65 6c 6c 6f", b"Hello"), + ("0x48 0x65 0x6c 0x6c 0x6f", b"Hello"), + ("0000", &[0, 0]), + ("ff ff", &[0xff, 0xff]), + ]; + for (input, want) in ok { + assert_eq!(parse_hex_string(input).unwrap(), want, "{input:?}"); + } + for input in ["", "ZZZZ", "abc"] { + assert!(parse_hex_string(input).is_err(), "{input:?}"); + } + } + + #[test] + fn parse_escaped_cases() { + let ok: [(&str, &[u8]); 9] = [ + ("hello", b"hello"), + (r"\n", b"\n"), + (r"\r\n", b"\r\n"), + (r"\t", b"\t"), + (r"\0", &[0]), + (r"\xff", &[0xff]), + (r"\\", b"\\"), + (r"AT\r\n", b"AT\r\n"), + (r"\x00", &[0]), + ]; + for (input, want) in ok { + assert_eq!(parse_escaped(input).unwrap(), want, "{input:?}"); + } + for input in [r"hello\", r"\q", r"\x0", r"\x+f"] { + assert!(parse_escaped(input).is_err(), "{input:?}"); + } + } + + #[test] + fn format_hex_dump_cases() { + assert_eq!(format_hex_dump(&[]), ""); + let got = format_hex_dump(b"Hi"); + assert!(got.contains("48 69") && got.contains("|Hi"), "{got}"); + let got = format_hex_dump(b"ABCDEFGHIJKLMNOPQRST"); + let lines: Vec<_> = got.trim_end_matches('\n').split('\n').collect(); + assert_eq!(lines.len(), 2); + assert!(lines[0].starts_with("00000000") && lines[1].starts_with("00000010")); + assert_eq!(lines[0], "00000000 41 42 43 44 45 46 47 48 49 4a 4b 4c 4d 4e 4f 50 |ABCDEFGHIJKLMNOP|"); + assert_eq!(lines[1].len(), lines[0].len()); + } + + #[test] + fn format_hex_cases() { + assert_eq!(format_hex(&[]), ""); + assert_eq!(format_hex(&[0x42]), "42"); + assert_eq!(format_hex(b"Hello"), "48 65 6c 6c 6f"); + } + + #[test] + fn to_display_encoding_cases() { + assert_eq!(to_display_encoding(b"Hello, World!", "utf-8"), "Hello, World!"); + assert_eq!(to_display_encoding(&[0x80, 0x41], "ascii"), ".A"); + assert_eq!(to_display_encoding(&[0xe9], "latin-1"), "é"); + assert_eq!(to_display_encoding(&[0xe9], "iso-8859-1"), "é"); + } + + #[test] + fn history_is_capped_by_entry_count() { + let c = client(); + for i in 0..MAX_HISTORY_ENTRIES + 25 { + c.inner.record(RECV, &[i as u8]); + } + assert_eq!(c.history().len(), MAX_HISTORY_ENTRIES); + } + + #[test] + fn history_is_capped_by_bytes() { + let c = client(); + let chunk = vec![b'a'; 1024]; + for _ in 0..MAX_HISTORY_BYTES / chunk.len() + 10 { + c.inner.record(RECV, &chunk); + } + let total: usize = c.history().iter().map(|e| e.data.len()).sum(); + assert!(total <= MAX_HISTORY_BYTES); + c.clear_history(); + assert!(c.history().is_empty()); + } + + #[test] + fn line_ending_is_appended_once() { + assert_eq!(apply_line_ending("AT", LineEnding::Crlf), b"AT\r\n"); + assert_eq!(apply_line_ending("AT\r\n", LineEnding::Crlf), b"AT\r\n"); + assert_eq!(apply_line_ending("AT", LineEnding::Cr), b"AT\r"); + assert_eq!(apply_line_ending("AT", LineEnding::None), b"AT"); + } + + #[test] + fn send_receive_history_and_capture() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let profile = Profile { host: "127.0.0.1".into(), port: listener.local_addr().unwrap().port() as i64, ..Profile::new(crate::models::Protocol::Raw) }; + let (tx, rx) = mpsc::channel::<(Vec, String, i64)>(); + let (ctx, crx) = mpsc::channel::<()>(); + let c = RawClient::new( + &profile, + Arc::new(move |d, dir, ts| tx.send((d.to_vec(), dir.to_string(), ts)).unwrap()), + Arc::new(|_| {}), + Arc::new(move || ctx.send(()).unwrap()), + ); + assert!(c.write(b"x").is_err()); + c.connect().unwrap(); + let (mut srv, _) = listener.accept().unwrap(); + srv.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); + + c.write_text("AT").unwrap(); + c.write_hex("01 ff").unwrap(); + c.write_escaped(r"\x00z").unwrap(); + assert!(c.write_hex("zz").unwrap_err().to_string().starts_with("invalid hex input")); + let mut buf = [0u8; 8]; + srv.read_exact(&mut buf).unwrap(); + assert_eq!(&buf, b"AT\r\n\x01\xff\x00z"); + + srv.write_all(b"OK").unwrap(); + let mut events = Vec::new(); + while events.iter().map(|(d, dir, _): &(Vec, String, i64)| if dir == RECV { d.len() } else { 0 }).sum::() < 2 { + events.push(rx.recv_timeout(Duration::from_secs(5)).unwrap()); + } + assert_eq!(events[0].1, SEND); + assert!(events[0].2 > 1_600_000_000_000); + let hist = c.history(); + assert_eq!(hist.iter().filter(|e| e.direction == SEND).count(), 3); + assert_eq!(hist.last().unwrap().direction, RECV); + + let path = std::env::temp_dir().join(format!("nexus-raw-capture-{}.json", std::process::id())); + c.save_capture(&path).unwrap(); + let v: serde_json::Value = serde_json::from_slice(&std::fs::read(&path).unwrap()).unwrap(); + std::fs::remove_file(&path).unwrap(); + assert_eq!(v[0]["direction"], "send"); + assert_eq!(v[0]["data"], "QVQNCg=="); // "AT\r\n" + assert!(v[0]["timestamp"].as_str().unwrap().contains('T')); + + c.close(); + c.close(); + crx.recv_timeout(Duration::from_secs(1)).unwrap(); + assert!(crx.recv_timeout(Duration::from_millis(100)).is_err()); + } +} diff --git a/rust/core/src/clients/serial.rs b/rust/core/src/clients/serial.rs new file mode 100644 index 0000000..977f925 --- /dev/null +++ b/rust/core/src/clients/serial.rs @@ -0,0 +1,368 @@ +//! Serial client (new protocol, no Go original) on the `serialport` crate. The device path is +//! `Profile::host`, line settings come from `Profile::serial`. Same conventions as the other +//! clients: blocking `connect`, an own reader thread, `on_close` exactly once (unplug included). + +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +use serialport::{DataBits, FlowControl, Parity, SerialPort, StopBits}; + +use super::Callbacks; +use crate::models::{self, LineEnding, Profile, SerialConfig}; + +/// The reader polls this often, so `close` stops it within this long. +const READ_POLL: Duration = Duration::from_millis(100); +const WRITE_TIMEOUT: Duration = Duration::from_secs(5); +const BREAK_DURATION: Duration = Duration::from_millis(250); + +type OpenPort = (Box, Arc); + +pub struct SerialClient { + inner: Arc, +} + +struct Inner { + path: String, + cfg: SerialConfig, + cb: Callbacks, + /// The writer handle plus this connection's stop flag (the reader owns a clone of the port). + port: Mutex>, +} + +/// Device paths of the serial ports on this machine (empty if enumeration fails). +pub fn list_ports() -> Vec { + serialport::available_ports().map(|v| v.into_iter().map(|p| p.port_name).collect()).unwrap_or_default() +} + +impl SerialClient { + pub fn new(profile: &Profile, cb: Callbacks) -> SerialClient { + SerialClient { + inner: Arc::new(Inner { + path: profile.host.trim().to_string(), + cfg: profile.serial.clone().unwrap_or_default(), + cb, + port: Mutex::new(None), + }), + } + } + + /// Opens and configures the port, applies DTR/RTS, and starts the reader thread. + pub fn connect(&self) -> crate::Result<()> { + let inner = &self.inner; + if inner.port.lock().unwrap().is_some() { + return Err("already connected".into()); + } + if inner.path.is_empty() { + return Err("no serial device specified".into()); + } + let c = &inner.cfg; + let mut port = serialport::new(&inner.path, c.baud_rate) + .data_bits(match c.data_bits { + 5 => DataBits::Five, + 6 => DataBits::Six, + 7 => DataBits::Seven, + _ => DataBits::Eight, + }) + .parity(match c.parity { + models::Parity::None => Parity::None, + models::Parity::Odd => Parity::Odd, + models::Parity::Even => Parity::Even, + }) + .stop_bits(if c.stop_bits == 2 { StopBits::Two } else { StopBits::One }) + .flow_control(match c.flow_control { + models::FlowControl::None => FlowControl::None, + models::FlowControl::Xonxoff => FlowControl::Software, + models::FlowControl::Rtscts => FlowControl::Hardware, + }) + .timeout(WRITE_TIMEOUT) + .open() + .map_err(|e| open_error(&inner.path, &e))?; + // Opening asserts both lines on most OSes, so set them either way. Ptys and some adapters + // have no modem lines; that's not fatal. + let _ = port.write_data_terminal_ready(c.dtr_on_connect); + let _ = port.write_request_to_send(c.rts_on_connect); + + let mut reader = port.try_clone()?; + reader.set_timeout(READ_POLL)?; + let stop = Arc::new(AtomicBool::new(false)); + { + let mut guard = inner.port.lock().unwrap(); + if guard.is_some() { + return Err("already connected".into()); + } + *guard = Some((port, stop.clone())); + } + let me = inner.clone(); + std::thread::spawn(move || me.read_loop(reader, stop)); + Ok(()) + } + + /// Sends user input: the Enter key's `\r` becomes the configured line ending, text is encoded, + /// and with local echo on the input is fed back through `on_data` (Enter shown as CRLF). + pub fn write(&self, data: &[u8]) -> crate::Result<()> { + let out = translate_enter(data, self.inner.cfg.line_ending); + let out = if super::is_latin1(&self.inner.cfg.encoding) { super::utf8_to_latin1(&out) } else { out }; + self.with_port(|p| Ok(p.write_all(&out)?))?; + if self.inner.cfg.local_echo { + (self.inner.cb.on_data)(&translate_enter(data, LineEnding::Crlf)); + } + Ok(()) + } + + pub fn set_lines(&self, dtr: bool, rts: bool) -> crate::Result<()> { + self.with_port(|p| { + p.write_data_terminal_ready(dtr)?; + p.write_request_to_send(rts)?; + Ok(()) + }) + } + + /// Holds the line in break for ~250 ms (blocks the caller, and other writes, meanwhile). + pub fn send_break(&self) -> crate::Result<()> { + self.with_port(|p| { + p.set_break()?; + std::thread::sleep(BREAK_DURATION); + p.clear_break()?; + Ok(()) + }) + } + + pub fn close(&self) { + self.inner.close(); + } + + pub fn is_connected(&self) -> bool { + self.inner.port.lock().unwrap().is_some() + } + + fn with_port(&self, f: impl FnOnce(&mut Box) -> crate::Result<()>) -> crate::Result<()> { + let mut guard = self.inner.port.lock().unwrap(); + let (port, _) = guard.as_mut().ok_or("not connected")?; + f(port) + } +} + +impl Inner { + fn read_loop(&self, mut reader: Box, stop: Arc) { + let mut buf = [0u8; 4096]; + let latin1 = super::is_latin1(&self.cfg.encoding); + while !stop.load(Ordering::SeqCst) { + let err = match reader.read(&mut buf) { + Ok(0) => "device disconnected".to_string(), + Ok(n) if latin1 => { + (self.cb.on_data)(super::latin1_to_utf8(&buf[..n]).as_bytes()); + continue; + } + Ok(n) => { + (self.cb.on_data)(&buf[..n]); + continue; + } + Err(e) if matches!(e.kind(), std::io::ErrorKind::TimedOut | std::io::ErrorKind::Interrupted) => continue, + Err(e) => format!("device disconnected: {e}"), + }; + if !stop.load(Ordering::SeqCst) { + (self.cb.on_error)(format!("read error: {err}")); + self.close(); + } + return; + } + } + + fn close(&self) { + let taken = self.port.lock().unwrap().take(); + if let Some((port, stop)) = taken { + stop.store(true, Ordering::SeqCst); + drop(port); + (self.cb.on_close)(); + } + } +} + +/// Replaces each `\r` (the Enter key) with `le`; `None` leaves it alone. +fn translate_enter(data: &[u8], le: LineEnding) -> Vec { + if le == LineEnding::None { + return data.to_vec(); + } + let mut out = Vec::with_capacity(data.len() + 4); + for &b in data { + if b == b'\r' { out.extend_from_slice(le.bytes()) } else { out.push(b) } + } + out +} + +fn open_error(path: &str, e: &serialport::Error) -> crate::Error { + let desc = e.description.to_lowercase(); + let busy = ["busy", "lock", "access is denied", "in use"].iter().any(|s| desc.contains(s)); + let hint = match e.kind { + serialport::ErrorKind::NoDevice if busy => " (the port is in use by another program)", + serialport::ErrorKind::Io(std::io::ErrorKind::PermissionDenied) => { + " (permission denied: add your user to the dialout (Linux) / uucp group, then log in again)" + } + _ => "", + }; + format!("cannot open serial port {path}: {}{hint}", e.description).into() +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::mpsc; + + #[test] + fn enter_translation() { + assert_eq!(translate_enter(b"AT\r", LineEnding::Crlf), b"AT\r\n"); + assert_eq!(translate_enter(b"AT\r", LineEnding::Lf), b"AT\n"); + assert_eq!(translate_enter(b"AT\r", LineEnding::Cr), b"AT\r"); + assert_eq!(translate_enter(b"AT\r", LineEnding::None), b"AT\r"); + } + + #[test] + fn error_hints() { + let perm = serialport::Error::new(serialport::ErrorKind::Io(std::io::ErrorKind::PermissionDenied), "Permission denied"); + assert!(open_error("/dev/ttyUSB0", &perm).to_string().contains("dialout")); + let busy = serialport::Error::new(serialport::ErrorKind::NoDevice, "Device or resource busy"); + assert!(open_error("/dev/ttyUSB0", &busy).to_string().contains("in use by another program")); + let missing = serialport::Error::new(serialport::ErrorKind::Io(std::io::ErrorKind::NotFound), "No such file or directory"); + assert_eq!(open_error("/dev/x", &missing).to_string(), "cannot open serial port /dev/x: No such file or directory"); + } + + #[test] + fn not_connected() { + let c = SerialClient::new(&Profile::new(models::Protocol::Serial), Callbacks::noop()); + assert!(c.write(b"x").is_err()); + assert_eq!(c.connect().unwrap_err().to_string(), "no serial device specified"); + let _ = list_ports(); + } + + /// A pty pair: the client opens the slave as its "device", the test plays the far end on the master. + #[cfg(target_os = "linux")] + mod pty { + use super::*; + use std::fs::File; + use std::io::Write; + use std::os::fd::{AsRawFd, FromRawFd}; + + struct Pty { + master: File, + slave: File, + path: String, + } + + fn open_pty() -> Pty { + let (mut m, mut s) = (0, 0); + let mut name = [0 as libc::c_char; 128]; + let rc = unsafe { libc::openpty(&mut m, &mut s, name.as_mut_ptr(), std::ptr::null(), std::ptr::null()) }; + assert_eq!(rc, 0); + let path = unsafe { std::ffi::CStr::from_ptr(name.as_ptr()) }.to_string_lossy().into_owned(); + Pty { master: unsafe { File::from_raw_fd(m) }, slave: unsafe { File::from_raw_fd(s) }, path } + } + + /// Reads from the master until something arrives or `timeout` passes. + fn read_master(master: &File, timeout: Duration) -> Vec { + let mut pfd = libc::pollfd { fd: master.as_raw_fd(), events: libc::POLLIN, revents: 0 }; + if unsafe { libc::poll(&mut pfd, 1, timeout.as_millis() as i32) } != 1 { + return Vec::new(); + } + let mut buf = [0u8; 256]; + let n = std::io::Read::read(&mut &*master, &mut buf).unwrap(); + buf[..n].to_vec() + } + + fn master_n(master: &File, n: usize) -> Vec { + let mut got = Vec::new(); + while got.len() < n { + let chunk = read_master(master, Duration::from_secs(5)); + assert!(!chunk.is_empty(), "timed out"); + got.extend(chunk); + } + got + } + + fn recv_n(rx: &mpsc::Receiver>, n: usize) -> Vec { + let mut got = Vec::new(); + while got.len() < n { + got.extend(rx.recv_timeout(Duration::from_secs(5)).expect("timed out")); + } + got + } + + fn client(pty: &Pty, cfg: SerialConfig) -> (SerialClient, mpsc::Receiver>, mpsc::Receiver, mpsc::Receiver<()>) { + let (dtx, drx) = mpsc::channel(); + let (etx, erx) = mpsc::channel(); + let (ctx, crx) = mpsc::channel(); + let cb = Callbacks { + on_data: Arc::new(move |d| { let _ = dtx.send(d.to_vec()); }), + on_error: Arc::new(move |e| { let _ = etx.send(e); }), + on_close: Arc::new(move || { let _ = ctx.send(()); }), + }; + let profile = Profile { host: pty.path.clone(), serial: Some(cfg), ..Profile::new(models::Protocol::Serial) }; + (SerialClient::new(&profile, cb), drx, erx, crx) + } + + #[test] + fn data_echo_line_endings_and_close() { + let mut pty = open_pty(); + let cfg = SerialConfig { local_echo: true, line_ending: LineEnding::Crlf, ..Default::default() }; + let (c, data, errors, closed) = client(&pty, cfg); + c.connect().unwrap(); + assert!(c.connect().is_err()); + + c.write(b"AT\r").unwrap(); + assert_eq!(master_n(&pty.master, 4), b"AT\r\n"); + assert_eq!(recv_n(&data, 4), b"AT\r\n"); // local echo + + pty.master.write_all(b"OK\r\n").unwrap(); + assert_eq!(recv_n(&data, 4), b"OK\r\n"); + + c.close(); + c.close(); + closed.recv_timeout(Duration::from_secs(1)).unwrap(); + assert!(closed.recv_timeout(Duration::from_millis(300)).is_err()); + assert!(errors.try_recv().is_err()); + assert!(c.write(b"x").is_err()); + } + + #[test] + fn latin1_encoding() { + let mut pty = open_pty(); + let cfg = SerialConfig { encoding: "latin1".into(), line_ending: LineEnding::Lf, ..Default::default() }; + let (c, data, _e, _c) = client(&pty, cfg); + c.connect().unwrap(); + c.write("é\r".as_bytes()).unwrap(); + assert_eq!(master_n(&pty.master, 2), [0xe9, b'\n']); + pty.master.write_all(&[0xe9]).unwrap(); + assert_eq!(recv_n(&data, 2), "é".as_bytes()); + } + + #[test] + fn xon_xoff_flow_control() { + let mut pty = open_pty(); + let cfg = SerialConfig { flow_control: models::FlowControl::Xonxoff, line_ending: LineEnding::None, ..Default::default() }; + let (c, _d, _e, _c) = client(&pty, cfg); + c.connect().unwrap(); + pty.master.write_all(&[0x13]).unwrap(); // XOFF + std::thread::sleep(Duration::from_millis(100)); + let c = Arc::new(c); + let w = c.clone(); + let writer = std::thread::spawn(move || w.write(b"x")); + assert!(read_master(&pty.master, Duration::from_millis(300)).is_empty(), "output not paused by XOFF"); + pty.master.write_all(&[0x11]).unwrap(); // XON + assert_eq!(master_n(&pty.master, 1), b"x"); + writer.join().unwrap().unwrap(); + } + + #[test] + fn unplug_reports_error_and_closes() { + let pty = open_pty(); + let (c, _d, errors, closed) = client(&pty, SerialConfig::default()); + c.connect().unwrap(); + drop(pty.master); + drop(pty.slave); + let err = errors.recv_timeout(Duration::from_secs(5)).unwrap(); + assert!(err.starts_with("read error: device disconnected"), "{err}"); + closed.recv_timeout(Duration::from_secs(1)).unwrap(); + assert!(!c.is_connected()); + } + } +} diff --git a/rust/core/src/clients/telnet.rs b/rust/core/src/clients/telnet.rs new file mode 100644 index 0000000..5ef5cac --- /dev/null +++ b/rust/core/src/clients/telnet.rs @@ -0,0 +1,434 @@ +//! Telnet client (port of Go `internal/telnetclient`): RFC 854/855 option negotiation (ECHO, SGA, +//! TTYPE, NAWS; everything else refused), IAC escaping, line-ending conversion, ISO 8859-1 output +//! encoding and BRK. Go read but never acted on `lineMode`, and neither do we. + +use std::sync::{Arc, Mutex}; + +use super::{Callbacks, DataFn, DebugFn, TcpConn}; +use crate::models::{LineEnding, Profile, TelnetConfig}; + +pub const IAC: u8 = 255; +pub const DONT: u8 = 254; +pub const DO: u8 = 253; +pub const WONT: u8 = 252; +pub const WILL: u8 = 251; +pub const SB: u8 = 250; +pub const SE: u8 = 240; +pub const BRK: u8 = 243; + +pub const OPT_ECHO: u8 = 1; +pub const OPT_SGA: u8 = 3; +pub const OPT_TERM_TYPE: u8 = 24; +pub const OPT_NAWS: u8 = 31; + +const SUB_IS: u8 = 0; +const SUB_SEND: u8 = 1; + +/// An unterminated IAC sequence longer than this is dropped rather than buffered forever. +const MAX_PENDING: usize = 8192; + +pub struct TelnetClient { + inner: Arc, +} + +struct Inner { + conn: Arc, + host: String, + port: i64, + connect_timeout: i64, + cfg: TelnetConfig, + encoding: String, + on_data: DataFn, + on_debug: Option, + state: Mutex, +} + +struct State { + local_echo: bool, + cols: u16, + rows: u16, +} + +impl TelnetClient { + /// `on_debug` receives negotiation messages ("received DO 31", "sending WILL 31", …), always, + /// as in Go; the profile's `negotiationDebug` is for the caller to honour. + pub fn new(profile: &Profile, cb: Callbacks, on_debug: Option) -> TelnetClient { + let cfg = profile.telnet.clone().unwrap_or_default(); + let encoding = if cfg.encoding.is_empty() { "utf-8".to_string() } else { cfg.encoding.clone() }; + let state = State { local_echo: cfg.local_echo, cols: 80, rows: 24 }; + TelnetClient { + inner: Arc::new(Inner { + conn: Arc::new(TcpConn::new(cb.on_error, cb.on_close)), + host: profile.host.clone(), + port: if profile.port == 0 { 23 } else { profile.port }, + connect_timeout: profile.connect_timeout, + cfg, + encoding, + on_data: cb.on_data, + on_debug, + state: Mutex::new(state), + }), + } + } + + pub fn connect(&self) -> crate::Result<()> { + let inner = &self.inner; + if inner.conn.is_connected() { + return Err("already connected".into()); + } + let (addr, res) = super::dial(&inner.host, inner.port, inner.connect_timeout); + let stream = res.map_err(|e| format!("Telnet connection to {addr} failed: {e}"))?; + let me = inner.clone(); + let mut pending = Vec::new(); + inner.conn.connect(stream, move |chunk| me.process(&mut pending, chunk)) + } + + /// Sends user input: line endings converted, encoded, literal 0xFF doubled. + pub fn write(&self, data: &[u8]) -> crate::Result<()> { + let converted = convert_line_ending(data, self.inner.cfg.line_ending); + let encoded = encode_outgoing(&converted, &self.inner.encoding); + self.inner.conn.write(&escape_iac(&encoded)) + } + + /// Records the size and sends a NAWS update (sent even if the server never asked for NAWS, as in Go). + pub fn resize(&self, cols: u16, rows: u16) -> crate::Result<()> { + if !self.inner.conn.is_connected() { + return Err("not connected".into()); + } + { + let mut st = self.inner.state.lock().unwrap(); + st.cols = cols; + st.rows = rows; + } + self.inner.send_naws(cols, rows) + } + + /// Sends IAC BRK. + pub fn send_break(&self) -> crate::Result<()> { + self.inner.conn.write(&[IAC, BRK]) + } + + pub fn close(&self) { + self.inner.conn.close(); + } + + pub fn is_connected(&self) -> bool { + self.inner.conn.is_connected() + } + + /// The negotiated local-echo state: starts at the profile's `localEcho`, cleared by the + /// server's WILL ECHO and set by WONT ECHO. Like Go, the client itself never echoes. + pub fn local_echo(&self) -> bool { + self.inner.state.lock().unwrap().local_echo + } +} + +impl Inner { + /// Splits a received chunk into data and IAC sequences. Unlike Go, a sequence cut off at the + /// end of a read is kept for the next read instead of dropped. + fn process(&self, pending: &mut Vec, chunk: &[u8]) { + pending.extend_from_slice(chunk); + let mut data = Vec::new(); + let mut i = 0; + while i < pending.len() { + if pending[i] != IAC { + data.push(pending[i]); + i += 1; + continue; + } + if !data.is_empty() { + (self.on_data)(&data); + data.clear(); + } + let consumed = self.handle_iac(&pending[i..]); + if consumed == 0 { + break; + } + i += consumed; + } + if !data.is_empty() { + (self.on_data)(&data); + } + pending.drain(..i); + if pending.len() > MAX_PENDING { + pending.clear(); + } + } + + /// Handles the IAC sequence at `data[0]`; returns bytes consumed, 0 if incomplete. + fn handle_iac(&self, data: &[u8]) -> usize { + if data.len() < 2 { + return 0; + } + match data[1] { + IAC => { + (self.on_data)(&[IAC]); + 2 + } + DO | DONT | WILL | WONT => { + let Some(&opt) = data.get(2) else { return 0 }; + match data[1] { + DO => self.handle_do(opt), + DONT => self.handle_dont(opt), + WILL => self.handle_will(opt), + _ => self.handle_wont(opt), + } + 3 + } + SB => match (2..data.len().saturating_sub(1)).find(|&i| data[i] == IAC && data[i + 1] == SE) { + Some(end) => { + self.process_subnegotiation(&data[2..end]); + end + 2 + } + None => 0, + }, + cmd => { + self.debug(format!("received IAC command: {cmd}")); + 2 + } + } + } + + fn handle_do(&self, opt: u8) { + self.debug(format!("received DO {opt}")); + match opt { + OPT_TERM_TYPE | OPT_SGA => self.negotiate(WILL, opt), + OPT_NAWS => { + self.negotiate(WILL, opt); + let (cols, rows) = { + let st = self.state.lock().unwrap(); + (st.cols, st.rows) + }; + let _ = self.send_naws(cols, rows); + } + _ => self.negotiate(WONT, opt), + } + } + + fn handle_dont(&self, opt: u8) { + self.debug(format!("received DONT {opt}")); + self.negotiate(WONT, opt); + } + + fn handle_will(&self, opt: u8) { + self.debug(format!("received WILL {opt}")); + match opt { + OPT_ECHO => { + self.negotiate(DO, opt); + self.state.lock().unwrap().local_echo = false; + } + OPT_SGA => self.negotiate(DO, opt), + _ => self.negotiate(DONT, opt), + } + } + + fn handle_wont(&self, opt: u8) { + self.debug(format!("received WONT {opt}")); + if opt == OPT_ECHO { + self.state.lock().unwrap().local_echo = true; + } + self.negotiate(DONT, opt); + } + + fn process_subnegotiation(&self, payload: &[u8]) { + let Some(&opt) = payload.first() else { return }; + self.debug(format!("sub-negotiation for option {opt}")); + if opt == OPT_TERM_TYPE && payload.get(1) == Some(&SUB_SEND) { + let tt = if self.cfg.terminal_type.is_empty() { "xterm-256color" } else { &self.cfg.terminal_type }; + self.debug(format!("sending terminal type: {tt}")); + let mut resp = vec![IAC, SB, OPT_TERM_TYPE, SUB_IS]; + resp.extend_from_slice(tt.to_uppercase().as_bytes()); + resp.extend_from_slice(&[IAC, SE]); + let _ = self.conn.write(&resp); + } + } + + fn negotiate(&self, cmd: u8, opt: u8) { + self.debug(format!("sending {} {opt}", command_name(cmd))); + let _ = self.conn.write(&[IAC, cmd, opt]); + } + + fn send_naws(&self, cols: u16, rows: u16) -> crate::Result<()> { + self.debug(format!("sending NAWS: {cols}x{rows}")); + let [c0, c1] = cols.to_be_bytes(); + let [r0, r1] = rows.to_be_bytes(); + self.conn.write(&[IAC, SB, OPT_NAWS, c0, c1, r0, r1, IAC, SE]) + } + + fn debug(&self, msg: String) { + if let Some(f) = &self.on_debug { + f(msg); + } + } +} + +fn command_name(cmd: u8) -> String { + match cmd { + WILL => "WILL".into(), + WONT => "WONT".into(), + DO => "DO".into(), + DONT => "DONT".into(), + _ => format!("CMD({cmd})"), + } +} + +/// Normalises CRLF/CR/LF to the configured ending (`None` leaves data untouched). +fn convert_line_ending(data: &[u8], le: LineEnding) -> Vec { + if le == LineEnding::None { + return data.to_vec(); + } + let mut out = Vec::with_capacity(data.len() + 8); + let mut i = 0; + while i < data.len() { + match data[i] { + b'\r' => { + if data.get(i + 1) == Some(&b'\n') { + i += 1; + } + out.extend_from_slice(le.bytes()); + } + b'\n' => out.extend_from_slice(le.bytes()), + b => out.push(b), + } + i += 1; + } + out +} + +/// Doubles literal 0xFF bytes. +fn escape_iac(data: &[u8]) -> Vec { + let mut out = Vec::with_capacity(data.len()); + for &b in data { + out.push(b); + if b == IAC { + out.push(IAC); + } + } + out +} + +/// UTF-8 → ISO 8859-1 for the Latin-1 encodings; everything else is sent as-is. Unlike Go this +/// runs before IAC escaping, so a Latin-1 'ÿ' (0xFF) is escaped correctly. +fn encode_outgoing(data: &[u8], encoding: &str) -> Vec { + if super::is_latin1(encoding) { super::utf8_to_latin1(data) } else { data.to_vec() } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{Read, Write}; + use std::net::TcpListener; + use std::sync::mpsc; + use std::time::Duration; + + #[test] + fn line_endings() { + let cases = [ + (LineEnding::Crlf, "a\nb\r\nc\rd", "a\r\nb\r\nc\r\nd"), + (LineEnding::Lf, "a\r\nb\rc\n", "a\nb\nc\n"), + (LineEnding::Cr, "a\r\nb\n", "a\rb\r"), + (LineEnding::None, "a\r\nb\n", "a\r\nb\n"), + ]; + for (le, input, want) in cases { + assert_eq!(convert_line_ending(input.as_bytes(), le), want.as_bytes(), "{le:?}"); + } + } + + #[test] + fn iac_escaping_and_latin1() { + assert_eq!(escape_iac(&[1, 0xff, 2]), vec![1, 0xff, 0xff, 2]); + assert_eq!(encode_outgoing("é".as_bytes(), "Latin-1"), vec![0xe9]); + assert_eq!(encode_outgoing("€".as_bytes(), "latin1"), "€".as_bytes()); // unencodable: unchanged + assert_eq!(encode_outgoing("é".as_bytes(), "utf-8"), "é".as_bytes()); + } + + fn read_n(s: &mut std::net::TcpStream, n: usize) -> Vec { + let mut buf = vec![0; n]; + s.read_exact(&mut buf).unwrap(); + buf + } + + #[test] + fn negotiates_and_frames_data() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let profile = Profile { host: "127.0.0.1".into(), port: listener.local_addr().unwrap().port() as i64, ..Profile::new(crate::models::Protocol::Telnet) }; + let (dtx, drx) = mpsc::channel::>(); + let (etx, erx) = mpsc::channel::(); + let (ctx, crx) = mpsc::channel::<()>(); + let debug = Arc::new(Mutex::new(Vec::::new())); + let dbg = debug.clone(); + let cb = Callbacks { + on_data: Arc::new(move |d| dtx.send(d.to_vec()).unwrap()), + on_error: Arc::new(move |e| etx.send(e).unwrap()), + on_close: Arc::new(move || ctx.send(()).unwrap()), + }; + let client = TelnetClient::new(&profile, cb, Some(Arc::new(move |m| dbg.lock().unwrap().push(m)))); + client.connect().unwrap(); + let (mut srv, _) = listener.accept().unwrap(); + srv.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); + + srv.write_all(&[IAC, DO, OPT_TERM_TYPE, IAC, DO, OPT_NAWS, IAC, WILL, OPT_ECHO, IAC, WILL, OPT_SGA, IAC, DO, 99]) + .unwrap(); + let mut want = vec![IAC, WILL, OPT_TERM_TYPE, IAC, WILL, OPT_NAWS, IAC, SB, OPT_NAWS, 0, 80, 0, 24, IAC, SE]; + want.extend_from_slice(&[IAC, DO, OPT_ECHO, IAC, DO, OPT_SGA, IAC, WONT, 99]); + assert_eq!(read_n(&mut srv, want.len()), want); + assert!(!client.local_echo()); + + // Data with an escaped 0xFF, then a TTYPE SEND split across two writes. + srv.write_all(b"hi").unwrap(); + srv.write_all(&[IAC, IAC, IAC, SB, OPT_TERM_TYPE]).unwrap(); + std::thread::sleep(Duration::from_millis(50)); + srv.write_all(&[SUB_SEND, IAC, SE]).unwrap(); + let mut want = vec![IAC, SB, OPT_TERM_TYPE, SUB_IS]; + want.extend_from_slice(b"XTERM-256COLOR"); + want.extend_from_slice(&[IAC, SE]); + assert_eq!(read_n(&mut srv, want.len()), want); + let mut got = Vec::new(); + while got.len() < 3 { + got.extend(drx.recv_timeout(Duration::from_secs(5)).unwrap()); + } + assert_eq!(got, b"hi\xff"); + + srv.write_all(&[IAC, WONT, OPT_ECHO]).unwrap(); + assert_eq!(read_n(&mut srv, 3), [IAC, DONT, OPT_ECHO]); + assert!(client.local_echo()); + + client.resize(132, 50).unwrap(); + assert_eq!(read_n(&mut srv, 9), [IAC, SB, OPT_NAWS, 0, 132, 0, 50, IAC, SE]); + client.send_break().unwrap(); + assert_eq!(read_n(&mut srv, 2), [IAC, BRK]); + client.write(b"ls\n\xff").unwrap(); + assert_eq!(read_n(&mut srv, 6), b"ls\r\n\xff\xff"); + + let log = debug.lock().unwrap().clone(); + for m in ["received DO 24", "sending WILL 31", "sending NAWS: 80x24", "sending WONT 99", "sending terminal type: xterm-256color"] { + assert!(log.iter().any(|l| l == m), "missing debug {m:?} in {log:?}"); + } + + // Remote close: Go reports "read error: EOF" then closes, exactly once. + drop(srv); + assert_eq!(erx.recv_timeout(Duration::from_secs(5)).unwrap(), "read error: EOF"); + crx.recv_timeout(Duration::from_secs(5)).unwrap(); + client.close(); + assert!(crx.recv_timeout(Duration::from_millis(100)).is_err()); + assert!(client.write(b"x").is_err()); + } + + #[test] + fn local_close_fires_once_without_error() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let profile = Profile { host: "127.0.0.1".into(), port: listener.local_addr().unwrap().port() as i64, ..Profile::new(crate::models::Protocol::Telnet) }; + let (etx, erx) = mpsc::channel::(); + let (ctx, crx) = mpsc::channel::<()>(); + let cb = Callbacks { on_error: Arc::new(move |e| etx.send(e).unwrap()), on_close: Arc::new(move || ctx.send(()).unwrap()), ..Callbacks::noop() }; + let client = TelnetClient::new(&profile, cb, None); + client.connect().unwrap(); + assert!(client.connect().is_err()); + let _srv = listener.accept().unwrap(); + client.close(); + client.close(); + crx.recv_timeout(Duration::from_secs(1)).unwrap(); + assert!(crx.recv_timeout(Duration::from_millis(100)).is_err()); + assert!(erx.recv_timeout(Duration::from_millis(100)).is_err()); + } +} From 4674c7cb3ff2664d44771063a4f387ce22adc481 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:18:15 +0100 Subject: [PATCH 10/45] rust: app crate scaffolding, terminal colour schemes --- .gitignore | 1 + rust/Cargo.lock | 2755 ++++++++++++++++++++++++++++++- rust/app/Cargo.toml | 17 + rust/app/build.rs | 14 + rust/app/src/terminal_themes.rs | 130 ++ 5 files changed, 2898 insertions(+), 19 deletions(-) create mode 100644 rust/app/build.rs create mode 100644 rust/app/src/terminal_themes.rs diff --git a/.gitignore b/.gitignore index 2ead8f3..04f1d71 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,4 @@ build/releases/manifest* # Rust port compatibility fixtures (generated by cmd/genfixtures) !rust/core/tests/fixtures/** +.claude/worktrees/ diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 22e6390..0f23771 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,6 +2,121 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "accesskit" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320" +dependencies = [ + "enumn", + "serde", + "uuid", +] + +[[package]] +name = "accesskit_atspi_common" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e8c61bee90b42a772d39d06a740207dc71a4e780004ace1db8d99fb1baaa954" +dependencies = [ + "accesskit", + "accesskit_consumer 0.36.0", + "atspi-common", + "phf", + "serde", + "zvariant 5.15.0", +] + +[[package]] +name = "accesskit_consumer" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_consumer" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245" +dependencies = [ + "accesskit", + "hashbrown 0.16.1", +] + +[[package]] +name = "accesskit_macos" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce02dc63b43f0c9296af9ac946312a2dc8814427d7a64d2d600971dac55b6076" +dependencies = [ + "accesskit", + "accesskit_consumer 0.38.0", + "hashbrown 0.16.1", + "objc2 0.5.3", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "accesskit_unix" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c" +dependencies = [ + "accesskit", + "accesskit_atspi_common", + "async-channel", + "async-executor", + "async-task", + "atspi", + "futures-lite", + "futures-util", + "serde", + "zbus 5.19.0", +] + +[[package]] +name = "accesskit_windows" +version = "0.32.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21" +dependencies = [ + "accesskit", + "accesskit_consumer 0.35.0", + "hashbrown 0.16.1", + "static_assertions", + "windows", + "windows-core", +] + +[[package]] +name = "accesskit_winit" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313" +dependencies = [ + "accesskit", + "accesskit_macos", + "accesskit_unix", + "accesskit_windows", + "raw-window-handle", + "winit", +] + [[package]] name = "adler2" version = "2.0.1" @@ -80,6 +195,79 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alacritty_terminal" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda177466b9524d59f1b12f0dd30b68696788e9992a7e959021c4a0ed96fcf59" +dependencies = [ + "base64", + "bitflags 2.13.2", + "home", + "libc", + "log", + "miow", + "parking_lot", + "piper", + "polling", + "regex-automata", + "rustix 1.1.5", + "rustix-openpty", + "serde", + "signal-hook", + "unicode-width", + "vte", + "windows-sys 0.59.0", +] + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags 2.13.2", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.21", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + [[package]] name = "android_system_properties" version = "0.1.6" @@ -95,6 +283,26 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" +[[package]] +name = "arboard" +version = "3.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0348a1c054491f4bfe6ab86a7b6ab1e44e45d899005de92f58b3df180b36ddaf" +dependencies = [ + "clipboard-win", + "image", + "log", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "windows-sys 0.60.2", + "x11rb", +] + [[package]] name = "argon2" version = "0.5.3" @@ -119,6 +327,18 @@ dependencies = [ "password-hash 0.6.1", ] +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + [[package]] name = "async-broadcast" version = "0.7.2" @@ -181,7 +401,7 @@ dependencies = [ "futures-lite", "parking", "polling", - "rustix", + "rustix 1.1.5", "slab", "windows-sys 0.61.2", ] @@ -212,7 +432,7 @@ dependencies = [ "cfg-if", "event-listener", "futures-lite", - "rustix", + "rustix 1.1.5", ] [[package]] @@ -238,7 +458,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix", + "rustix 1.1.5", "signal-hook-registry", "slab", "windows-sys 0.61.2", @@ -267,6 +487,43 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atspi" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77886257be21c9cd89a4ae7e64860c6f0eefca799bb79127913052bd0eefb3d" +dependencies = [ + "atspi-common", + "atspi-proxies", +] + +[[package]] +name = "atspi-common" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c5617155740c98003016429ad13fe43ce7a77b007479350a9f8bf95a29f63d" +dependencies = [ + "enumflags2", + "serde", + "static_assertions", + "zbus 5.19.0", + "zbus-lockstep", + "zbus-lockstep-macros", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "atspi-proxies" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2230e48787ed3eb4088996eab66a32ca20c0b67bbd4fd6cdfe79f04f1f04c9fc" +dependencies = [ + "atspi-common", + "serde", + "zbus 5.19.0", +] + [[package]] name = "autocfg" version = "1.5.1" @@ -302,6 +559,21 @@ dependencies = [ "sha2 0.11.0", ] +[[package]] +name = "bit-set" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2f926cc3060f09db9ebc5b52823d85268d24bb917e472c0c4bea35780a7d" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + [[package]] name = "bitflags" version = "1.3.2" @@ -372,6 +644,24 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2 0.5.3", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2 0.6.5", +] + [[package]] name = "blocking" version = "1.7.0" @@ -401,18 +691,94 @@ version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a1f896587b6f2c069c73d2f0913e2d590c3990285cd2f0b6aa02b786b4c679c" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "byteorder-lite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" + [[package]] name = "bytes" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +[[package]] +name = "calloop" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" +dependencies = [ + "bitflags 2.13.2", + "log", + "polling", + "rustix 0.38.44", + "slab", + "thiserror 1.0.69", +] + +[[package]] +name = "calloop" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e" +dependencies = [ + "bitflags 2.13.2", + "polling", + "rustix 1.1.5", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" +dependencies = [ + "calloop 0.13.0", + "rustix 0.38.44", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop 0.14.5", + "rustix 1.1.5", + "wayland-backend", + "wayland-client", +] + [[package]] name = "cbc" version = "0.1.2" @@ -438,6 +804,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" dependencies = [ "find-msvc-tools", + "jobserver", + "libc", "shlex", ] @@ -459,6 +827,15 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "cgl" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ced0551234e87afee12411d535648dd89d2e7f34c78b753395567aff3d447ff" +dependencies = [ + "libc", +] + [[package]] name = "chacha20" version = "0.10.2" @@ -508,12 +885,49 @@ dependencies = [ "zeroize", ] +[[package]] +name = "clipboard-win" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03770d3df201d4fb868f2c9c59e66a3e4e2bd06692a0fe701e7103c7e84d4" +dependencies = [ + "error-code", +] + [[package]] name = "cmov" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" +[[package]] +name = "codespan-reporting" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "color" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ec7c5eb7a16992b1904d76c517d170ab353b0e0b3d5a0c81a8a0cd1037893cf" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -555,6 +969,30 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + [[package]] name = "cpubits" version = "0.1.1" @@ -594,6 +1032,12 @@ version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.7.5" @@ -671,6 +1115,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + [[package]] name = "curve25519-dalek" version = "5.0.0" @@ -802,12 +1252,54 @@ dependencies = [ "ctutils", ] +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "libc", + "objc2 0.6.5", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "downcast-rs" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + [[package]] name = "ecdsa" version = "0.17.0" @@ -823,6 +1315,17 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ecolor" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0656a7530b9e2841ee7a5f983965bdd9840c32f4012f759cfd44b912cced6b34" +dependencies = [ + "bytemuck", + "emath", + "serde", +] + [[package]] name = "ed25519" version = "3.0.0" @@ -849,6 +1352,135 @@ dependencies = [ "zeroize", ] +[[package]] +name = "eframe" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ad41fac6e3149abb4866e93df37dbd33baa95db525c3368c944584e95a5dc2" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "egui-wgpu", + "egui-winit", + "egui_glow", + "glow", + "glutin", + "glutin-winit", + "image", + "js-sys", + "log", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-foundation 0.3.2", + "parking_lot", + "percent-encoding", + "profiling", + "raw-window-handle", + "ron", + "serde", + "static_assertions", + "wasm-bindgen", + "web-sys", + "web-time", + "windows-sys 0.61.2", + "winit", +] + +[[package]] +name = "egui" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc938cc27cd911415e1e4d151bc56ff9df063d8db081374e87aeb805ea74b2ba" +dependencies = [ + "accesskit", + "ahash", + "bitflags 2.13.2", + "emath", + "epaint", + "itertools", + "log", + "nohash-hasher", + "profiling", + "ron", + "serde", + "smallvec", + "unicode-segmentation", + "web-sys", +] + +[[package]] +name = "egui-phosphor" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4299551488dddaa8f757727e1eb4ffdaf277ffe6e30b413403468f5ecf4634" +dependencies = [ + "egui", +] + +[[package]] +name = "egui-wgpu" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cef1f6d1bba40b09a4d0d62cb822b81030922cfbf65c5373398c3a3caafc5bd" +dependencies = [ + "ahash", + "bytemuck", + "document-features", + "egui", + "epaint", + "log", + "profiling", + "thiserror 2.0.21", + "type-map", + "web-time", + "wgpu", + "winit", +] + +[[package]] +name = "egui-winit" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98466000559d66ba4db786a30448b01483946add4188e7f10f522a27b4fb89cc" +dependencies = [ + "accesskit_winit", + "arboard", + "bytemuck", + "egui", + "log", + "objc2 0.6.5", + "objc2-foundation 0.3.2", + "objc2-ui-kit 0.3.2", + "profiling", + "raw-window-handle", + "serde", + "smithay-clipboard", + "web-time", + "winit", +] + +[[package]] +name = "egui_glow" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "606ed4167daf341e5753d703285c4c1d562324b2c07aa8f405cd7d066fe58f3b" +dependencies = [ + "bytemuck", + "egui", + "glow", + "log", + "profiling", + "winit", +] + +[[package]] +name = "either" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be" + [[package]] name = "elliptic-curve" version = "0.14.1" @@ -871,6 +1503,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "emath" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c8d4141cf0f60fb2f7aefaf352d3ffa5171ef0ab09731c64391801e95e48f1" +dependencies = [ + "bytemuck", + "serde", +] + [[package]] name = "endi" version = "1.1.1" @@ -910,6 +1552,49 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "enumn" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f9ed6b3789237c8a0c1c505af1c7eb2c560df6186f01b098c3a1064ea532f38" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "epaint" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0ec0308dc23130fc1a7d4623a8534c2ed069c6dc84e258ee4f3de99615aa1a3" +dependencies = [ + "ahash", + "bytemuck", + "ecolor", + "emath", + "epaint_default_fonts", + "font-types", + "harfrust", + "log", + "nohash-hasher", + "parking_lot", + "profiling", + "self_cell", + "serde", + "skrifa", + "smallvec", + "unicode-general-category", + "unicode-segmentation", + "vello_cpu", +] + +[[package]] +name = "epaint_default_fonts" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773fa9c96dd0dbef887e39d0ed6177f141cce4d68e6041df77570aa3702dfa13" + [[package]] name = "equivalent" version = "1.0.2" @@ -926,6 +1611,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "error-code" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5343afd4a8365a643ac588dab4cf234a190c7f6c88c9f6dd6ffe00837661b7" + +[[package]] +name = "euclid" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1a05365e3b1c6d1650318537c7460c6923f1abdd272ad6842baa2b509957a06" +dependencies = [ + "num-traits", +] + [[package]] name = "event-listener" version = "5.4.2" @@ -964,6 +1664,27 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" +[[package]] +name = "fax" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caf1079563223d5d59d83c85886a56e586cfd5c1a26292e971a0fa266531ac5a" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "fearless_simd" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97b65636e5b9ef369943878ac74335ba1c55c1cb6adbf1e2c293c624248d693" + [[package]] name = "ff" version = "0.14.0" @@ -1004,7 +1725,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.9.1", "zlib-rs", ] @@ -1014,6 +1735,49 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "font-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c92d9081c7ab3bd83ef1eecdfad090f3d966e3ce74106779664114cfe1a6274" +dependencies = [ + "bytemuck", + "serde", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.7", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + [[package]] name = "futures" version = "0.3.34" @@ -1136,6 +1900,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.5", + "windows-link", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -1147,6 +1921,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + [[package]] name = "getrandom" version = "0.4.3" @@ -1156,7 +1942,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "r-efi", + "r-efi 6.0.0", "rand_core 0.10.1", "wasm-bindgen", ] @@ -1181,6 +1967,33 @@ dependencies = [ "zeroize", ] +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glifo" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed4a1bb24121291d27230c1b1b44e07d6a9b28cefdb32fe1581dfb84e14f940a" +dependencies = [ + "bytemuck", + "foldhash 0.2.0", + "hashbrown 0.17.1", + "log", + "peniko", + "skrifa", + "smallvec", + "vello_common", +] + [[package]] name = "gloo-timers" version = "0.4.0" @@ -1193,6 +2006,84 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "glow" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" +dependencies = [ + "js-sys", + "slotmap", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "glutin" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12124de845cacfebedff80e877bb37b5b75c34c5a4c89e47e1cdd67fb6041325" +dependencies = [ + "bitflags 2.13.2", + "cfg_aliases 0.2.2", + "cgl", + "dispatch2", + "glutin_egl_sys", + "glutin_glx_sys", + "glutin_wgl_sys", + "libloading", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "once_cell", + "raw-window-handle", + "wayland-sys", + "windows-sys 0.52.0", + "x11-dl", +] + +[[package]] +name = "glutin-winit" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85edca7075f8fc728f28cb8fbb111a96c3b89e930574369e3e9c27eb75d3788f" +dependencies = [ + "cfg_aliases 0.2.2", + "glutin", + "raw-window-handle", + "winit", +] + +[[package]] +name = "glutin_egl_sys" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c4680ba6195f424febdc3ba46e7a42a0e58743f2edb115297b86d7f8ecc02d2" +dependencies = [ + "gl_generator", + "windows-sys 0.52.0", +] + +[[package]] +name = "glutin_glx_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7bb2938045a88b612499fbcba375a77198e01306f52272e692f8c1f3751185" +dependencies = [ + "gl_generator", + "x11-dl", +] + +[[package]] +name = "glutin_wgl_sys" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c4ee00b289aba7a9e5306d57c2d05499b2e5dc427f84ac708bd2c090212cf3e" +dependencies = [ + "gl_generator", +] + [[package]] name = "group" version = "0.14.0" @@ -1204,6 +2095,39 @@ dependencies = [ "subtle", ] +[[package]] +name = "guillotiere" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b17e70c989c36bad147b27a58d148c0741c51448aa5653436547323e524d0ab" +dependencies = [ + "euclid", +] + +[[package]] +name = "half" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" +dependencies = [ + "cfg-if", + "crunchy", + "num-traits", + "zerocopy", +] + +[[package]] +name = "harfrust" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03d949a14aa089bbb282f7dd76a498a7f684428e4257202efc119ec010376f9" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "read-fonts", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.14.5" @@ -1216,7 +2140,16 @@ version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", ] [[package]] @@ -1224,6 +2157,9 @@ name = "hashbrown" version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "foldhash 0.2.0", +] [[package]] name = "hashlink" @@ -1288,6 +2224,15 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "hybrid-array" version = "0.4.15" @@ -1324,6 +2269,20 @@ dependencies = [ "cc", ] +[[package]] +name = "image" +version = "0.25.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104" +dependencies = [ + "bytemuck", + "byteorder-lite", + "moxcms", + "num-traits", + "png", + "tiff", +] + [[package]] name = "indexmap" version = "2.14.2" @@ -1354,12 +2313,89 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "itertools" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b4baf93f58d4425749ca49a51c50ebab072c5df6994d08fed93541c331481dc" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.21", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + [[package]] name = "js-sys" version = "0.3.106" @@ -1404,10 +2440,28 @@ dependencies = [ "security-framework 2.11.1", "security-framework 3.7.0", "windows-sys 0.60.2", - "zbus", + "zbus 4.4.0", "zeroize", ] +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "kurbo" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b60dfc32f652b926df6192e55525b16d186c69d47876c3ead4da5cc9f8450e2" +dependencies = [ + "arrayvec", + "euclid", + "polycool", + "smallvec", +] + [[package]] name = "lazy_static" version = "1.5.1" @@ -1429,6 +2483,34 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ff90caf6077a803a240f62fdbe88645a890bbca49ef8174c3cb0404362171d" +dependencies = [ + "bitflags 2.13.2", + "libc", + "plain", + "redox_syscall 0.9.4", +] + [[package]] name = "libsqlite3-sys" version = "0.35.0" @@ -1440,12 +2522,30 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "linebender_resource_handle" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4a5ff6bcca6c4867b1c4fd4ef63e4db7436ef363e0ad7531d1558856bae64f4" + +[[package]] +name = "linux-raw-sys" +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 = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "lock_api" version = "0.4.14" @@ -1473,6 +2573,15 @@ version = "2.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + [[package]] name = "memoffset" version = "0.9.1" @@ -1482,6 +2591,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "miniz_oxide" version = "0.9.1" @@ -1503,6 +2622,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "miow" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "536bfad37a309d62069485248eeaba1e8d9853aaf951caaeaed0585a95346f08" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "ml-kem" version = "0.3.2" @@ -1528,11 +2656,98 @@ dependencies = [ "num-traits", ] +[[package]] +name = "moxcms" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b" +dependencies = [ + "num-traits", + "pxfm", +] + +[[package]] +name = "naga" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616d2fb8c89516ac2723a581f69d6c18576046bed761bd6b305e5618e6ae130" +dependencies = [ + "arrayvec", + "bit-set", + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases 0.2.2", + "codespan-reporting", + "half", + "hashbrown 0.17.1", + "indexmap", + "libm", + "log", + "naga-types", + "num-traits", + "once_cell", + "rustc-hash 1.1.0", + "thiserror 2.0.21", + "unicode-ident", +] + +[[package]] +name = "naga-types" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "590afbf58a6f4f62873cd5cff4468061844bafa1cdf399cc954537c22d768d49" +dependencies = [ + "hashbrown 0.17.1", + "indexmap", + "rustc-hash 1.1.0", + "thiserror 2.0.21", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.2", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + [[package]] name = "nexus" version = "0.1.0" dependencies = [ + "alacritty_terminal", + "eframe", + "egui", + "egui-phosphor", + "image", "nexus-core", + "regex", + "rfd", + "serde", + "serde_json", + "winresource", ] [[package]] @@ -1595,6 +2810,12 @@ dependencies = [ "libc", ] +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + [[package]] name = "num" version = "0.4.3" @@ -1677,6 +2898,314 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d5490aaf8f1d7cf7688dfa9b0ce07900e168852c45cd2c03f534dfd27cfd0b" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08849bbd4767dfae9457696856ae1c84fe4e0281bbe4a7abff2d0e06fb7981f8" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "libc", + "objc2 0.5.3", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation 0.2.2", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.5", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-core-location", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.5", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2 0.6.5", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-contacts", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "dispatch", + "libc", + "objc2 0.5.3", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.2", + "block2 0.6.2", + "objc2 0.6.5", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.5", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2 0.5.3", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation 0.2.2", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.2", + "objc2 0.6.5", + "objc2-core-foundation", + "objc2-foundation 0.3.2", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2 0.5.1", + "objc2 0.5.3", + "objc2-foundation 0.2.2", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.13.2", + "block2 0.5.1", + "objc2 0.5.3", + "objc2-core-location", + "objc2-foundation 0.2.2", ] [[package]] @@ -1691,6 +3220,16 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -1786,7 +3325,7 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link", ] @@ -1830,6 +3369,25 @@ dependencies = [ "base64ct", ] +[[package]] +name = "peniko" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "839c8299360d2e998bdb106dc0a6cd71dcc5f4df51df1b620361bf50e283cca6" +dependencies = [ + "bytemuck", + "color", + "kurbo", + "linebender_resource_handle", + "smallvec", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "phc" version = "0.6.1" @@ -1840,6 +3398,69 @@ dependencies = [ "ctutils", ] +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1902,6 +3523,25 @@ version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + [[package]] name = "polling" version = "3.11.0" @@ -1912,10 +3552,16 @@ dependencies = [ "concurrent-queue", "hermit-abi", "pin-project-lite", - "rustix", + "rustix 1.1.5", "windows-sys 0.61.2", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "poly1305" version = "0.9.1" @@ -1927,6 +3573,15 @@ dependencies = [ "zeroize", ] +[[package]] +name = "polycool" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50596ddc09eb5ad5f75cacd40209568e66df71baf86e1499a0e99c4cff12a5a6" +dependencies = [ + "arrayvec", +] + [[package]] name = "polyval" version = "0.6.2" @@ -1951,6 +3606,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + [[package]] name = "portable-pty" version = "0.9.0" @@ -2026,6 +3696,33 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" + +[[package]] +name = "pxfm" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quick-xml" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e660451e55124f798a69a5af3f49ccfbefbd41910eefd25caf2393e1f3473ec1" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.47" @@ -2035,6 +3732,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -2088,6 +3791,32 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "read-fonts" +version = "0.41.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046a7d674daf459825b32f5062056d6882db0d2f5a479fbd76ccfc870ac18709" +dependencies = [ + "bytemuck", + "font-types", + "once_cell", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -2097,6 +3826,50 @@ dependencies = [ "bitflags 2.13.2", ] +[[package]] +name = "redox_syscall" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737970939a87c6fa31e7acad13307bccbb017a073b695b6089a2c484f929e20e" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "renderdoc-sys" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" + [[package]] name = "rfc6979" version = "0.6.0" @@ -2107,6 +3880,33 @@ dependencies = [ "hmac 0.13.0", ] +[[package]] +name = "rfd" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20dafead71c16a34e1ff357ddefc8afc11e7d51d6d2b9fbd07eaa48e3e540220" +dependencies = [ + "block2 0.6.2", + "dispatch2", + "js-sys", + "libc", + "log", + "objc2 0.6.5", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "percent-encoding", + "pollster", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "web-sys", + "windows-sys 0.61.2", +] + [[package]] name = "ring" version = "0.17.14" @@ -2121,6 +3921,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ron" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81116b9531d61eabc41aeb228e4b6b2435bcca3233b98cf3b3077d4e6e9debb3" +dependencies = [ + "bitflags 2.13.2", + "once_cell", + "serde", + "serde_derive", + "typeid", + "unicode-ident", +] + [[package]] name = "rsa" version = "0.10.0-rc.18" @@ -2269,6 +4083,18 @@ dependencies = [ "wasm-bindgen-futures", ] +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -2278,6 +4104,19 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + [[package]] name = "rustix" version = "1.1.5" @@ -2287,10 +4126,21 @@ dependencies = [ "bitflags 2.13.2", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.12.1", "windows-sys 0.61.2", ] +[[package]] +name = "rustix-openpty" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1de16c7c59892b870a6336f185dc10943517f1327447096bbb7bb32cd85e2393" +dependencies = [ + "errno", + "libc", + "rustix 1.1.5", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -2307,6 +4157,21 @@ dependencies = [ "cipher 0.5.2", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "scopeguard" version = "1.2.0" @@ -2355,7 +4220,7 @@ dependencies = [ "rand 0.8.8", "serde", "sha2 0.10.9", - "zbus", + "zbus 4.4.0", ] [[package]] @@ -2394,6 +4259,12 @@ dependencies = [ "libc", ] +[[package]] +name = "self_cell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab42ca02749e120097e328d91d415325bdf43b1c72c4c8badf37375fe40a813" + [[package]] name = "semver" version = "1.0.28" @@ -2464,6 +4335,15 @@ dependencies = [ "syn 3.0.7", ] +[[package]] +name = "serde_spanned" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7523beb55eece201a2356bee0bbca0d1ab466c14c07703b2e0ee6d42cb0c2c" +dependencies = [ + "serde_core", +] + [[package]] name = "serdect" version = "0.4.3" @@ -2572,6 +4452,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b29ffa27d3d59a049bb328258de100f8012848d1b08d444e08bbc02ac468435e" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.8" @@ -2598,17 +4488,133 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "siphasher" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33f4fe9184a62d842c9ef383018f3306d8ba224fd9d836f56d7288308847c256" + +[[package]] +name = "skrifa" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819ab7d62b1d3e72d9d9dea5650bac30424f9111364bb94928dbf5ecad1baa68" +dependencies = [ + "bytemuck", + "read-fonts", +] + [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "slotmap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" +dependencies = [ + "version_check", +] + [[package]] name = "smallvec" version = "1.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" +dependencies = [ + "serde", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" +dependencies = [ + "bitflags 2.13.2", + "calloop 0.13.0", + "calloop-wayland-source 0.3.0", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 0.38.44", + "thiserror 1.0.69", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags 2.13.2", + "calloop 0.14.5", + "calloop-wayland-source 0.4.1", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.5", + "thiserror 2.0.21", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smithay-clipboard" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226" +dependencies = [ + "libc", + "smithay-client-toolkit 0.20.0", + "wayland-backend", +] + +[[package]] +name = "smol_str" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd538fb6910ac1099850255cf94a94df6551fbdd602454387d0adb2d1ca6dead" +dependencies = [ + "serde", +] [[package]] name = "socket2" @@ -2738,7 +4744,7 @@ dependencies = [ "fastrand", "getrandom 0.4.3", "once_cell", - "rustix", + "rustix 1.1.5", "windows-sys 0.61.2", ] @@ -2782,6 +4788,20 @@ dependencies = [ "syn 3.0.7", ] +[[package]] +name = "tiff" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63feaf3343d35b6ca4d50483f94843803b0f51634937cc2ec519fc32232bc52" +dependencies = [ + "fax", + "flate2", + "half", + "quick-error", + "weezl", + "zune-jpeg", +] + [[package]] name = "tokio" version = "1.53.2" @@ -2825,6 +4845,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "1.1.8+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20489e00e4d8741d6be680764cc12e270655e375a20d1011e844a9c3379e678d" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "1.1.2+spec-1.1.0" @@ -2855,12 +4890,19 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06bdbd8cfc056b8d2e2e85f29b56a3bdbecb527cef81eb39e3e7b98af4652770" + [[package]] name = "tracing" version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2886,6 +4928,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "type-map" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90" +dependencies = [ + "rustc-hash 2.1.3", +] + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + [[package]] name = "typenum" version = "1.20.1" @@ -2903,12 +4960,30 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "unicode-general-category" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b993bddc193ae5bd0d623b49ec06ac3e9312875fdae725a975c51db1cc1677f" + [[package]] name = "unicode-ident" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "unicode-width" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" + [[package]] name = "universal-hash" version = "0.5.1" @@ -2943,6 +5018,7 @@ checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" dependencies = [ "getrandom 0.4.3", "js-sys", + "serde_core", "wasm-bindgen", ] @@ -2952,18 +5028,78 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vello_common" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e9aed918117e8152c9eddfd8362d73c465c23f26c44786aa331707b8a64fa2" +dependencies = [ + "bytemuck", + "fearless_simd", + "guillotiere", + "log", + "peniko", + "smallvec", + "thiserror 2.0.21", +] + +[[package]] +name = "vello_cpu" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac7349e1f55f6b801c7c277958df4ea53e7f20f21e8014910ad888b2ecda93ea" +dependencies = [ + "bytemuck", + "glifo", + "hashbrown 0.17.1", + "vello_common", +] + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vte" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5924018406ce0063cd67f8e008104968b74b563ee1b85dde3ed1f7cb87d3dbd" +dependencies = [ + "arrayvec", + "bitflags 2.13.2", + "cursor-icon", + "log", + "memchr", + "serde", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.129" @@ -3020,6 +5156,286 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "wayland-backend" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38a91b4eaddff87b1cd1074985e3713da4af2c49742d1b356b2c01670a67a078" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.5", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c36a0f861ad76d0901f2800b46321410d9f73f2ea88aac0650d86c32688073" +dependencies = [ + "bitflags 2.13.2", + "rustix 1.1.5", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags 2.13.2", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.5", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags 2.13.2", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338e30461b3a2b67d70eb30a6d89f8e0c93a833e07d2ae89085cd070c4a00ac0" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88261b9deccee56594c11a3460c462c41f58d148598fe70ad77070126a68aba4" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" + +[[package]] +name = "wgpu" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527ccdf43dd5b2e8676eed9984ce00e2bbb0a1b85b70c1969dcb6cd2eb55ab9e" +dependencies = [ + "arrayvec", + "bitflags 2.13.2", + "bytemuck", + "cfg-if", + "cfg_aliases 0.2.2", + "document-features", + "hashbrown 0.17.1", + "js-sys", + "log", + "portable-atomic", + "profiling", + "raw-window-handle", + "smallvec", + "static_assertions", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "wgpu-core", + "wgpu-hal", + "wgpu-types", +] + +[[package]] +name = "wgpu-core" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c018fce9b6270aa203c2fdd56f3cce996713534bd757e4ea58c8560b121f14" +dependencies = [ + "arrayvec", + "bit-set", + "bit-vec", + "bitflags 2.13.2", + "bytemuck", + "cfg_aliases 0.2.2", + "document-features", + "hashbrown 0.17.1", + "indexmap", + "log", + "naga", + "naga-types", + "once_cell", + "parking_lot", + "portable-atomic", + "profiling", + "raw-window-handle", + "rustc-hash 1.1.0", + "smallvec", + "thiserror 2.0.21", + "wgpu-core-deps-windows-linux-android", + "wgpu-hal", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-core-deps-windows-linux-android" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7586165fd5f6d881cb9ce4bb71f40d6caab2c0f1837e3fc1d9788a197fb6004f" +dependencies = [ + "wgpu-hal", +] + +[[package]] +name = "wgpu-hal" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b7fb58561a792bc237628ba0792e332de418fefe145f13b5ed8201e6d52f58" +dependencies = [ + "bitflags 2.13.2", + "cfg-if", + "cfg_aliases 0.2.2", + "hashbrown 0.17.1", + "libloading", + "log", + "naga", + "naga-types", + "portable-atomic", + "portable-atomic-util", + "raw-window-handle", + "renderdoc-sys", + "static_assertions", + "thiserror 2.0.21", + "wgpu-naga-bridge", + "wgpu-types", +] + +[[package]] +name = "wgpu-naga-bridge" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f62e73117bb7a62bfd9c5a5841438a823f6566c6442a808ee269d2d055c081" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "30.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99dad6f1fbdbbdb4c278a6508b059d44688f5cebddf78d005a46a31340269286" +dependencies = [ + "bitflags 2.13.2", + "bytemuck", + "js-sys", + "log", + "naga-types", + "raw-window-handle", + "static_assertions", + "web-sys", +] + [[package]] name = "winapi" version = "0.3.9" @@ -3036,6 +5452,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -3317,6 +5742,57 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winit" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6755fa58a9f8350bd1e472d4c3fcc25f824ec358933bba33306d0b63df5978d" +dependencies = [ + "ahash", + "android-activity", + "atomic-waker", + "bitflags 2.13.2", + "block2 0.5.1", + "bytemuck", + "calloop 0.13.0", + "cfg_aliases 0.2.2", + "concurrent-queue", + "core-foundation 0.9.4", + "core-graphics", + "cursor-icon", + "dpi", + "js-sys", + "libc", + "memmap2", + "ndk", + "objc2 0.5.3", + "objc2-app-kit 0.2.2", + "objc2-foundation 0.2.2", + "objc2-ui-kit 0.2.2", + "orbclient", + "percent-encoding", + "pin-project", + "raw-window-handle", + "redox_syscall 0.4.1", + "rustix 0.38.44", + "smithay-client-toolkit 0.19.2", + "smol_str", + "tracing", + "unicode-segmentation", + "wasm-bindgen", + "wasm-bindgen-futures", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "web-sys", + "web-time", + "windows-sys 0.52.0", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + [[package]] name = "winnow" version = "1.0.4" @@ -3335,6 +5811,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "winresource" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0986a8b1d586b7d3e4fe3d9ea39fb451ae22869dcea4aa109d287a374d866087" +dependencies = [ + "toml", + "version_check", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + [[package]] name = "wnaf" version = "0.14.1" @@ -3347,6 +5839,44 @@ dependencies = [ "primefield", ] +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.5", + "x11rb-protocol", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "163b33ed8786455e2fa5d72f554057ce3f3182425434f756cd39c99839d88e23" + [[package]] name = "xdg-home" version = "1.3.0" @@ -3357,6 +5887,31 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags 2.13.2", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7" + [[package]] name = "zbus" version = "4.4.0" @@ -3391,9 +5946,68 @@ dependencies = [ "uds_windows", "windows-sys 0.52.0", "xdg-home", - "zbus_macros", - "zbus_names", - "zvariant", + "zbus_macros 4.4.0", + "zbus_names 3.0.0", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5db4be7c075cb421e4b7ee645541604239bd243ba7c357511f4ff3a74b555907" +dependencies = [ + "async-broadcast", + "async-executor", + "async-io", + "async-lock", + "async-process", + "async-recursion", + "async-task", + "async-trait", + "blocking", + "enumflags2", + "event-listener", + "futures-core", + "futures-lite", + "hex", + "libc", + "ordered-stream", + "rustix 1.1.5", + "serde", + "serde_repr", + "tracing", + "uds_windows", + "uuid", + "windows-sys 0.61.2", + "winnow", + "zbus_macros 5.19.0", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus-lockstep" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998de05217a084b7578728a9443d04ea4cd80f2a0839b8d78770b76ccd45863" +dependencies = [ + "zbus_xml", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus-lockstep-macros" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10da05367f3a7b7553c8cdf8fa91aee6b64afebe32b51c95177957efc47ca3a0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "zbus-lockstep", + "zbus_xml", + "zvariant 5.15.0", ] [[package]] @@ -3406,7 +6020,22 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zbus_macros" +version = "5.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2990635d09ade6df1868f72f8cac69a876a90981e8bd3c40b1be413f8dc88f40" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.7", + "zbus_names 4.3.4", + "zvariant 5.15.0", + "zvariant_utils 4.2.0", ] [[package]] @@ -3417,7 +6046,39 @@ checksum = "4b9b1fef7d021261cc16cba64c351d291b715febe0fa10dc3a443ac5a5022e6c" dependencies = [ "serde", "static_assertions", - "zvariant", + "zvariant 4.2.0", +] + +[[package]] +name = "zbus_names" +version = "4.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bf88b4a3ff53e883001e0e0115b297a9d53c31b9c1edd2bfdd853e3428624e" +dependencies = [ + "serde", + "winnow", + "zvariant 5.15.0", +] + +[[package]] +name = "zbus_xml" +version = "5.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1586c021a01ca0a9216dcd874e546382e156a5cbab5fab6cb5f10087e22682a" +dependencies = [ + "serde", + "winnow", + "zbus_names 4.3.4", + "zvariant 5.15.0", +] + +[[package]] +name = "zcheapstr" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1afec51604565183aeb5c54c20aeab286120d4e4460f7f76e3e8bb8c0d99473" +dependencies = [ + "serde", ] [[package]] @@ -3472,6 +6133,21 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" +[[package]] +name = "zune-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56377fd46368984a170bc5aac5567e52ca5da874caa60bea39fcbca78fb658b" + +[[package]] +name = "zune-jpeg" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296" +dependencies = [ + "zune-core", +] + [[package]] name = "zvariant" version = "4.2.0" @@ -3482,7 +6158,22 @@ dependencies = [ "enumflags2", "serde", "static_assertions", - "zvariant_derive", + "zvariant_derive 4.2.0", +] + +[[package]] +name = "zvariant" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1d34c27cc6cdd1f458427519dd6b8612f7b7e3f7b9a0b2355d041dda9869147" +dependencies = [ + "endi", + "enumflags2", + "serde", + "winnow", + "zcheapstr", + "zvariant_derive 5.15.0", + "zvariant_utils 4.2.0", ] [[package]] @@ -3495,7 +6186,20 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.119", - "zvariant_utils", + "zvariant_utils 2.1.0", +] + +[[package]] +name = "zvariant_derive" +version = "5.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "864155e69b4352db0c7f374917bf45d1e0c8d17659c8b3dbf9795f3673f8c497" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 3.0.7", + "zvariant_utils 4.2.0", ] [[package]] @@ -3508,3 +6212,16 @@ dependencies = [ "quote", "syn 2.0.119", ] + +[[package]] +name = "zvariant_utils" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad0294361a320b694a328460dc73add56c306150f5cb6bfafc44446120008a3" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "syn 3.0.7", + "winnow", +] diff --git a/rust/app/Cargo.toml b/rust/app/Cargo.toml index f2783e9..fb60451 100644 --- a/rust/app/Cargo.toml +++ b/rust/app/Cargo.toml @@ -2,6 +2,23 @@ name = "nexus" version.workspace = true edition.workspace = true +build = "build.rs" + +[[bin]] +name = "nexus-terminal" +path = "src/main.rs" [dependencies] nexus-core = { path = "../core" } +eframe = { version = "0.36", default-features = false, features = ["default_fonts", "glow", "wayland", "x11", "accesskit", "persistence"] } +egui = "0.36" +egui-phosphor = "0.14" +rfd = "0.17" +image = { version = "0.25", default-features = false, features = ["png"] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +regex = "1" +alacritty_terminal = "0.26.0" + +[build-dependencies] +winresource = "0.1" diff --git a/rust/app/build.rs b/rust/app/build.rs new file mode 100644 index 0000000..04f2ece --- /dev/null +++ b/rust/app/build.rs @@ -0,0 +1,14 @@ +//! Embeds the icon and version info into the Windows executable. +fn main() { + println!("cargo:rerun-if-changed=../packaging/windows/nexus.ico"); + if std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("windows") { + let mut res = winresource::WindowsResource::new(); + if std::path::Path::new("../packaging/windows/nexus.ico").exists() { + res.set_icon("../packaging/windows/nexus.ico"); + } + res.set("ProductName", "Nexus Terminal").set("FileDescription", "Nexus Terminal"); + if let Err(e) = res.compile() { + println!("cargo:warning=Windows icon not embedded: {e}"); + } + } +} diff --git a/rust/app/src/terminal_themes.rs b/rust/app/src/terminal_themes.rs new file mode 100644 index 0000000..f14367a --- /dev/null +++ b/rust/app/src/terminal_themes.rs @@ -0,0 +1,130 @@ +//! The 11 terminal colour schemes, generated from the web build's themes.js. + +pub struct TermTheme { + pub id: &'static str, + pub label: &'static str, + pub background: [u8; 4], + pub foreground: [u8; 4], + pub cursor: [u8; 4], + pub cursor_accent: [u8; 4], + pub selection: [u8; 4], + /// black, red, green, yellow, blue, magenta, cyan, white, then the bright eight. + pub ansi: [[u8; 4]; 16], +} + +pub const THEMES: &[TermTheme] = &[ + TermTheme { + id: "charcoal", + label: "Charcoal (Default)", + background: [28, 28, 28, 255], + foreground: [228, 228, 228, 255], + cursor: [78, 205, 196, 255], + cursor_accent: [28, 28, 28, 255], + selection: [78, 205, 196, 76], + ansi: [[28, 28, 28, 255], [239, 83, 80, 255], [102, 187, 106, 255], [255, 167, 38, 255], [66, 165, 245, 255], [171, 71, 188, 255], [78, 205, 196, 255], [228, 228, 228, 255], [85, 85, 85, 255], [255, 112, 67, 255], [129, 199, 132, 255], [255, 204, 2, 255], [100, 181, 246, 255], [206, 147, 216, 255], [128, 222, 234, 255], [255, 255, 255, 255]], + }, + TermTheme { + id: "solarized-dark", + label: "Solarized Dark", + background: [0, 43, 54, 255], + foreground: [131, 148, 150, 255], + cursor: [147, 161, 161, 255], + cursor_accent: [0, 43, 54, 255], + selection: [147, 161, 161, 76], + ansi: [[7, 54, 66, 255], [220, 50, 47, 255], [133, 153, 0, 255], [181, 137, 0, 255], [38, 139, 210, 255], [211, 54, 130, 255], [42, 161, 152, 255], [238, 232, 213, 255], [88, 110, 117, 255], [203, 75, 22, 255], [88, 110, 117, 255], [101, 123, 131, 255], [131, 148, 150, 255], [108, 113, 196, 255], [147, 161, 161, 255], [253, 246, 227, 255]], + }, + TermTheme { + id: "monokai", + label: "Monokai", + background: [39, 40, 34, 255], + foreground: [248, 248, 242, 255], + cursor: [248, 248, 240, 255], + cursor_accent: [39, 40, 34, 255], + selection: [73, 72, 62, 153], + ansi: [[39, 40, 34, 255], [249, 38, 114, 255], [166, 226, 46, 255], [244, 191, 117, 255], [102, 217, 239, 255], [174, 129, 255, 255], [161, 239, 228, 255], [248, 248, 242, 255], [117, 113, 94, 255], [249, 38, 114, 255], [166, 226, 46, 255], [244, 191, 117, 255], [102, 217, 239, 255], [174, 129, 255, 255], [161, 239, 228, 255], [249, 248, 245, 255]], + }, + TermTheme { + id: "nord", + label: "Nord", + background: [46, 52, 64, 255], + foreground: [216, 222, 233, 255], + cursor: [216, 222, 233, 255], + cursor_accent: [46, 52, 64, 255], + selection: [136, 192, 208, 76], + ansi: [[59, 66, 82, 255], [191, 97, 106, 255], [163, 190, 140, 255], [235, 203, 139, 255], [129, 161, 193, 255], [180, 142, 173, 255], [136, 192, 208, 255], [229, 233, 240, 255], [76, 86, 106, 255], [191, 97, 106, 255], [163, 190, 140, 255], [235, 203, 139, 255], [129, 161, 193, 255], [180, 142, 173, 255], [143, 188, 187, 255], [236, 239, 244, 255]], + }, + TermTheme { + id: "dracula", + label: "Dracula", + background: [40, 42, 54, 255], + foreground: [248, 248, 242, 255], + cursor: [248, 248, 242, 255], + cursor_accent: [40, 42, 54, 255], + selection: [68, 71, 90, 153], + ansi: [[33, 34, 44, 255], [255, 85, 85, 255], [80, 250, 123, 255], [241, 250, 140, 255], [189, 147, 249, 255], [255, 121, 198, 255], [139, 233, 253, 255], [248, 248, 242, 255], [98, 114, 164, 255], [255, 110, 110, 255], [105, 255, 148, 255], [255, 255, 165, 255], [214, 172, 255, 255], [255, 146, 223, 255], [164, 255, 255, 255], [255, 255, 255, 255]], + }, + TermTheme { + id: "one-dark", + label: "One Dark", + background: [40, 44, 52, 255], + foreground: [171, 178, 191, 255], + cursor: [82, 139, 255, 255], + cursor_accent: [40, 44, 52, 255], + selection: [82, 139, 255, 76], + ansi: [[40, 44, 52, 255], [224, 108, 117, 255], [152, 195, 121, 255], [229, 192, 123, 255], [97, 175, 239, 255], [198, 120, 221, 255], [86, 182, 194, 255], [171, 178, 191, 255], [92, 99, 112, 255], [224, 108, 117, 255], [152, 195, 121, 255], [229, 192, 123, 255], [97, 175, 239, 255], [198, 120, 221, 255], [86, 182, 194, 255], [255, 255, 255, 255]], + }, + TermTheme { + id: "gruvbox-dark", + label: "Gruvbox Dark", + background: [40, 40, 40, 255], + foreground: [235, 219, 178, 255], + cursor: [235, 219, 178, 255], + cursor_accent: [40, 40, 40, 255], + selection: [214, 93, 14, 76], + ansi: [[40, 40, 40, 255], [204, 36, 29, 255], [152, 151, 26, 255], [215, 153, 33, 255], [69, 133, 136, 255], [177, 98, 134, 255], [104, 157, 106, 255], [168, 153, 132, 255], [146, 131, 116, 255], [251, 73, 52, 255], [184, 187, 38, 255], [250, 189, 47, 255], [131, 165, 152, 255], [211, 134, 155, 255], [142, 192, 124, 255], [235, 219, 178, 255]], + }, + TermTheme { + id: "tokyo-night", + label: "Tokyo Night", + background: [26, 27, 38, 255], + foreground: [169, 177, 214, 255], + cursor: [192, 202, 245, 255], + cursor_accent: [26, 27, 38, 255], + selection: [41, 46, 66, 230], + ansi: [[21, 22, 30, 255], [247, 118, 142, 255], [158, 206, 106, 255], [224, 175, 104, 255], [122, 162, 247, 255], [187, 154, 247, 255], [125, 207, 255, 255], [169, 177, 214, 255], [65, 72, 104, 255], [247, 118, 142, 255], [158, 206, 106, 255], [224, 175, 104, 255], [122, 162, 247, 255], [187, 154, 247, 255], [125, 207, 255, 255], [192, 202, 245, 255]], + }, + TermTheme { + id: "catppuccin-mocha", + label: "Catppuccin Mocha", + background: [30, 30, 46, 255], + foreground: [205, 214, 244, 255], + cursor: [245, 224, 220, 255], + cursor_accent: [30, 30, 46, 255], + selection: [88, 91, 112, 128], + ansi: [[69, 71, 90, 255], [243, 139, 168, 255], [166, 227, 161, 255], [249, 226, 175, 255], [137, 180, 250, 255], [245, 194, 231, 255], [148, 226, 213, 255], [186, 194, 222, 255], [88, 91, 112, 255], [243, 139, 168, 255], [166, 227, 161, 255], [249, 226, 175, 255], [137, 180, 250, 255], [245, 194, 231, 255], [148, 226, 213, 255], [166, 173, 200, 255]], + }, + TermTheme { + id: "rose-pine", + label: "Rosé Pine", + background: [25, 23, 36, 255], + foreground: [224, 222, 244, 255], + cursor: [82, 79, 103, 255], + cursor_accent: [224, 222, 244, 255], + selection: [110, 106, 134, 102], + ansi: [[38, 35, 58, 255], [235, 111, 146, 255], [49, 116, 143, 255], [246, 193, 119, 255], [156, 207, 216, 255], [196, 167, 231, 255], [235, 188, 186, 255], [224, 222, 244, 255], [110, 106, 134, 255], [235, 111, 146, 255], [49, 116, 143, 255], [246, 193, 119, 255], [156, 207, 216, 255], [196, 167, 231, 255], [235, 188, 186, 255], [224, 222, 244, 255]], + }, + TermTheme { + id: "solarized-light", + label: "Solarized Light", + background: [253, 246, 227, 255], + foreground: [101, 123, 131, 255], + cursor: [88, 110, 117, 255], + cursor_accent: [253, 246, 227, 255], + selection: [7, 54, 66, 51], + ansi: [[7, 54, 66, 255], [220, 50, 47, 255], [133, 153, 0, 255], [181, 137, 0, 255], [38, 139, 210, 255], [211, 54, 130, 255], [42, 161, 152, 255], [238, 232, 213, 255], [0, 43, 54, 255], [203, 75, 22, 255], [88, 110, 117, 255], [101, 123, 131, 255], [131, 148, 150, 255], [108, 113, 196, 255], [147, 161, 161, 255], [253, 246, 227, 255]], + }, +]; + +pub fn get(id: &str) -> &'static TermTheme { + THEMES.iter().find(|t| t.id == id).unwrap_or(&THEMES[0]) +} From d20d16e8c83800ffc08f2b174b44adbf95bf1b55 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:21:08 +0100 Subject: [PATCH 11/45] rust: port GitHub and IRC clients, IRC formatting --- rust/Cargo.lock | 213 ++++- rust/core/Cargo.toml | 6 + rust/core/src/clients/github.rs | 555 +++++++++++++ rust/core/src/clients/irc.rs | 1337 +++++++++++++++++++++++++++++++ rust/core/src/clients/mod.rs | 3 + rust/core/src/ircformat.rs | 469 +++++++++++ rust/core/src/lib.rs | 1 + 7 files changed, 2582 insertions(+), 2 deletions(-) create mode 100644 rust/core/src/clients/github.rs create mode 100644 rust/core/src/clients/irc.rs create mode 100644 rust/core/src/ircformat.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index cb8cc7d..14527ee 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -58,6 +64,12 @@ dependencies = [ "password-hash", ] +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "async-broadcast" version = "0.7.2" @@ -218,6 +230,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + [[package]] name = "base64ct" version = "1.8.3" @@ -333,6 +351,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + [[package]] name = "cipher" version = "0.4.4" @@ -387,6 +411,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.23" @@ -540,6 +573,17 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + [[package]] name = "foldhash" version = "0.1.5" @@ -706,6 +750,28 @@ dependencies = [ "digest", ] +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -837,6 +903,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.4" @@ -861,14 +937,18 @@ version = "0.1.0" dependencies = [ "aes-gcm", "argon2", - "base64", + "base64 0.22.1", "chrono", "keyring", "rand", "rusqlite", + "rustls", "serde", "serde_json", + "tiny_http", + "ureq", "uuid", + "webpki-roots", ] [[package]] @@ -995,6 +1075,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1116,6 +1202,20 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rusqlite" version = "0.37.0" @@ -1143,6 +1243,41 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1296,6 +1431,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "slab" version = "0.4.12" @@ -1315,7 +1456,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -1365,6 +1506,18 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + [[package]] name = "tokio" version = "1.53.2" @@ -1475,6 +1628,47 @@ dependencies = [ "subtle", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a7ac20be9b7726e0bbdbf974c059676d9acb1cd414961f570a4e8231cacd7fc" +dependencies = [ + "base64 0.23.1", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f86fd172ccca569e458f61b6bdd6220965a9ef36e672a6852953b51a0e1583be" +dependencies = [ + "base64 0.23.1", + "http", + "httparse", + "log", +] + +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "uuid" version = "1.28.0" @@ -1549,6 +1743,15 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -1895,6 +2098,12 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + [[package]] name = "zmij" version = "1.0.23" diff --git a/rust/core/Cargo.toml b/rust/core/Cargo.toml index a3c8903..eae0693 100644 --- a/rust/core/Cargo.toml +++ b/rust/core/Cargo.toml @@ -14,3 +14,9 @@ aes-gcm = "0.10" rand = "0.8" base64 = "0.22" keyring = { version = "3", features = ["apple-native", "windows-native", "async-secret-service", "tokio", "crypto-rust"] } +ureq = "3" +rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } +webpki-roots = "1" + +[dev-dependencies] +tiny_http = "0.12" diff --git a/rust/core/src/clients/github.rs b/rust/core/src/clients/github.rs new file mode 100644 index 0000000..462e7a5 --- /dev/null +++ b/rust/core/src/clients/github.rs @@ -0,0 +1,555 @@ +//! Lightweight GitHub REST client for repository metadata and archive downloads. Works with +//! github.com and GitHub Enterprise via a configurable API base URL. + +use std::io::{Read, Write}; +use std::path::Path; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::time::Duration; + +use chrono::{DateTime, FixedOffset, SecondsFormat}; +use serde::Deserialize; + +use crate::models::GitHubRepo; + +const MAX_JSON_BODY_BYTES: u64 = 4 << 20; +const MAX_ERROR_BODY_BYTES: u64 = 64 << 10; +const MAX_ARCHIVE_BYTES: u64 = 2 << 30; +const USER_AGENT: &str = "NexusTerminal/1.0"; +const COMMIT_LOOKUP_WORKERS: usize = 5; + +pub struct Client { + base_url: String, + token: String, + agent: ureq::Agent, +} + +#[derive(Deserialize)] +struct ApiRepo { + #[serde(default)] + full_name: Option, + #[serde(default)] + name: Option, + #[serde(default)] + description: Option, + #[serde(default)] + html_url: Option, + #[serde(default)] + default_branch: Option, + #[serde(default)] + private: Option, + #[serde(default)] + fork: Option, + #[serde(default)] + archived: Option, + #[serde(default)] + language: Option, + #[serde(default)] + stargazers_count: Option, + #[serde(default)] + pushed_at: Option, + #[serde(default)] + updated_at: Option, + #[serde(default)] + owner: Option, +} + +#[derive(Deserialize)] +struct ApiOwner { + #[serde(default)] + login: Option, +} + +#[derive(Deserialize, Default)] +struct ApiPerson { + #[serde(default)] + name: Option, + #[serde(default)] + date: Option, +} + +impl Client { + /// `base_url` is the API root, e.g. `https://api.github.com` or `https://ghe.example.com/api/v3`. + pub fn new(base_url: &str, token: &str) -> Client { + let agent = ureq::Agent::config_builder() + .timeout_global(Some(Duration::from_secs(30))) + .http_status_as_error(false) + .build() + .into(); + Client { base_url: base_url.trim_end_matches('/').to_string(), token: token.to_string(), agent } + } + + fn get(&self, url: &str) -> Result, ureq::Error> { + self.agent + .get(url) + .header("Authorization", &format!("Bearer {}", self.token)) + .header("Accept", "application/vnd.github+json") + .header("User-Agent", USER_AGENT) + .call() + } + + /// Confirms the token works and returns the authenticated login. + pub fn validate_token(&self) -> crate::Result { + let mut resp = self + .get(&format!("{}/user", self.base_url)) + .map_err(|e| format!("contacting GitHub API: {e}"))?; + let status = resp.status().as_u16(); + if status != 200 { + return Err(format!("token validation failed (HTTP {status})").into()); + } + let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES) + .map_err(|e| format!("reading user response: {e}"))?; + #[derive(Deserialize)] + struct User { + #[serde(default)] + login: Option, + } + let user: User = serde_json::from_slice(&body).map_err(|e| format!("decoding user: {e}"))?; + Ok(user.login.unwrap_or_default()) + } + + /// All repositories visible to the token, newest push first, enriched with latest-commit info. + pub fn fetch_repos(&self) -> crate::Result> { + self.fetch_repos_with_progress(|_, _| {}) + } + + /// Like `fetch_repos`, reporting commit-enrichment progress as `(done, total)`; it is called + /// from worker threads. + pub fn fetch_repos_with_progress( + &self, + progress: impl Fn(usize, usize) + Sync, + ) -> crate::Result> { + let mut all: Vec = Vec::new(); + let mut page = 1; + loop { + let url = format!( + "{}/user/repos?per_page=100&page={page}&sort=pushed&direction=desc", + self.base_url + ); + let mut resp = self.get(&url).map_err(|e| format!("fetching repos page {page}: {e}"))?; + let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES) + .map_err(|e| format!("reading response body: {e}"))?; + let status = resp.status().as_u16(); + if status == 401 || status == 403 { + return Err( + format!("authentication failed (HTTP {status}) — check your access token").into() + ); + } + if status != 200 { + return Err(format!( + "GitHub API returned HTTP {status}: {}", + String::from_utf8_lossy(&body) + ) + .into()); + } + let batch: Vec = + serde_json::from_slice(&body).map_err(|e| format!("decoding repos: {e}"))?; + if batch.is_empty() { + break; + } + let short = batch.len() < 100; + all.extend(batch); + // Safety cap — 5000 repos should be more than enough. + if all.len() >= 5000 || short { + break; + } + page += 1; + } + + let mut parsed = Vec::with_capacity(all.len()); + for r in all { + let pushed = parse_time(r.pushed_at.as_deref()).map_err(|e| format!("decoding repos: {e}"))?; + let updated = parse_time(r.updated_at.as_deref()).map_err(|e| format!("decoding repos: {e}"))?; + parsed.push((r, pushed, updated)); + } + parsed.sort_by(|a, b| b.1.cmp(&a.1)); + + let mut result: Vec = parsed + .into_iter() + .map(|(r, pushed, updated)| GitHubRepo { + full_name: r.full_name.unwrap_or_default(), + name: r.name.unwrap_or_default(), + owner: r.owner.and_then(|o| o.login).unwrap_or_default(), + description: r.description.unwrap_or_default(), + html_url: r.html_url.unwrap_or_default(), + default_branch: r.default_branch.unwrap_or_default(), + private: r.private.unwrap_or_default(), + fork: r.fork.unwrap_or_default(), + archived: r.archived.unwrap_or_default(), + language: r.language.unwrap_or_default(), + stars: r.stargazers_count.unwrap_or_default(), + pushed_at: format_time(pushed), + updated_at: format_time(updated), + ..Default::default() + }) + .collect(); + + let total = result.len(); + progress(0, total); + + // Enrich with latest commit metadata (best-effort), five lookups at a time. + let next = AtomicUsize::new(0); + let done = AtomicUsize::new(0); + let lookups: Vec<(usize, String, String)> = result + .iter() + .enumerate() + .map(|(i, r)| { + let reference = if r.default_branch.is_empty() { "HEAD" } else { &r.default_branch }; + (i, r.full_name.clone(), reference.to_string()) + }) + .collect(); + let found: Vec<(usize, String, String)> = std::thread::scope(|s| { + let workers: Vec<_> = (0..COMMIT_LOOKUP_WORKERS.min(total)) + .map(|_| { + s.spawn(|| { + let mut out = Vec::new(); + loop { + let i = next.fetch_add(1, Ordering::SeqCst); + let Some((idx, name, reference)) = lookups.get(i) else { break }; + if let Ok((who, when)) = self.fetch_latest_commit(name, reference) { + out.push((*idx, who, when)); + } + progress(done.fetch_add(1, Ordering::SeqCst) + 1, total); + } + out + }) + }) + .collect(); + workers.into_iter().flat_map(|w| w.join().unwrap_or_default()).collect() + }); + for (idx, who, when) in found { + result[idx].latest_committer = who; + result[idx].latest_commit_at = when; + } + Ok(result) + } + + /// Committer name and RFC 3339 commit time for a repo and ref (`HEAD` when empty). + pub fn fetch_latest_commit(&self, full_name: &str, reference: &str) -> crate::Result<(String, String)> { + let reference = if reference.is_empty() { "HEAD" } else { reference }; + let (owner, repo) = split_repo_full_name(full_name)?; + let url = self.endpoint_url(&["repos", &owner, &repo, "commits", reference])?; + let mut resp = self.get(&url).map_err(|e| format!("fetching commit: {e}"))?; + let status = resp.status().as_u16(); + if status == 401 || status == 403 { + return Err(format!("authentication failed (HTTP {status})").into()); + } + if status != 200 { + return Err(format!( + "GitHub API returned HTTP {status}: {}", + read_error_body(resp.body_mut().as_reader()) + ) + .into()); + } + let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES) + .map_err(|e| format!("reading commit response: {e}"))?; + + #[derive(Deserialize)] + struct Payload { + #[serde(default)] + commit: Commit, + } + #[derive(Deserialize, Default)] + struct Commit { + #[serde(default)] + committer: Option, + #[serde(default)] + author: Option, + } + let payload: Payload = serde_json::from_slice(&body).map_err(|e| format!("decoding commit: {e}"))?; + let committer = payload.commit.committer.unwrap_or_default(); + let author = payload.commit.author.unwrap_or_default(); + let (mut name, mut date) = (committer.name.unwrap_or_default(), committer.date); + if name.is_empty() { + name = author.name.unwrap_or_default(); + date = author.date; + } + let date = parse_time(date.as_deref()).map_err(|e| format!("decoding commit: {e}"))?; + match date { + Some(d) if !name.is_empty() => Ok((name, format_time(Some(d)))), + _ => Err("commit metadata missing".into()), + } + } + + /// Downloads the repository zipball (default branch when `reference` is empty) to `dest_path`. + pub fn download_repo_archive(&self, full_name: &str, reference: &str, dest_path: &Path) -> crate::Result<()> { + let (owner, repo) = split_repo_full_name(full_name)?; + let mut segments = vec!["repos", &owner, &repo, "zipball"]; + if !reference.is_empty() { + segments.push(reference); + } + let url = self.endpoint_url(&segments)?; + let mut resp = self.get(&url).map_err(|e| format!("downloading archive: {e}"))?; + let status = resp.status().as_u16(); + if status == 401 || status == 403 { + return Err(format!("authentication failed (HTTP {status}) — check your access token").into()); + } + if status != 200 { + return Err(format!( + "GitHub API returned HTTP {status}: {}", + read_error_body(resp.body_mut().as_reader()) + ) + .into()); + } + let length = resp + .headers() + .get("content-length") + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.trim().parse::().ok()); + if let Some(length) = length.filter(|&l| l > MAX_ARCHIVE_BYTES) { + return Err( + format!("archive is too large: {length} bytes exceeds {MAX_ARCHIVE_BYTES} bytes").into() + ); + } + let mut file = std::fs::File::create(dest_path).map_err(|e| format!("creating file: {e}"))?; + copy_limited_archive(&mut file, resp.body_mut().as_reader(), MAX_ARCHIVE_BYTES) + .map_err(|e| format!("writing archive: {e}"))?; + Ok(()) + } + + /// Base URL plus path-escaped segments. + fn endpoint_url(&self, segments: &[&str]) -> crate::Result { + let mut url = self.base_url.clone(); + for segment in segments { + if segment.is_empty() { + return Err("empty GitHub API path segment".into()); + } + url.push('/'); + url.push_str(&path_escape(segment)); + } + Ok(url) + } +} + +/// Go's `url.PathEscape`. +fn path_escape(segment: &str) -> String { + let mut out = String::new(); + for b in segment.bytes() { + if b.is_ascii_alphanumeric() || b"-_.~$&+:=@".contains(&b) { + out.push(b as char); + } else { + out.push_str(&format!("%{b:02X}")); + } + } + out +} + +fn parse_time(value: Option<&str>) -> Result>, String> { + match value { + None => Ok(None), + Some(v) => { + let t = DateTime::parse_from_rfc3339(v).map_err(|e| format!("parsing time {v:?}: {e}"))?; + // Go's zero time means "unset". + Ok(Some(t).filter(|t| t.timestamp() != -62135596800)) + } + } +} + +/// Go's `time.Time.Format(time.RFC3339)`, with the zero time for a missing value. +fn format_time(value: Option>) -> String { + match value { + Some(t) => t.to_rfc3339_opts(SecondsFormat::Secs, true), + None => "0001-01-01T00:00:00Z".to_string(), + } +} + +fn read_limited(r: impl Read, max_bytes: u64) -> crate::Result> { + let mut body = Vec::new(); + r.take(max_bytes + 1).read_to_end(&mut body)?; + if body.len() as u64 > max_bytes { + return Err(format!("response body exceeds {max_bytes} bytes").into()); + } + Ok(body) +} + +fn read_error_body(r: impl Read) -> String { + match read_limited(r, MAX_ERROR_BODY_BYTES) { + Ok(body) => String::from_utf8_lossy(&body).trim().to_string(), + Err(e) => format!(""), + } +} + +fn split_repo_full_name(full_name: &str) -> crate::Result<(String, String)> { + let parts: Vec<&str> = full_name.trim().split('/').collect(); + let [owner, repo] = parts.as_slice() else { + return Err("repository name must be in owner/repo format".into()); + }; + let (owner, repo) = (owner.trim(), repo.trim()); + if owner.is_empty() || repo.is_empty() { + return Err("repository name must be in owner/repo format".into()); + } + Ok((owner.to_string(), repo.to_string())) +} + +/// Copies at most `max_bytes`; returns the number copied, or an error once the source exceeds it. +fn copy_limited_archive(dst: &mut impl Write, src: impl Read, max_bytes: u64) -> crate::Result { + let n = std::io::copy(&mut src.take(max_bytes + 1), dst)?; + if n > max_bytes { + return Err(format!("archive exceeds {max_bytes} bytes").into()); + } + Ok(n) +} + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::{Arc, Mutex}; + + #[test] + fn read_limited_rejects_oversized_response() { + let err = read_limited(&b"abcd"[..], 3).unwrap_err(); + assert!(err.to_string().contains("response body exceeds"), "{err}"); + } + + #[test] + fn read_limited_allows_response_at_limit() { + assert_eq!(read_limited(&b"abc"[..], 3).unwrap(), b"abc"); + } + + #[test] + fn endpoint_url_escapes_repo_path_segments() { + let client = Client::new("https://ghe.example.com/api/v3", ""); + let got = client.endpoint_url(&["repos", "owner", "repo", "commits", "feature/a b"]).unwrap(); + assert_eq!(got, "https://ghe.example.com/api/v3/repos/owner/repo/commits/feature%2Fa%20b"); + assert!(client.endpoint_url(&["repos", ""]).is_err()); + } + + #[test] + fn split_repo_full_name_rejects_extra_segments() { + assert!(split_repo_full_name("owner/repo/../../user").is_err()); + assert!(split_repo_full_name("owner/").is_err()); + assert_eq!(split_repo_full_name(" a / b ").unwrap(), ("a".into(), "b".into())); + } + + #[test] + fn copy_limited_archive_rejects_oversized_stream() { + let mut dst = Vec::new(); + let err = copy_limited_archive(&mut dst, &b"abcd"[..], 3).unwrap_err(); + assert!(err.to_string().contains("archive exceeds"), "{err}"); + assert_eq!(dst.len(), 4); + } + + #[test] + fn copy_limited_archive_allows_stream_at_limit() { + let mut dst = Vec::new(); + assert_eq!(copy_limited_archive(&mut dst, &b"abc"[..], 3).unwrap(), 3); + assert_eq!(dst, b"abc"); + } + + type Seen = Arc, Option)>>>; + + /// A local HTTP server answering with `handler(url) -> (status, body)`; records + /// (url, authorization, user-agent) for every request. + fn serve(handler: impl Fn(&str) -> (u16, String) + Send + 'static) -> (String, Seen) { + let server = tiny_http::Server::http("127.0.0.1:0").unwrap(); + let base = format!("http://{}", server.server_addr().to_ip().unwrap()); + let seen: Seen = Arc::default(); + let log = seen.clone(); + std::thread::spawn(move || { + for req in server.incoming_requests() { + let header = |name: &'static str| { + req.headers().iter().find(|h| h.field.equiv(name)).map(|h| h.value.to_string()) + }; + log.lock().unwrap().push((req.url().to_string(), header("Authorization"), header("User-Agent"))); + let (status, body) = handler(req.url()); + let _ = req.respond(tiny_http::Response::from_string(body).with_status_code(status)); + } + }); + (base, seen) + } + + #[test] + fn validate_token_returns_login_and_reports_status() { + let (base, seen) = serve(|_| (200, r#"{"login":"octocat"}"#.into())); + assert_eq!(Client::new(&base, "tok").validate_token().unwrap(), "octocat"); + let (url, auth, ua) = seen.lock().unwrap()[0].clone(); + assert_eq!(url, "/user"); + assert_eq!(auth.as_deref(), Some("Bearer tok")); + assert_eq!(ua.as_deref(), Some(USER_AGENT)); + + let (base, _) = serve(|_| (401, "{}".into())); + let err = Client::new(&base, "bad").validate_token().unwrap_err(); + assert_eq!(err.to_string(), "token validation failed (HTTP 401)"); + } + + fn repo_json(n: usize, pushed: &str) -> serde_json::Value { + serde_json::json!({ + "full_name": format!("me/r{n}"), "name": format!("r{n}"), "description": null, + "html_url": format!("https://example/me/r{n}"), "default_branch": if n == 0 { "" } else { "main" }, + "private": n % 2 == 0, "fork": false, "archived": false, "language": null, + "stargazers_count": n, "pushed_at": pushed, "updated_at": "2024-01-01T00:00:00Z", + "owner": {"login": "me"} + }) + } + + #[test] + fn fetch_repos_paginates_sorts_and_enriches() { + let (base, seen) = serve(|url| { + if url.starts_with("/user/repos") && url.contains("page=1&") { + let repos: Vec<_> = (0..100) + .map(|n| repo_json(n, &format!("2024-02-{:02}T00:00:00Z", 1 + n % 28))) + .collect(); + (200, serde_json::to_string(&repos).unwrap()) + } else if url.starts_with("/user/repos") { + (200, serde_json::to_string(&[repo_json(100, "2025-06-01T12:00:00Z")]).unwrap()) + } else if url == "/repos/me/r100/commits/main" { + (200, r#"{"commit":{"committer":{"name":"","date":"0001-01-01T00:00:00Z"},"author":{"name":"Ann","date":"2025-06-01T11:00:00Z"}}}"#.into()) + } else if url.starts_with("/repos/me/r0/commits/HEAD") { + (200, r#"{"commit":{"committer":{"name":"Bob","date":"2024-01-05T10:00:00+00:00"}}}"#.into()) + } else { + (404, "nope".into()) + } + }); + let calls = Mutex::new(Vec::new()); + let repos = Client::new(&base, "t") + .fetch_repos_with_progress(|done, total| calls.lock().unwrap().push((done, total))) + .unwrap(); + assert_eq!(repos.len(), 101); + assert_eq!(repos[0].full_name, "me/r100"); + assert_eq!(repos[0].pushed_at, "2025-06-01T12:00:00Z"); + assert_eq!(repos[0].latest_committer, "Ann"); + assert_eq!(repos[0].latest_commit_at, "2025-06-01T11:00:00Z"); + assert!(repos.windows(2).all(|w| w[0].pushed_at >= w[1].pushed_at)); + let r0 = repos.iter().find(|r| r.name == "r0").unwrap(); + assert_eq!((r0.latest_committer.as_str(), r0.latest_commit_at.as_str()), ("Bob", "2024-01-05T10:00:00Z")); + assert_eq!(r0.owner, "me"); + assert!(r0.private && r0.description.is_empty() && r0.language.is_empty()); + assert!(repos.iter().filter(|r| r.latest_committer.is_empty()).count() == 99); + + let mut calls = calls.into_inner().unwrap(); + assert_eq!(calls[0], (0, 101)); + calls.sort(); + assert_eq!(calls.last(), Some(&(101, 101))); + assert_eq!(calls.len(), 102); + assert_eq!(seen.lock().unwrap().iter().filter(|s| s.0.starts_with("/user/repos")).count(), 2); + } + + #[test] + fn fetch_repos_reports_auth_and_api_errors() { + let (base, _) = serve(|_| (403, "{}".into())); + let err = Client::new(&base, "t").fetch_repos().unwrap_err().to_string(); + assert_eq!(err, "authentication failed (HTTP 403) — check your access token"); + + let (base, _) = serve(|_| (500, "boom".into())); + let err = Client::new(&base, "t").fetch_repos().unwrap_err().to_string(); + assert_eq!(err, "GitHub API returned HTTP 500: boom"); + } + + #[test] + fn download_repo_archive_writes_file() { + let (base, seen) = serve(|url| match url { + "/repos/me/r/zipball/v1%2F2" => (200, "ZIPDATA".into()), + _ => (404, " missing \n".into()), + }); + let dir = std::env::temp_dir().join(format!("nexus-gh-{}", uuid::Uuid::new_v4())); + std::fs::create_dir_all(&dir).unwrap(); + let dest = dir.join("a.zip"); + let client = Client::new(&base, "t"); + client.download_repo_archive("me/r", "v1/2", &dest).unwrap(); + assert_eq!(std::fs::read(&dest).unwrap(), b"ZIPDATA"); + let err = client.download_repo_archive("me/r", "", &dest).unwrap_err().to_string(); + assert_eq!(err, "GitHub API returned HTTP 404: missing"); + assert_eq!(seen.lock().unwrap()[1].0, "/repos/me/r/zipball"); + assert!(client.download_repo_archive("bad", "", &dest).is_err()); + std::fs::remove_dir_all(dir).unwrap(); + } +} diff --git a/rust/core/src/clients/irc.rs b/rust/core/src/clients/irc.rs new file mode 100644 index 0000000..3fdf209 --- /dev/null +++ b/rust/core/src/clients/irc.rs @@ -0,0 +1,1337 @@ +//! IRC client: registration, PING/PONG, channel/user tracking, CTCP ACTION, NickServ identify, +//! auto-join and the composer's /commands, reporting through typed `Event`s. +//! +//! One I/O thread owns the transport (TCP, or a caller-supplied stream such as an SSH tunnel, +//! optionally wrapped in TLS). It alternates between short reads and draining the outgoing +//! queue, so the transport never has to be split. + +use std::collections::HashMap; +use std::io::{ErrorKind, Read, Write}; +use std::net::{TcpStream, ToSocketAddrs}; +use std::sync::mpsc::{self, Receiver, Sender}; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::time::{Duration, Instant}; + +use serde::{Deserialize, Serialize}; + +use super::{CloseFn, ErrorFn}; + +pub const SERVER_TARGET: &str = "server"; +const MAX_ROOM_LIST_ENTRIES: usize = 2000; +const QUIT_MESSAGE: &str = "Nexus Terminal disconnected"; +const READ_POLL: Duration = Duration::from_millis(50); +const PING_DELAY: Duration = Duration::from_secs(20); +const PING_TIMEOUT: Duration = Duration::from_secs(60); +const PING_GRACE: Duration = Duration::from_secs(30); +const MAX_LINE_BYTES: usize = 1 << 20; + +/// A bidirectional byte stream. Reads must not block indefinitely: they should return +/// `WouldBlock` or `TimedOut` after a short interval (tens of milliseconds) so the client's +/// I/O thread can interleave writes. +pub trait ReadWrite: Read + Write + Send {} +impl ReadWrite for T {} + +pub type Connector = Arc crate::Result> + Send + Sync>; +pub type EventFn = Arc; +pub type RoomsChangedFn = Arc) + Send + Sync>; + +#[derive(Clone)] +pub struct Config { + pub server: String, + pub port: u16, + pub nick: String, + pub user: String, + pub real_name: String, + pub use_tls: bool, + pub server_pass: String, + pub auto_join_rooms: Vec, + + pub nickserv_identify: bool, + pub nickserv_account: String, + pub nickserv_password: String, + + /// Provides the transport instead of a direct TCP connect (e.g. an SSH tunnel). TLS, when + /// enabled, is layered on top of it. + pub connector: Option, + + pub connect_timeout: Duration, + pub on_event: EventFn, + pub on_error: ErrorFn, + pub on_close: CloseFn, + pub on_rooms_changed: RoomsChangedFn, +} + +impl Default for Config { + fn default() -> Config { + Config { + server: String::new(), + port: 6667, + nick: String::new(), + user: String::new(), + real_name: String::new(), + use_tls: false, + server_pass: String::new(), + auto_join_rooms: Vec::new(), + nickserv_identify: false, + nickserv_account: String::new(), + nickserv_password: String::new(), + connector: None, + connect_timeout: Duration::ZERO, + on_event: Arc::new(|_| {}), + on_error: Arc::new(|_| {}), + on_close: Arc::new(|| {}), + on_rooms_changed: Arc::new(|_| {}), + } + } +} + +/// A typed IRC event, serialised for the UI exactly as Go's `ircclient.Event`. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", default)] +pub struct Event { + #[serde(rename = "type")] + pub kind: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub target: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub from: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub text: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub command: String, + pub timestamp: i64, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub rooms: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub users: Vec, + #[serde(skip_serializing_if = "String::is_empty")] + pub topic: String, +} + +/// A joined or listed IRC room. +#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub struct RoomInfo { + pub name: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub topic: String, + #[serde(skip_serializing_if = "is_zero")] + pub users: i64, +} + +fn is_zero(n: &i64) -> bool { + *n == 0 +} + +enum Out { + Line(String), + Quit(String), +} + +struct Channel { + name: String, + topic: String, + users: Vec, +} + +struct State { + nick: String, + connected: bool, + closing: bool, + list_rooms: Vec, + room_names: Vec, + /// Keyed by RFC 1459 casefolded name. + channels: HashMap, + ready: Option>>, + last_pong: Instant, +} + +pub struct Client { + cfg: Config, + state: Mutex, + out: Mutex>>, +} + +/// A parsed server line. +struct Message { + source: String, + command: String, + params: Vec, +} + +impl Message { + fn last(&self) -> &str { + self.params.last().map(String::as_str).unwrap_or("") + } + fn param(&self, i: usize) -> &str { + self.params.get(i).map(String::as_str).unwrap_or("") + } +} + +impl Client { + pub fn new(mut cfg: Config) -> Arc { + if cfg.connect_timeout.is_zero() { + cfg.connect_timeout = Duration::from_secs(30); + } + if cfg.user.is_empty() { + cfg.user = cfg.nick.clone(); + } + if cfg.real_name.is_empty() { + cfg.real_name = cfg.nick.clone(); + } + let nick = cfg.nick.clone(); + Arc::new(Client { + cfg, + state: Mutex::new(State { + nick, + connected: false, + closing: false, + list_rooms: Vec::new(), + room_names: Vec::new(), + channels: HashMap::new(), + ready: None, + last_pong: Instant::now(), + }), + out: Mutex::new(None), + }) + } + + fn st(&self) -> MutexGuard<'_, State> { + self.state.lock().unwrap_or_else(|e| e.into_inner()) + } + + /// Connects and blocks until registration succeeds (RPL_WELCOME), fails, or times out. + pub fn connect(self: &Arc) -> crate::Result<()> { + let (ready_tx, ready_rx) = mpsc::channel(); + let (out_tx, out_rx) = mpsc::channel(); + self.st().ready = Some(ready_tx); + *self.out.lock().unwrap_or_else(|e| e.into_inner()) = Some(out_tx); + + let client = self.clone(); + std::thread::spawn(move || client.io_thread(out_rx)); + + let timeout = self.cfg.connect_timeout; + match ready_rx.recv_timeout(timeout) { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(e.into()), + Err(_) => { + self.close(); + Err(format!("IRC connection timed out after {timeout:?}").into()) + } + } + } + + /// Sends QUIT and disconnects. Idempotent; `on_close` fires from the I/O thread. + pub fn close(&self) { + { + let mut st = self.st(); + if st.closing { + return; + } + st.closing = true; + } + self.queue(Out::Quit(QUIT_MESSAGE.to_string())); + } + + fn queue(&self, out: Out) -> bool { + let guard = self.out.lock().unwrap_or_else(|e| e.into_inner()); + guard.as_ref().is_some_and(|tx| tx.send(out).is_ok()) + } + + /// Sends one IRC line (CR/LF are stripped). + fn send_raw(&self, line: &str) -> crate::Result<()> { + let line: String = line.chars().filter(|c| *c != '\r' && *c != '\n').collect(); + if self.queue(Out::Line(line)) { Ok(()) } else { Err("not connected".into()) } + } + + fn send(&self, command: &str, params: &[&str]) -> crate::Result<()> { + self.send_raw(&encode(command, params)) + } + + /// PRIVMSG/NOTICE, split so each line fits the 512-byte limit. + fn send_text(&self, command: &str, target: &str, text: &str) -> crate::Result<()> { + // ponytail: fixed prefix allowance (nick!user@host) like girc's default; no ISUPPORT tuning. + let max = 510usize.saturating_sub(115 + command.len() + target.len() + 3).max(64); + let mut rest = text; + while !rest.is_empty() { + let mut cut = rest.len().min(max); + while !rest.is_char_boundary(cut) { + cut -= 1; + } + self.send(command, &[target, &rest[..cut]])?; + rest = &rest[cut..]; + } + Ok(()) + } + + fn nick(&self) -> String { + self.st().nick.clone() + } + + /// Parses a composer line: plain text goes to `target`, `/commands` are interpreted. + pub fn handle_input(&self, target: &str, input: &str) -> crate::Result<()> { + let text = input.trim(); + if text.is_empty() { + return Ok(()); + } + let target = if target.is_empty() { SERVER_TARGET } else { target }; + + if !text.starts_with('/') || text == "/" { + if target == SERVER_TARGET { + return Err("select a room or use /msg ".into()); + } + self.send_text("PRIVMSG", target, text)?; + self.emit_local_message("message", target, text); + return Ok(()); + } + + let command_line = text[1..].trim(); + let Some(first) = command_line.split_whitespace().next() else { return Ok(()) }; + let cmd = first.to_lowercase(); + let args = command_line[first.len()..].trim(); + + match cmd.as_str() { + "me" | "action" => { + if target == SERVER_TARGET { + return Err("/me requires an active room or private tab".into()); + } + if args.is_empty() { + return Err("/me requires an action".into()); + } + self.send("PRIVMSG", &[target, &format!("\x01ACTION {args}\x01")])?; + self.emit_local_message("action", target, args); + } + "msg" | "query" => { + let (recipient, body) = split_first_arg(args); + if recipient.is_empty() || body.is_empty() { + return Err("/msg requires a target and message".into()); + } + self.send_text("PRIVMSG", recipient, body)?; + self.emit_local_message("message", recipient, body); + } + "notice" => { + let (recipient, body) = split_first_arg(args); + if recipient.is_empty() || body.is_empty() { + return Err("/notice requires a target and message".into()); + } + self.send_text("NOTICE", recipient, body)?; + self.emit_local_message("notice", recipient, body); + } + "join" | "j" => { + let (room, key) = split_first_arg(args); + if room.is_empty() { + return Err("/join requires a room".into()); + } + if key.is_empty() { + self.send("JOIN", &[room])?; + } else { + self.send("JOIN", &[room, key])?; + } + } + "part" | "leave" => { + let (room, msg) = split_first_arg(args); + let room = if room.is_empty() { target } else { room }; + if room == SERVER_TARGET { + return Err("/part requires a room".into()); + } + if msg.is_empty() { + self.send("PART", &[room])?; + } else { + self.send("PART", &[room, msg])?; + } + } + "quit" => { + let msg = if args.is_empty() { QUIT_MESSAGE } else { args }; + self.queue(Out::Quit(msg.to_string())); + } + "nick" => { + let Some(nick) = args.split_whitespace().next() else { + return Err("/nick requires a nickname".into()); + }; + self.send("NICK", &[nick])?; + } + "topic" => { + let (room, topic) = split_first_arg(args); + let room = if room.is_empty() { target } else { room }; + if room == SERVER_TARGET { + return Err("/topic requires a room".into()); + } + if topic.is_empty() { + self.send("TOPIC", &[room])?; + } else { + self.send("TOPIC", &[room, topic])?; + } + } + "list" => { + if args.is_empty() { + self.send("LIST", &[])?; + } else { + self.send("LIST", &[&args.split_whitespace().collect::>().join(",")])?; + } + } + "names" => { + let room = if args.is_empty() && target != SERVER_TARGET { target } else { args }; + if room.is_empty() { + return self.send_raw("NAMES"); + } + return self.send_raw(&format!("NAMES {room}")); + } + "whois" => { + if args.is_empty() { + return Err("/whois requires a nickname".into()); + } + for nick in args.split_whitespace() { + self.send("WHOIS", &[nick])?; + } + } + "who" => { + let args = if args.is_empty() { + if target == SERVER_TARGET { + return Err("/who requires a target".into()); + } + target + } else { + args + }; + for who in args.split_whitespace() { + self.send("WHO", &[who, "%tcuhnr,2"])?; + } + } + "raw" | "quote" => { + if args.is_empty() { + return Err("/raw requires an IRC command".into()); + } + return self.send_raw(args); + } + "help" => self.emit(Event { + kind: "system".into(), + target: target.into(), + text: "Commands: /join, /part, /msg, /notice, /me, /nick, /topic, /list, /names, /whois, /who, /raw, /quit".into(), + ..Default::default() + }), + _ => { + let mut raw = first.to_uppercase(); + if !args.is_empty() { + raw.push(' '); + raw.push_str(args); + } + return self.send_raw(&raw); + } + } + Ok(()) + } + + pub fn join(&self, room: &str) -> crate::Result<()> { + let room = room.trim(); + if room.is_empty() { + return Err("room is required".into()); + } + self.send("JOIN", &[room]) + } + + pub fn part(&self, room: &str) -> crate::Result<()> { + let room = room.trim(); + if room.is_empty() { + return Err("room is required".into()); + } + self.send("PART", &[room]) + } + + /// Requests the server's room list; results arrive as a `room-list` event. + pub fn list_rooms(&self) -> crate::Result<()> { + self.st().list_rooms.clear(); + self.send("LIST", &[]) + } + + /// Currently joined rooms, sorted case-insensitively. + pub fn rooms(&self) -> Vec { + let st = self.st(); + let mut rooms: Vec = st + .room_names + .iter() + .map(|name| match st.channels.get(&fold(name)) { + Some(ch) => RoomInfo { name: ch.name.clone(), topic: ch.topic.clone(), users: ch.users.len() as i64 }, + None => RoomInfo { name: name.clone(), ..Default::default() }, + }) + .collect(); + rooms.sort_by_key(|r| r.name.to_lowercase()); + rooms + } + + /// Known users in a room, sorted ignoring mode prefixes. + pub fn users(&self, room: &str) -> Vec { + let st = self.st(); + let Some(ch) = st.channels.get(&fold(room)) else { return Vec::new() }; + let mut users = ch.users.clone(); + users.sort_by_key(|u| u.trim_start_matches(['~', '&', '@', '%', '+']).to_lowercase()); + users + } + + // ---- I/O thread ---- + + fn io_thread(self: Arc, out_rx: Receiver) { + let result = self.open_transport().and_then(|stream| self.run(stream, &out_rx)); + *self.out.lock().unwrap_or_else(|e| e.into_inner()) = None; + let (was_closing, was_connected, ready) = { + let mut st = self.st(); + let r = (st.closing, st.connected, st.ready.take()); + st.connected = false; + r + }; + if let Err(e) = result { + if !was_closing { + let msg = e.to_string(); + self.emit(Event { kind: "error".into(), text: msg.clone(), ..Default::default() }); + (self.cfg.on_error)(msg.clone()); + if let Some(ready) = ready { + let _ = ready.send(Err(msg)); + } + } + } + if was_connected { + (self.cfg.on_close)(); + } + } + + fn open_transport(&self) -> crate::Result> { + let stream: Box = match &self.cfg.connector { + Some(connector) => connector()?, + None => { + let addr = format!("{}:{}", self.cfg.server, self.cfg.port); + let tcp = dial(&self.cfg.server, self.cfg.port, self.cfg.connect_timeout) + .map_err(|e| format!("dial tcp {addr}: {e}"))?; + tcp.set_read_timeout(Some(READ_POLL))?; + tcp.set_write_timeout(Some(self.cfg.connect_timeout))?; + let _ = tcp.set_nodelay(true); + Box::new(tcp) + } + }; + if !self.cfg.use_tls { + return Ok(stream); + } + self.tls_wrap(stream).map_err(|e| format!("IRC TLS handshake failed: {e}").into()) + } + + fn tls_wrap(&self, sock: Box) -> crate::Result> { + let mut roots = rustls::RootCertStore::empty(); + roots.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); + let config = rustls::ClientConfig::builder_with_provider(Arc::new(rustls::crypto::ring::default_provider())) + .with_safe_default_protocol_versions()? + .with_root_certificates(roots) + .with_no_client_auth(); + let name = rustls::pki_types::ServerName::try_from(self.cfg.server.clone())?; + let conn = rustls::ClientConnection::new(Arc::new(config), name)?; + let mut tls = rustls::StreamOwned::new(conn, sock); + let deadline = Instant::now() + self.cfg.connect_timeout; + while tls.conn.is_handshaking() { + match tls.conn.complete_io(&mut tls.sock) { + Ok(_) => {} + Err(e) if is_poll_timeout(&e) && Instant::now() < deadline => {} + Err(e) => return Err(e.into()), + } + } + Ok(Box::new(tls)) + } + + /// Registers, then pumps reads, the outgoing queue and keepalive pings until the connection + /// ends. `Ok` means a deliberate QUIT. + fn run(&self, mut stream: Box, out_rx: &Receiver) -> crate::Result<()> { + if self.st().closing { + return Ok(()); + } + let write = |stream: &mut Box, line: &str| -> crate::Result<()> { + stream.write_all(line.as_bytes())?; + stream.write_all(b"\r\n")?; + Ok(stream.flush()?) + }; + if !self.cfg.server_pass.is_empty() { + write(&mut stream, &encode("PASS", &[&self.cfg.server_pass]))?; + } + write(&mut stream, &encode("NICK", &[&self.cfg.nick]))?; + write(&mut stream, &encode("USER", &[&self.cfg.user, "*", "*", &self.cfg.real_name]))?; + + let started = Instant::now(); + let mut last_ping = Instant::now(); + let mut ping_sent = false; + self.st().last_pong = Instant::now(); + let mut buf = Vec::new(); + let mut chunk = [0u8; 8192]; + loop { + while let Ok(out) = out_rx.try_recv() { + match out { + Out::Line(line) => write(&mut stream, &line)?, + Out::Quit(msg) => { + let _ = write(&mut stream, &encode("QUIT", &[&msg])); + return Ok(()); + } + } + } + + if started.elapsed() >= PING_GRACE && last_ping.elapsed() >= PING_DELAY { + if ping_sent && self.st().last_pong.elapsed() > PING_DELAY + PING_TIMEOUT { + return Err("timed out waiting for a requested PING response".into()); + } + let id = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_nanos(); + write(&mut stream, &encode("PING", &[&id.to_string()]))?; + last_ping = Instant::now(); + ping_sent = true; + } + + match stream.read(&mut chunk) { + Ok(0) => return Err("connection closed by server".into()), + Ok(n) => buf.extend_from_slice(&chunk[..n]), + Err(e) if is_poll_timeout(&e) || e.kind() == ErrorKind::Interrupted => continue, + Err(e) => return Err(e.into()), + } + while let Some(pos) = buf.iter().position(|&b| b == b'\n') { + let raw: Vec = buf.drain(..=pos).collect(); + let line = String::from_utf8_lossy(&raw); + if let Some(msg) = parse_line(line.trim_end_matches(['\r', '\n'])) { + self.handle_message(&msg); + } + } + if buf.len() > MAX_LINE_BYTES { + return Err("server line too long".into()); + } + } + } + + // ---- server message handling (girc's state tracking, then Link's handlers) ---- + + fn is_me(&self, nick: &str) -> bool { + fold(nick) == fold(&self.st().nick) + } + + fn handle_message(&self, m: &Message) { + match m.command.as_str() { + "001" => self.handle_welcome(m), + "433" | "436" | "437" => { + let mut st = self.st(); + if !st.connected { + st.nick.push('_'); + let nick = st.nick.clone(); + drop(st); + let _ = self.send("NICK", &[&nick]); + } + } + "PING" => { + let _ = self.send("PONG", &[m.last()]); + } + "PONG" => self.st().last_pong = Instant::now(), + "PRIVMSG" => self.handle_privmsg(m), + "NOTICE" => self.handle_notice(m), + "JOIN" => self.handle_join(m), + "PART" => self.handle_part(m), + "KICK" => self.handle_kick(m), + "QUIT" => self.handle_quit(m), + "NICK" => self.handle_nick(m), + "TOPIC" | "332" => self.handle_topic(m), + "353" => self.handle_names(m), + "366" => { + if m.params.len() >= 2 { + self.emit_users(m.param(1)); + } + } + "322" => { + if m.params.len() >= 4 { + let room = RoomInfo { + name: m.param(1).into(), + users: m.param(2).parse().unwrap_or(0), + topic: m.param(3).into(), + }; + let mut st = self.st(); + if st.list_rooms.len() < MAX_ROOM_LIST_ENTRIES { + st.list_rooms.push(room); + } + } + } + "323" => { + let mut rooms = std::mem::take(&mut self.st().list_rooms); + rooms.sort_by(|a, b| b.users.cmp(&a.users).then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))); + self.emit(Event { kind: "room-list".into(), rooms, ..Default::default() }); + } + "ERROR" => self.emit(Event { kind: "error".into(), text: m.last().into(), ..Default::default() }), + _ => {} + } + } + + fn handle_welcome(&self, m: &Message) { + let ready = { + let mut st = self.st(); + if !m.params.is_empty() { + st.nick = m.param(0).to_string(); + } + st.connected = true; + st.ready.take() + }; + let host = if self.cfg.server.contains(':') { format!("[{}]", self.cfg.server) } else { self.cfg.server.clone() }; + self.emit(Event { + kind: "system".into(), + text: format!("Connected to {host}:{}", self.cfg.port), + ..Default::default() + }); + self.identify_with_nickserv(); + if !self.cfg.auto_join_rooms.is_empty() { + let _ = self.send("JOIN", &[&self.cfg.auto_join_rooms.join(",")]); + } + self.emit_rooms(); + if let Some(ready) = ready { + let _ = ready.send(Ok(())); + } + } + + fn identify_with_nickserv(&self) { + if !self.cfg.nickserv_identify || self.cfg.nickserv_password.is_empty() { + return; + } + let body = if self.cfg.nickserv_account.is_empty() { + format!("IDENTIFY {}", self.cfg.nickserv_password) + } else { + format!("IDENTIFY {} {}", self.cfg.nickserv_account, self.cfg.nickserv_password) + }; + let _ = self.send("PRIVMSG", &["NickServ", &body]); + self.emit(Event { kind: "system".into(), text: "Sent NickServ identify request".into(), ..Default::default() }); + } + + fn handle_privmsg(&self, m: &Message) { + if m.params.len() < 2 || m.source.is_empty() { + return; + } + let mut target = m.param(0).to_string(); + if self.is_me(&target) { + target = m.source.clone(); + } + let mut kind = "message"; + let mut text = m.last().to_string(); + if let Some((ctcp, args)) = decode_ctcp(m) { + match ctcp.as_str() { + "ACTION" => { + kind = "action"; + text = args; + } + "VERSION" => { + let _ = self.send("NOTICE", &[&m.source, "\x01VERSION Nexus Terminal\x01"]); + } + "PING" => { + let body = if args.is_empty() { "\x01PING\x01".to_string() } else { format!("\x01PING {args}\x01") }; + let _ = self.send("NOTICE", &[&m.source, &body]); + } + _ => {} + } + } + self.emit(Event { kind: kind.into(), target, from: m.source.clone(), text, ..Default::default() }); + } + + fn handle_notice(&self, m: &Message) { + let mut target = SERVER_TARGET.to_string(); + if !m.params.is_empty() { + target = m.param(0).to_string(); + if target == "*" { + target = SERVER_TARGET.into(); + } + if self.is_me(&target) && !m.source.is_empty() { + target = m.source.clone(); + } + } + self.emit(Event { kind: "notice".into(), target, from: m.source.clone(), text: m.last().into(), ..Default::default() }); + } + + fn handle_join(&self, m: &Message) { + let room = m.param(0).to_string(); + if room.is_empty() { + return; + } + let from = m.source.clone(); + if !from.is_empty() { + let mut st = self.st(); + let ch = st.channels.entry(fold(&room)).or_insert_with(|| Channel { + name: room.clone(), + topic: String::new(), + users: Vec::new(), + }); + add_user(ch, &from); + } + if self.is_me(&from) { + self.remember_room_membership(&room, true); + } + self.emit(Event { kind: "join".into(), target: room.clone(), from: from.clone(), text: format!("{from} joined {room}"), ..Default::default() }); + self.emit_rooms(); + self.emit_users(&room); + } + + fn handle_part(&self, m: &Message) { + let room = m.param(0).to_string(); + if room.is_empty() { + return; + } + let from = m.source.clone(); + let me = self.is_me(&from); + if !from.is_empty() { + let mut st = self.st(); + if me { + st.channels.remove(&fold(&room)); + } else if let Some(ch) = st.channels.get_mut(&fold(&room)) { + remove_user(ch, &from); + } + } + let message = if m.params.len() > 1 { m.last().trim().to_string() } else { String::new() }; + if me { + self.remember_room_membership(&room, false); + } + self.emit(Event { kind: "part".into(), target: room.clone(), from, text: message, ..Default::default() }); + self.emit_rooms(); + self.emit_users(&room); + } + + fn handle_kick(&self, m: &Message) { + if m.params.len() < 2 { + return; + } + let (room, kicked) = (m.param(0).to_string(), m.param(1).to_string()); + let me = self.is_me(&kicked); + { + let mut st = self.st(); + if me { + st.channels.remove(&fold(&room)); + } else if let Some(ch) = st.channels.get_mut(&fold(&room)) { + remove_user(ch, &kicked); + } + } + if me { + self.remember_room_membership(&room, false); + } + self.emit(Event { + kind: "kick".into(), + target: room.clone(), + from: m.source.clone(), + command: kicked, + text: m.last().trim().into(), + ..Default::default() + }); + self.emit_rooms(); + self.emit_users(&room); + } + + fn handle_quit(&self, m: &Message) { + if !m.source.is_empty() && !self.is_me(&m.source) { + for ch in self.st().channels.values_mut() { + remove_user(ch, &m.source); + } + } + self.emit(Event { kind: "quit".into(), from: m.source.clone(), text: m.last().trim().into(), ..Default::default() }); + self.emit_users_for_all(); + } + + fn handle_nick(&self, m: &Message) { + let new_nick = m.last().to_string(); + if !m.source.is_empty() && !m.params.is_empty() { + let mut st = self.st(); + if fold(&m.source) == fold(&st.nick) { + st.nick = new_nick.clone(); + } + for ch in st.channels.values_mut() { + if remove_user(ch, &m.source) { + add_user(ch, &new_nick); + } + } + } + self.emit(Event { kind: "nick".into(), from: m.source.clone(), text: new_nick, ..Default::default() }); + self.emit_users_for_all(); + } + + fn handle_topic(&self, m: &Message) { + let (room, topic) = if m.command == "332" && m.params.len() >= 3 { + (m.param(1), m.param(2)) + } else if m.params.len() >= 2 { + (m.param(0), m.param(1)) + } else { + return; + }; + if room.is_empty() { + return; + } + if let Some(ch) = self.st().channels.get_mut(&fold(room)) { + ch.topic = topic.to_string(); + } + self.emit(Event { + kind: "topic".into(), + target: room.into(), + from: m.source.clone(), + text: topic.into(), + topic: topic.into(), + ..Default::default() + }); + self.emit_rooms(); + } + + fn handle_names(&self, m: &Message) { + if m.params.len() < 4 { + return; + } + { + let mut st = self.st(); + if let Some(ch) = st.channels.get_mut(&fold(m.param(2))) { + for part in m.last().split(' ') { + let nick = part.trim_start_matches(['~', '&', '@', '%', '+']); + let nick = if nick.contains('@') { source_name(nick) } else { nick }; + if is_valid_nick(nick) { + add_user(ch, nick); + } + } + } + } + self.emit(Event { + kind: "users".into(), + target: m.param(2).into(), + users: m.param(3).split_whitespace().map(String::from).collect(), + ..Default::default() + }); + } + + // ---- emitting ---- + + fn remember_room_membership(&self, room: &str, joined: bool) { + let room = room.trim(); + if room.is_empty() { + return; + } + let names = { + let mut st = self.st(); + let position = st.room_names.iter().position(|r| r.eq_ignore_ascii_case(room)); + match (joined, position) { + (true, None) => st.room_names.push(room.to_string()), + (false, Some(i)) => { + st.room_names.remove(i); + } + _ => return, + } + st.room_names.clone() + }; + (self.cfg.on_rooms_changed)(names); + } + + fn emit_rooms(&self) { + self.emit(Event { kind: "rooms".into(), rooms: self.rooms(), ..Default::default() }); + } + + fn emit_users(&self, room: &str) { + if !room.is_empty() { + self.emit(Event { kind: "users".into(), target: room.into(), users: self.users(room), ..Default::default() }); + } + } + + fn emit_users_for_all(&self) { + for room in self.rooms() { + self.emit_users(&room.name); + } + } + + fn emit_local_message(&self, kind: &str, target: &str, text: &str) { + self.emit(Event { kind: kind.into(), target: target.into(), from: self.nick(), text: text.into(), ..Default::default() }); + } + + fn emit(&self, mut event: Event) { + if event.timestamp == 0 { + event.timestamp = chrono::Utc::now().timestamp_millis(); + } + if event.target.is_empty() { + event.target = SERVER_TARGET.into(); + } + (self.cfg.on_event)(event); + } +} + +fn dial(host: &str, port: u16, timeout: Duration) -> std::io::Result { + let mut last = std::io::Error::new(ErrorKind::NotFound, "no addresses found"); + for addr in (host, port).to_socket_addrs()? { + match TcpStream::connect_timeout(&addr, timeout) { + Ok(s) => return Ok(s), + Err(e) => last = e, + } + } + Err(last) +} + +fn is_poll_timeout(e: &std::io::Error) -> bool { + matches!(e.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut) +} + +/// girc's `ToRFC1459`: ASCII A-Z and `[\]^` fold to a-z and `{|}~`. +fn fold(s: &str) -> String { + s.chars().map(|c| if ('A'..='^').contains(&c) { (c as u8 + 32) as char } else { c }).collect() +} + +fn add_user(ch: &mut Channel, nick: &str) { + if !ch.users.iter().any(|u| fold(u) == fold(nick)) { + ch.users.push(nick.to_string()); + } +} + +fn remove_user(ch: &mut Channel, nick: &str) -> bool { + let before = ch.users.len(); + ch.users.retain(|u| fold(u) != fold(nick)); + ch.users.len() != before +} + +/// girc's `IsValidNick`. +fn is_valid_nick(nick: &str) -> bool { + let b = nick.as_bytes(); + let Some(&first) = b.first() else { return false }; + if !(b'A'..=b'}').contains(&first) && first != b'?' { + return false; + } + b[1..].iter().all(|&c| (b'A'..=b'}').contains(&c) || c.is_ascii_digit() || c == b'-') +} + +/// The nick part of `nick!user@host`. +fn source_name(prefix: &str) -> &str { + prefix.split(['!', '@']).next().unwrap_or("") +} + +/// Encodes a command line the way girc does: the last parameter gets a `:` when it is empty, +/// contains a space or starts with `:`; CR/LF are stripped. +fn encode(command: &str, params: &[&str]) -> String { + let mut line = command.to_string(); + for (i, p) in params.iter().enumerate() { + line.push(' '); + if i == params.len() - 1 && (p.is_empty() || p.contains(' ') || p.starts_with(':')) { + line.push(':'); + } + line.push_str(p); + } + line.retain(|c| c != '\r' && c != '\n'); + line +} + +fn parse_line(line: &str) -> Option { + let mut rest = line.trim_start(); + if rest.starts_with('@') { + rest = rest.split_once(' ')?.1.trim_start(); + } + let mut source = String::new(); + if let Some(prefixed) = rest.strip_prefix(':') { + let (prefix, tail) = prefixed.split_once(' ')?; + source = source_name(prefix).to_string(); + rest = tail.trim_start(); + } + let (head, trailing) = match rest.split_once(" :") { + Some((h, t)) => (h, Some(t)), + None => (rest, None), + }; + let mut words = head.split(' ').filter(|w| !w.is_empty()); + let command = words.next()?.to_uppercase(); + let mut params: Vec = words.map(String::from).collect(); + params.extend(trailing.map(String::from)); + Some(Message { source, command, params }) +} + +/// A CTCP request in a PRIVMSG: `(TAG, args)`, as girc's `DecodeCTCP`. +fn decode_ctcp(m: &Message) -> Option<(String, String)> { + if m.params.len() != 2 { + return None; + } + let text = m.param(1).strip_prefix('\x01')?.strip_suffix('\x01')?; + let (tag, args) = text.split_once(' ').unwrap_or((text, "")); + if tag.is_empty() || !tag.bytes().all(|b| b.is_ascii_uppercase() || b.is_ascii_digit()) { + return None; + } + Some((tag.to_string(), args.to_string())) +} + +/// The first whitespace-separated word, and the trimmed remainder. +fn split_first_arg(input: &str) -> (&str, &str) { + let input = input.trim_start(); + match input.split_whitespace().next() { + Some(first) => (first, input[first.len()..].trim()), + None => ("", ""), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::{BufRead, BufReader}; + use std::net::TcpListener; + + #[test] + fn remember_room_membership_tracks_confirmed_channels() { + let updates = Arc::new(Mutex::new(Vec::new())); + let log = updates.clone(); + let client = Client::new(Config { + on_rooms_changed: Arc::new(move |rooms| log.lock().unwrap().push(rooms.join(","))), + ..Default::default() + }); + client.remember_room_membership("#One", true); + client.remember_room_membership("#two", true); + client.remember_room_membership("#one", true); + client.remember_room_membership("#missing", false); + client.remember_room_membership("#ONE", false); + assert_eq!(updates.lock().unwrap().join("|"), "#One|#One,#two|#two"); + assert_eq!(client.st().room_names, vec!["#two"]); + } + + #[test] + fn parses_and_encodes_lines() { + let m = parse_line("@time=x :nick!u@h PRIVMSG #chan :hello there").unwrap(); + assert_eq!((m.source.as_str(), m.command.as_str()), ("nick", "PRIVMSG")); + assert_eq!(m.params, vec!["#chan", "hello there"]); + let m = parse_line("PING :abc").unwrap(); + assert_eq!((m.source.as_str(), m.last()), ("", "abc")); + let m = parse_line(":srv 353 me = #c :@op +v plain").unwrap(); + assert_eq!(m.params.len(), 4); + assert_eq!(encode("PRIVMSG", &["#c", "hi"]), "PRIVMSG #c hi"); + assert_eq!(encode("PRIVMSG", &["#c", "hi there"]), "PRIVMSG #c :hi there"); + assert_eq!(encode("TOPIC", &["#c", ""]), "TOPIC #c :"); + assert_eq!(encode("QUIT", &[":x\r\nJOIN #evil"]), "QUIT ::xJOIN #evil"); + assert_eq!(split_first_arg(" #room key here "), ("#room", "key here")); + assert_eq!(split_first_arg(" "), ("", "")); + assert_eq!(fold("Nick[A]^"), "nick{a}~"); + } + + #[test] + fn event_serialises_like_go() { + let e = Event { kind: "users".into(), target: "#a".into(), timestamp: 5, ..Default::default() }; + assert_eq!(serde_json::to_string(&e).unwrap(), r##"{"type":"users","target":"#a","timestamp":5}"##); + let e = Event { + kind: "rooms".into(), + rooms: vec![RoomInfo { name: "#a".into(), topic: String::new(), users: 0 }], + ..Default::default() + }; + assert_eq!(serde_json::to_string(&e).unwrap(), r##"{"type":"rooms","timestamp":0,"rooms":[{"name":"#a"}]}"##); + } + + struct Fake { + reader: BufReader, + writer: TcpStream, + } + + impl Fake { + fn line(&mut self) -> String { + let mut s = String::new(); + self.reader.read_line(&mut s).unwrap(); + s.trim_end().to_string() + } + fn expect(&mut self, want: &str) { + for _ in 0..20 { + if self.line() == want { + return; + } + } + panic!("never received {want:?}"); + } + fn send(&mut self, line: &str) { + self.writer.write_all(format!("{line}\r\n").as_bytes()).unwrap(); + } + } + + fn harness(cfg: Config) -> (Arc, TcpListener, Receiver, Arc>) { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let (tx, rx) = mpsc::channel(); + let tx = Mutex::new(tx); + let closes = Arc::new(Mutex::new(0)); + let c = closes.clone(); + let client = Client::new(Config { + server: "127.0.0.1".into(), + port: listener.local_addr().unwrap().port(), + nick: "me".into(), + connect_timeout: Duration::from_secs(5), + on_event: Arc::new(move |e| { + let _ = tx.lock().unwrap().send(e); + }), + on_close: Arc::new(move || *c.lock().unwrap() += 1), + ..cfg + }); + (client, listener, rx, closes) + } + + fn accept(listener: &TcpListener) -> Fake { + let (s, _) = listener.accept().unwrap(); + s.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); + Fake { reader: BufReader::new(s.try_clone().unwrap()), writer: s } + } + + fn wait_for(rx: &Receiver, pred: impl Fn(&Event) -> bool) -> Event { + loop { + let e = rx.recv_timeout(Duration::from_secs(5)).expect("event"); + if pred(&e) { + return e; + } + } + } + + #[test] + fn registers_tracks_rooms_and_handles_input() { + let (client, listener, rx, closes) = harness(Config { + server_pass: "pw".into(), + auto_join_rooms: vec!["#a".into(), "#b".into()], + nickserv_identify: true, + nickserv_account: "acct".into(), + nickserv_password: "secret".into(), + ..Default::default() + }); + let c = client.clone(); + let connecting = std::thread::spawn(move || c.connect()); + let mut srv = accept(&listener); + assert_eq!(srv.line(), "PASS pw"); + assert_eq!(srv.line(), "NICK me"); + assert_eq!(srv.line(), "USER me * * me"); + srv.send(":srv 433 * me :Nickname is already in use"); + assert_eq!(srv.line(), "NICK me_"); + srv.send(":srv 001 me_ :Welcome"); + connecting.join().unwrap().unwrap(); + let e = wait_for(&rx, |e| e.kind == "system"); + assert_eq!(e.text, format!("Connected to 127.0.0.1:{}", client.cfg.port)); + assert_eq!(e.target, SERVER_TARGET); + assert_eq!(srv.line(), "PRIVMSG NickServ :IDENTIFY acct secret"); + assert_eq!(srv.line(), "JOIN #a,#b"); + assert_eq!(wait_for(&rx, |e| e.kind == "system").text, "Sent NickServ identify request"); + + srv.send("PING :xyz"); + srv.expect("PONG xyz"); + + srv.send(":me_!u@h JOIN #a"); + let e = wait_for(&rx, |e| e.kind == "join"); + assert_eq!((e.target.as_str(), e.from.as_str(), e.text.as_str()), ("#a", "me_", "me_ joined #a")); + srv.send(":srv 332 me_ #a :the topic"); + assert_eq!(wait_for(&rx, |e| e.kind == "topic").topic, "the topic"); + srv.send(":srv 353 me_ = #a :@me_ +Bob carol"); + assert_eq!(wait_for(&rx, |e| e.kind == "users").users, vec!["@me_", "+Bob", "carol"]); + srv.send(":srv 366 me_ #a :End of NAMES"); + assert_eq!(wait_for(&rx, |e| e.kind == "users").users, vec!["Bob", "carol", "me_"]); + assert_eq!(client.rooms(), vec![RoomInfo { name: "#a".into(), topic: "the topic".into(), users: 3 }]); + + srv.send(":Bob!u@h PRIVMSG #a :hi all"); + let e = wait_for(&rx, |e| e.kind == "message"); + assert_eq!((e.target.as_str(), e.from.as_str(), e.text.as_str()), ("#a", "Bob", "hi all")); + srv.send(":Bob!u@h PRIVMSG ME_ :\x01ACTION waves\x01"); + let e = wait_for(&rx, |e| e.kind == "action"); + assert_eq!((e.target.as_str(), e.text.as_str()), ("Bob", "waves")); + srv.send(":Bob!u@h NICK :Robert"); + assert_eq!(wait_for(&rx, |e| e.kind == "nick").text, "Robert"); + assert_eq!(client.users("#A"), vec!["carol", "me_", "Robert"]); + srv.send(":carol!u@h QUIT :bye"); + assert_eq!(wait_for(&rx, |e| e.kind == "quit").text, "bye"); + assert_eq!(client.users("#a"), vec!["me_", "Robert"]); + srv.send(":Robert!u@h KICK #a me_ :out"); + let e = wait_for(&rx, |e| e.kind == "kick"); + assert_eq!((e.from.as_str(), e.command.as_str(), e.text.as_str()), ("Robert", "me_", "out")); + assert!(client.rooms().is_empty()); + + client.handle_input("#a", "hello there").unwrap(); + srv.expect("PRIVMSG #a :hello there"); + let e = wait_for(&rx, |e| e.kind == "message"); + assert_eq!((e.from.as_str(), e.text.as_str()), ("me_", "hello there")); + client.handle_input("#a", "/me dances").unwrap(); + srv.expect("PRIVMSG #a :\x01ACTION dances\x01"); + client.handle_input("#a", "/join #c key").unwrap(); + srv.expect("JOIN #c key"); + client.handle_input("#a", "/part").unwrap(); + srv.expect("PART #a"); + client.handle_input("#a", "/topic").unwrap(); + srv.expect("TOPIC #a"); + client.handle_input("#a", "/names").unwrap(); + srv.expect("NAMES #a"); + client.handle_input("#a", "/mode #a +i").unwrap(); + srv.expect("MODE #a +i"); + assert_eq!( + client.handle_input(SERVER_TARGET, "hi").unwrap_err().to_string(), + "select a room or use /msg " + ); + assert_eq!(client.handle_input("#a", "/msg bob").unwrap_err().to_string(), "/msg requires a target and message"); + client.handle_input("", "/help").unwrap(); + assert!(wait_for(&rx, |e| e.kind == "system").text.starts_with("Commands: /join")); + + client.list_rooms().unwrap(); + srv.expect("LIST"); + srv.send(":srv 322 me_ #small 2 :s"); + srv.send(":srv 322 me_ #Big 9 :b"); + srv.send(":srv 322 me_ #also 2 :a"); + srv.send(":srv 323 me_ :End"); + let names: Vec<_> = wait_for(&rx, |e| e.kind == "room-list").rooms.into_iter().map(|r| r.name).collect(); + assert_eq!(names, vec!["#Big", "#also", "#small"]); + + client.close(); + client.close(); + srv.expect("QUIT :Nexus Terminal disconnected"); + let deadline = Instant::now() + Duration::from_secs(5); + while *closes.lock().unwrap() == 0 && Instant::now() < deadline { + std::thread::sleep(Duration::from_millis(10)); + } + std::thread::sleep(Duration::from_millis(100)); + assert_eq!(*closes.lock().unwrap(), 1); + assert!(client.handle_input("#a", "late").is_err()); + } + + #[test] + fn remote_close_reports_error_and_close() { + let errors = Arc::new(Mutex::new(Vec::new())); + let log = errors.clone(); + let (client, listener, rx, closes) = harness(Config { + on_error: Arc::new(move |e| log.lock().unwrap().push(e)), + ..Default::default() + }); + let c = client.clone(); + let connecting = std::thread::spawn(move || c.connect()); + let mut srv = accept(&listener); + srv.expect("USER me * * me"); + srv.send(":srv 001 me :Welcome"); + connecting.join().unwrap().unwrap(); + srv.send("ERROR :Closing link"); + assert_eq!(wait_for(&rx, |e| e.kind == "error").text, "Closing link"); + drop(srv); + assert_eq!(wait_for(&rx, |e| e.kind == "error").text, "connection closed by server"); + std::thread::sleep(Duration::from_millis(100)); + assert_eq!(*errors.lock().unwrap(), vec!["connection closed by server"]); + assert_eq!(*closes.lock().unwrap(), 1); + } + + #[test] + fn connect_times_out_without_welcome() { + let (client, listener, _rx, closes) = harness(Config::default()); + let client = Client::new(Config { connect_timeout: Duration::from_millis(300), ..client.cfg.clone() }); + let err = client.connect().unwrap_err().to_string(); + assert_eq!(err, "IRC connection timed out after 300ms"); + drop(listener); + assert_eq!(*closes.lock().unwrap(), 0); + } + + #[test] + fn connector_supplies_the_transport() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + let used = Arc::new(Mutex::new(false)); + let flag = used.clone(); + let client = Client::new(Config { + server: "unresolvable.invalid".into(), + nick: "me".into(), + connect_timeout: Duration::from_secs(5), + connector: Some(Arc::new(move || { + *flag.lock().unwrap() = true; + let s = TcpStream::connect(addr)?; + s.set_read_timeout(Some(Duration::from_millis(20)))?; + Ok(Box::new(s) as Box) + })), + ..Default::default() + }); + let c = client.clone(); + let connecting = std::thread::spawn(move || c.connect()); + let mut srv = accept(&listener); + srv.expect("USER me * * me"); + srv.send(":srv 001 me :Welcome"); + connecting.join().unwrap().unwrap(); + assert!(*used.lock().unwrap()); + client.handle_input("", "/quit gone").unwrap(); + srv.expect("QUIT gone"); + } + + #[test] + fn dial_failure_is_reported() { + let port = TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port(); + let client = Client::new(Config { server: "127.0.0.1".into(), port, nick: "me".into(), ..Default::default() }); + let err = client.connect().unwrap_err().to_string(); + assert!(err.starts_with(&format!("dial tcp 127.0.0.1:{port}:")), "{err}"); + } +} diff --git a/rust/core/src/clients/mod.rs b/rust/core/src/clients/mod.rs index d811535..069865f 100644 --- a/rust/core/src/clients/mod.rs +++ b/rust/core/src/clients/mod.rs @@ -6,6 +6,9 @@ //! - `write`/`resize`/`close` are callable from any thread; `close` is idempotent and must not //! fire `on_close` re-entrantly while holding locks. +pub mod github; +pub mod irc; + use std::sync::Arc; pub type DataFn = Arc; diff --git a/rust/core/src/ircformat.rs b/rust/core/src/ircformat.rs new file mode 100644 index 0000000..115888e --- /dev/null +++ b/rust/core/src/ircformat.rs @@ -0,0 +1,469 @@ +//! mIRC formatting (^B ^] ^^ ^_ ^V ^Q ^O ^C fg,bg ^D hex) to styled spans, the nick colour hash +//! and the IRC event line layout. Ported from the Wails frontend's `irc.js`. +//! +//! Colours are xterm-256 palette indices: IRC colour codes are mapped through `IRC_COLOURS`. + +use crate::clients::irc::Event; + +/// IRC colours 00-98 mapped to the xterm 256-colour palette (modern IRC formatting spec). +/// Colour 99, and anything out of range, means the terminal default. +pub const IRC_COLOURS: [u8; 99] = [ + 15, 0, 4, 2, 9, 1, 5, 130, 11, 10, 6, 14, 12, 13, 8, 7, // + 52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89, // + 88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125, // + 124, 166, 184, 106, 34, 49, 37, 33, 19, 129, 127, 161, // + 196, 208, 226, 154, 46, 86, 51, 75, 21, 171, 201, 198, // + 203, 215, 227, 191, 83, 122, 87, 111, 63, 177, 207, 205, // + 217, 223, 229, 193, 157, 158, 159, 153, 147, 183, 219, 212, // + 16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231, +]; + +/// Palette indices used for nicks. +pub const NICK_COLOURS: [u8; 12] = [33, 39, 45, 69, 75, 81, 111, 141, 177, 207, 214, 220]; + +const GREY: u8 = 244; +const RED: u8 = 203; +const GREEN: u8 = 83; +const YELLOW: u8 = 221; +const CYAN: u8 = 81; +const MAGENTA: u8 = 177; + +/// Text style. `fg`/`bg` are xterm-256 indices; `fg_rgb`/`bg_rgb` come from ^D hex colours. +/// At most one of `fg`/`fg_rgb` (and of `bg`/`bg_rgb`) is set. `None` is the default colour. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Style { + pub fg: Option, + pub bg: Option, + pub fg_rgb: Option<[u8; 3]>, + pub bg_rgb: Option<[u8; 3]>, + pub bold: bool, + pub dim: bool, + pub italic: bool, + pub underline: bool, + pub reverse: bool, + pub strike: bool, + pub monospace: bool, +} + +impl Style { + fn fg(colour: u8) -> Style { + Style { fg: Some(colour), ..Style::default() } + } + fn set_fg(&mut self, colour: u8) { + self.fg = Some(colour); + self.fg_rgb = None; + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Span { + pub text: String, + pub style: Style, +} + +/// Spans under construction; `style` applies to the next text pushed. +#[derive(Default)] +struct Line { + spans: Vec, + style: Style, +} + +impl Line { + fn push(&mut self, text: &str) { + if text.is_empty() { + return; + } + match self.spans.last_mut() { + Some(last) if last.style == self.style => last.text.push_str(text), + _ => self.spans.push(Span { text: text.to_string(), style: self.style }), + } + } + + /// `text` in `style`, then back to the default style. + fn styled(&mut self, style: Style, text: &str) { + self.style = style; + self.push(text); + self.style = Style::default(); + } + + /// A bold, nick-coloured name layered on the current style, then reset. + fn nick(&mut self, nick: &str) { + let mut style = self.style; + style.bold = true; + style.set_fg(nick_colour(nick)); + self.styled(style, nick); + } + + /// Applies mIRC formatting codes in `value` on top of the current style. + fn mirc(&mut self, value: &str) { + let input: Vec = strip_terminal_escapes(value).chars().collect(); + // Toggle states start off regardless of the inherited style, as in irc.js. + let (mut bold, mut italic, mut strike, mut underline, mut reverse, mut mono) = (false, false, false, false, false, false); + let mut i = 0; + while i < input.len() { + let c = input[i]; + i += 1; + match c { + '\x02' => { + bold = !bold; + self.style.bold = bold; + if !bold { + self.style.dim = false; // SGR 22 clears both + } + } + '\x1d' => { + italic = !italic; + self.style.italic = italic; + } + '\x1e' => { + strike = !strike; + self.style.strike = strike; + } + '\x1f' => { + underline = !underline; + self.style.underline = underline; + } + '\x16' => { + reverse = !reverse; + self.style.reverse = reverse; + } + '\x11' => { + mono = !mono; + self.style.monospace = mono; + } + '\x0f' => { + self.style = Style::default(); + (bold, italic, strike, underline, reverse, mono) = (false, false, false, false, false, false); + } + '\x03' | '\x04' => { + let hex = c == '\x04'; + let read = |at: usize| if hex { read_hex(&input, at) } else { read_decimal(&input, at) }; + let Some((fg, len)) = read(i) else { + self.reset_colours(); + continue; + }; + i += len; + self.apply_colour(fg, false); + if input.get(i) != Some(&',') { + continue; + } + match read(i + 1) { + Some((bg, len)) => { + i += len + 1; + self.apply_colour(bg, true); + } + None => { + self.push(","); + i += 1; + } + } + } + c if (c as u32) < 0x20 || c as u32 == 0x7f => {} + c => self.push(c.encode_utf8(&mut [0; 4])), + } + } + } + + fn reset_colours(&mut self) { + (self.style.fg, self.style.bg, self.style.fg_rgb, self.style.bg_rgb) = (None, None, None, None); + } + + fn apply_colour(&mut self, colour: Colour, background: bool) { + let s = &mut self.style; + let (index, rgb) = if background { (&mut s.bg, &mut s.bg_rgb) } else { (&mut s.fg, &mut s.fg_rgb) }; + match colour { + Colour::Indexed(code) => { + *index = IRC_COLOURS.get(code as usize).copied(); + *rgb = None; + } + Colour::Rgb(value) => { + *rgb = Some(value); + *index = None; + } + } + } +} + +enum Colour { + Indexed(u8), + Rgb([u8; 3]), +} + +fn read_decimal(input: &[char], at: usize) -> Option<(Colour, usize)> { + let digits: String = input.iter().skip(at).take(2).take_while(|c| c.is_ascii_digit()).collect(); + if digits.is_empty() { + return None; + } + Some((Colour::Indexed(digits.parse().ok()?), digits.len())) +} + +fn read_hex(input: &[char], at: usize) -> Option<(Colour, usize)> { + let hex: String = input.get(at..at + 6)?.iter().collect(); + if !hex.chars().all(|c| c.is_ascii_hexdigit()) { + return None; + } + let byte = |i: usize| u8::from_str_radix(&hex[i..i + 2], 16).ok(); + Some((Colour::Rgb([byte(0)?, byte(2)?, byte(4)?]), 6)) +} + +/// Removes untrusted terminal sequences (OSC, CSI, other ESC pairs) and C1 controls, applying the +/// same four passes as irc.js. +fn strip_terminal_escapes(value: &str) -> String { + // OSC: ESC ] ... (BEL | ESC \), greedy like the JS regex. + let mut s = String::new(); + let mut rest = value; + while let Some(pos) = rest.find("\x1b]") { + let body = &rest[pos + 2..]; + let run_end = body.find('\x07'); + let end = match run_end { + Some(bel) => Some(bel + 1), + None => body.rfind("\x1b\\").map(|st| st + 2), + }; + match end { + Some(end) => { + s.push_str(&rest[..pos]); + rest = &body[end..]; + } + None => { + s.push_str(&rest[..pos + 2]); + rest = body; + } + } + } + s.push_str(rest); + + // CSI: ESC [ params intermediates final. + let chars: Vec = s.chars().collect(); + let mut csi = String::new(); + let mut i = 0; + while i < chars.len() { + if chars[i] == '\x1b' && chars.get(i + 1) == Some(&'[') { + let mut j = i + 2; + while chars.get(j).is_some_and(|c| ('0'..='?').contains(c)) { + j += 1; + } + while chars.get(j).is_some_and(|c| (' '..='/').contains(c)) { + j += 1; + } + if chars.get(j).is_some_and(|c| ('@'..='~').contains(c)) { + i = j + 1; + continue; + } + } + csi.push(chars[i]); + i += 1; + } + + // ESC plus any character but a line terminator. + let chars: Vec = csi.chars().collect(); + let mut out = String::new(); + let mut i = 0; + while i < chars.len() { + if chars[i] == '\x1b' && chars.get(i + 1).is_some_and(|c| !matches!(c, '\n' | '\r' | '\u{2028}' | '\u{2029}')) { + i += 2; + continue; + } + out.push(chars[i]); + i += 1; + } + out.retain(|c| !('\u{80}'..='\u{9f}').contains(&c)); + out +} + +/// mIRC-formatted text as styled spans, starting from the default style. +pub fn format_text(value: &str) -> Vec { + let mut line = Line::default(); + line.mirc(value); + line.spans +} + +/// The text with all formatting removed. +pub fn plain_text(value: &str) -> String { + format_text(value).into_iter().map(|s| s.text).collect() +} + +/// The palette index for a nick (Java-style string hash over code points). +pub fn nick_colour(nick: &str) -> u8 { + let hash = nick.chars().fold(0u32, |h, c| h.wrapping_mul(31).wrapping_add(c as u32)); + NICK_COLOURS[hash as usize % NICK_COLOURS.len()] +} + +/// One chat line for an event: `[time] body`, laid out per event type as the Wails viewer did. +pub fn format_event(event: &Event, time: &str) -> Vec { + let from = plain_text(&event.from); + let subject = plain_text(&event.command); + let mut l = Line::default(); + l.styled(Style { dim: true, ..Style::fg(GREY) }, &format!("[{time}]")); + l.push(" "); + + let nick_or = |l: &mut Line, fallback: &str| { + if from.is_empty() { l.push(fallback) } else { l.nick(&from) } + }; + let suffix = |l: &mut Line| { + if !event.text.is_empty() { + l.push(" ("); + l.mirc(&event.text); + l.push(")"); + } + }; + let magenta_italic = Style { italic: true, ..Style::fg(MAGENTA) }; + + match event.kind.as_str() { + "message" => { + l.styled(Style::fg(GREY), "<"); + nick_or(&mut l, ""); + l.styled(Style::fg(GREY), ">"); + l.push(" "); + l.mirc(&event.text); + } + "action" => { + l.style = magenta_italic; + l.push("* "); + nick_or(&mut l, ""); + l.style = magenta_italic; + l.push(" "); + l.mirc(&event.text); + } + "notice" => { + l.style = Style::fg(YELLOW); + l.push("-"); + nick_or(&mut l, "notice"); + l.styled(Style::fg(YELLOW), "-"); + l.push(" "); + l.mirc(&event.text); + } + "join" => { + l.styled(Style::fg(GREEN), "-->"); + l.push(" "); + nick_or(&mut l, "user"); + l.push(" joined"); + } + "part" | "quit" => { + l.styled(Style::fg(RED), "<--"); + l.push(" "); + nick_or(&mut l, "user"); + l.push(if event.kind == "part" { " left" } else { " quit" }); + suffix(&mut l); + } + "kick" => { + l.styled(Style::fg(RED), "<--"); + l.push(" "); + l.styled(Style { bold: true, ..Style::default() }, if subject.is_empty() { "user" } else { &subject }); + l.push(" was kicked by "); + nick_or(&mut l, "server"); + suffix(&mut l); + } + "nick" => { + nick_or(&mut l, "user"); + l.push(" is now known as "); + l.styled(Style { bold: true, ..Style::default() }, &plain_text(&event.text)); + } + "topic" | "error" => { + let (colour, label) = if event.kind == "topic" { (CYAN, "topic:") } else { (RED, "error:") }; + l.styled(Style { bold: true, ..Style::fg(colour) }, label); + l.push(" "); + l.mirc(&event.text); + } + _ => { + l.style = Style::fg(CYAN); + l.mirc(&event.text); + } + } + l.spans +} + +#[cfg(test)] +mod tests { + use super::*; + + fn span(text: &str, style: Style) -> Span { + Span { text: text.into(), style } + } + + #[test] + fn toggles() { + let b = Style { bold: true, ..Style::default() }; + let i = Style { italic: true, ..Style::default() }; + let u = Style { underline: true, ..Style::default() }; + let d = Style::default(); + assert_eq!( + format_text("plain \x02bold\x02 \x1ditalic\x1d \x1funderlined\x1f"), + vec![span("plain ", d), span("bold", b), span(" ", d), span("italic", i), span(" ", d), span("underlined", u)] + ); + let all = format_text("\x16\x1e\x11x\x0fy"); + assert_eq!(all[0].style, Style { reverse: true, strike: true, monospace: true, ..Style::default() }); + assert_eq!(all[1], span("y", d)); + } + + #[test] + fn indexed_colours() { + assert_eq!(format_text("\x0304red\x03 default"), vec![span("red", Style::fg(9)), span(" default", Style::default())]); + assert_eq!( + format_text("\x0352extended \x0303,01matrix"), + vec![span("extended ", Style::fg(196)), span("matrix", Style { bg: Some(0), ..Style::fg(2) })] + ); + assert_eq!(format_text("\x0399default"), vec![span("default", Style::default())]); + } + + #[test] + fn hex_colours() { + assert_eq!( + format_text("\x04ff8800orange\x04 default"), + vec![ + span("orange", Style { fg_rgb: Some([255, 136, 0]), ..Style::default() }), + span(" default", Style::default()) + ] + ); + let s = format_text("\x0400ff00,0000FFx"); + assert_eq!(s[0].style, Style { fg_rgb: Some([0, 255, 0]), bg_rgb: Some([0, 0, 255]), ..Style::default() }); + } + + #[test] + fn colour_without_background_keeps_comma() { + assert_eq!(format_text("\x0304,comma"), vec![span(",comma", Style::fg(9))]); + } + + #[test] + fn plain_text_strips_formatting() { + assert_eq!(plain_text("\x02bold\x02 \x0304red\x03"), "bold red"); + } + + #[test] + fn untrusted_terminal_sequences_are_removed() { + assert_eq!(plain_text("before\x1b[31munsafe\x1b]0;title\x07after"), "beforeunsafeafter"); + assert_eq!(plain_text("a\x1b]8;;x\x1b\\b\u{9b}c\x1bZd\x01"), "abcd"); + } + + #[test] + fn nick_colour_matches_js_hash() { + // Hashes computed with the irc.js function under node. + assert_eq!(nick_colour(""), 33); + assert_eq!(nick_colour("bob"), NICK_COLOURS[97717 % 12]); + assert_eq!(nick_colour("alice"), NICK_COLOURS[92903040 % 12]); + } + + #[test] + fn event_lines() { + let message = format_event( + &Event { kind: "message".into(), from: "alice".into(), text: "hello \x02world\x02".into(), ..Default::default() }, + "12:34:56", + ); + assert_eq!(message[0], span("[12:34:56]", Style { dim: true, ..Style::fg(GREY) })); + assert!(message.iter().any(|s| s.text == "world" && s.style.bold)); + let nick = message.iter().find(|s| s.text == "alice").unwrap(); + assert_eq!(nick.style, Style { bold: true, ..Style::fg(nick_colour("alice")) }); + let text: String = message.iter().map(|s| s.text.as_str()).collect(); + assert_eq!(text, "[12:34:56] hello world"); + + let action = format_event(&Event { kind: "action".into(), from: "bob".into(), text: "waves".into(), ..Default::default() }, "01:02:03"); + assert!(action.iter().any(|s| s.text.ends_with("waves") && s.style.italic && s.style.fg == Some(MAGENTA))); + + let kick = format_event( + &Event { kind: "kick".into(), from: "op".into(), command: "eve".into(), text: "spam".into(), ..Default::default() }, + "t", + ); + assert_eq!(kick.iter().map(|s| s.text.as_str()).collect::(), "[t] <-- eve was kicked by op (spam)"); + let system = format_event(&Event { kind: "system".into(), text: "hi".into(), ..Default::default() }, "t"); + assert_eq!(system.last().unwrap(), &span("hi", Style::fg(CYAN))); + } +} diff --git a/rust/core/src/lib.rs b/rust/core/src/lib.rs index 5eaec28..46fb6d2 100644 --- a/rust/core/src/lib.rs +++ b/rust/core/src/lib.rs @@ -4,6 +4,7 @@ pub mod clients; pub mod config; pub mod exportimport; +pub mod ircformat; pub mod models; pub mod secrets; pub mod storage; From 55e65410ceae10b59ca9054f101ff4734c71bc4f Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:23:05 +0100 Subject: [PATCH 12/45] rust: use terminalenv for SSH and local shell env --- rust/core/src/clients/local.rs | 45 ++++------------------------------ rust/core/src/clients/ssh.rs | 12 +-------- 2 files changed, 6 insertions(+), 51 deletions(-) diff --git a/rust/core/src/clients/local.rs b/rust/core/src/clients/local.rs index 3192906..eadbfed 100644 --- a/rust/core/src/clients/local.rs +++ b/rust/core/src/clients/local.rs @@ -17,34 +17,6 @@ pub fn default_shell() -> String { std::env::var("SHELL").ok().filter(|s| !s.is_empty()).unwrap_or_else(|| "/bin/sh".into()) } -fn is_utf8_locale(v: &str) -> bool { - let v = v.to_lowercase(); - v.contains("utf-8") || v.contains("utf8") -} - -/// TERM=xterm-256color and a UTF-8 character locale, keeping the user's environment otherwise. -pub fn local_shell_env(env: &mut Vec<(String, String)>) { - let set = |env: &mut Vec<(String, String)>, k: &str, v: &str| { - env.retain(|(ek, _)| ek != k); - env.push((k.into(), v.into())); - }; - set(env, "TERM", "xterm-256color"); - let get = |k: &str| env.iter().find(|(ek, _)| ek == k).map(|(_, v)| v.clone()); - let effective = get("LC_ALL").or_else(|| get("LC_CTYPE")).or_else(|| get("LANG")); - if effective.as_deref().is_some_and(is_utf8_locale) { - return; - } - let locale = if cfg!(target_os = "macos") { "en_US.UTF-8" } else { "C.UTF-8" }; - let key = if get("LC_ALL").is_some() { - "LC_ALL" - } else if get("LC_CTYPE").is_some() || get("LANG").is_some() { - "LC_CTYPE" - } else { - "LANG" - }; - set(env, key, locale); -} - struct Running { master: Box, writer: Box, @@ -77,11 +49,12 @@ impl LocalShell { .openpty(PtySize { rows: st.3, cols: st.2, pixel_width: 0, pixel_height: 0 }) .map_err(|e| format!("failed to start shell {:?}: {e}", self.shell))?; let mut cmd = CommandBuilder::new(&self.shell); - let mut env: Vec<(String, String)> = std::env::vars().collect(); - local_shell_env(&mut env); cmd.env_clear(); - for (k, v) in env { - cmd.env(k, v); + let env = crate::terminalenv::local_shell_env(std::env::vars().map(|(k, v)| format!("{k}={v}")).collect()); + for kv in env { + if let Some((k, v)) = kv.split_once('=') { + cmd.env(k, v); + } } cmd.cwd(&st.1); let mut child = pair.slave.spawn_command(cmd).map_err(|e| format!("failed to start shell {:?}: {e}", self.shell))?; @@ -215,12 +188,4 @@ mod tests { } assert_eq!(sh.working_directory(), PathBuf::from("/")); } - - #[test] - fn env_gets_term_and_utf8() { - let mut e = vec![("LANG".to_string(), "C".to_string())]; - local_shell_env(&mut e); - assert!(e.contains(&("TERM".into(), "xterm-256color".into()))); - assert!(e.iter().any(|(k, v)| k == "LC_CTYPE" && v.ends_with("UTF-8"))); - } } diff --git a/rust/core/src/clients/ssh.rs b/rust/core/src/clients/ssh.rs index addc97a..f5b5e4f 100644 --- a/rust/core/src/clients/ssh.rs +++ b/rust/core/src/clients/ssh.rs @@ -158,16 +158,6 @@ async fn connect_agent() -> Result { } } -/// Profile env vars plus a UTF-8 LC_CTYPE when the profile sets no locale (Go's SSHEnvVars). -fn ssh_env_vars(env: &std::collections::BTreeMap) -> std::collections::BTreeMap { - let mut out: std::collections::BTreeMap = - env.iter().filter(|(k, _)| !k.trim().is_empty()).map(|(k, v)| (k.trim().to_string(), v.clone())).collect(); - if !["LC_ALL", "LC_CTYPE", "LANG"].iter().any(|k| out.contains_key(*k)) { - out.insert("LC_CTYPE".into(), if cfg!(target_os = "macos") { "en_US.UTF-8" } else { "C.UTF-8" }.into()); - } - out -} - fn is_totp_prompt(q: &str) -> bool { let q = q.to_lowercase(); [ @@ -496,7 +486,7 @@ impl SshClient { let handle = Arc::new(handle); let ch = handle.channel_open_session().await.map_err(|e| format!("failed to create SSH session: {e}"))?; - for (k, v) in ssh_env_vars(&sc.env_vars) { + for (k, v) in crate::terminalenv::ssh_env_vars(&sc.env_vars) { let _ = ch.set_env(false, k, v).await; } let term = if sc.terminal_type.is_empty() { "xterm-256color" } else { &sc.terminal_type }; From cbd83489b274df32bfd084c8135911bce0b7bb1e Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:33:17 +0100 Subject: [PATCH 13/45] rust: session manager (lifecycle, reconnect backoff, login scripts, recording, host-key policy, credential modes) and SSH tunnels --- rust/app/src/main.rs | 6 + rust/core/src/clients/ssh.rs | 34 +- rust/core/src/lib.rs | 1 + rust/core/src/secrets.rs | 50 ++ rust/core/src/sessions.rs | 1163 ++++++++++++++++++++++++++++++++++ rust/core/tests/sessions.rs | 131 ++++ 6 files changed, 1383 insertions(+), 2 deletions(-) create mode 100644 rust/core/src/sessions.rs create mode 100644 rust/core/tests/sessions.rs diff --git a/rust/app/src/main.rs b/rust/app/src/main.rs index f328e4d..660c693 100644 --- a/rust/app/src/main.rs +++ b/rust/app/src/main.rs @@ -1 +1,7 @@ +//! Nexus Terminal: SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local terminals. +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +mod terminal; +mod terminal_themes; + fn main() {} diff --git a/rust/core/src/clients/ssh.rs b/rust/core/src/clients/ssh.rs index f5b5e4f..839ccb6 100644 --- a/rust/core/src/clients/ssh.rs +++ b/rust/core/src/clients/ssh.rs @@ -432,7 +432,33 @@ impl SshClient { /// Connects, authenticates and starts the shell. Blocks the calling thread. pub fn connect(self: &Arc, check: HostKeyCheck, password: Option, passphrase: Option) -> Result<()> { let me = self.clone(); - rt().block_on(async move { me.connect_async(check, password, passphrase).await }) + rt().block_on(async move { me.connect_async(check, password, passphrase, true).await }) + } + + /// Connects and authenticates without opening a shell, for tunnelling another protocol. + pub fn connect_tunnel_only(self: &Arc, check: HostKeyCheck, password: Option, passphrase: Option) -> Result<()> { + let me = self.clone(); + rt().block_on(async move { me.connect_async(check, password, passphrase, false).await }) + } + + /// A `direct-tcpip` channel to `host:port` presented as an ordinary loopback TCP stream, + /// so blocking clients (IRC, VNC) can use it with read timeouts. + pub fn open_tunnel(&self, host: &str, port: u16) -> Result { + let h = self.handle()?; + let host = host.to_string(); + let listener = std::net::TcpListener::bind("127.0.0.1:0")?; + let addr = listener.local_addr()?; + let ch = rt().block_on(h.channel_open_direct_tcpip(host.clone(), port as u32, "127.0.0.1", 0)).map_err(|e| format!("SSH tunnel to {host}:{port} failed: {e}"))?; + let client = std::net::TcpStream::connect(addr)?; + let (server, _) = listener.accept()?; + server.set_nonblocking(true)?; + rt().spawn(async move { + if let Ok(mut local) = tokio::net::TcpStream::from_std(server) { + let mut remote = ch.into_stream(); + let _ = tokio::io::copy_bidirectional(&mut local, &mut remote).await; + } + }); + Ok(client) } async fn handshake( @@ -454,7 +480,7 @@ impl SshClient { } } - async fn connect_async(self: &Arc, check: HostKeyCheck, password: Option, passphrase: Option) -> Result<()> { + async fn connect_async(self: &Arc, check: HostKeyCheck, password: Option, passphrase: Option, shell: bool) -> Result<()> { let sc = self.sc(); let p = &self.profile; let port = if p.port == 0 { 22 } else { p.port as u16 }; @@ -484,6 +510,10 @@ impl SshClient { ) .await?; let handle = Arc::new(handle); + if !shell { + *self.handle.lock().unwrap() = Some(handle); + return Ok(()); + } let ch = handle.channel_open_session().await.map_err(|e| format!("failed to create SSH session: {e}"))?; for (k, v) in crate::terminalenv::ssh_env_vars(&sc.env_vars) { diff --git a/rust/core/src/lib.rs b/rust/core/src/lib.rs index 4d5c119..9b1131f 100644 --- a/rust/core/src/lib.rs +++ b/rust/core/src/lib.rs @@ -9,6 +9,7 @@ pub mod keyfiles; pub mod models; pub mod recording; pub mod secrets; +pub mod sessions; pub mod sshconfig; pub mod storage; pub mod terminalenv; diff --git a/rust/core/src/secrets.rs b/rust/core/src/secrets.rs index 0e2203b..9dc55f3 100644 --- a/rust/core/src/secrets.rs +++ b/rust/core/src/secrets.rs @@ -226,6 +226,56 @@ impl Keychain { } } +/// Keychain first, vault second — how every credential lookup in the app works. +pub struct Secrets { + pub vault: Vault, + pub keychain: Keychain, + /// Probed once at startup (the probe writes an item, so don't repeat it per lookup). + pub keychain_ok: bool, +} + +impl Secrets { + pub fn new(vault: Vault, keychain: Keychain) -> Secrets { + let keychain_ok = keychain.available(); + Secrets { vault, keychain, keychain_ok } + } + + pub fn get(&self, key: &str) -> Option { + if self.keychain_ok + && let Some(v) = self.keychain.get(key).filter(|v| !v.is_empty()) + { + return Some(v); + } + self.vault.get(key).ok().filter(|v| !v.is_empty()) + } + + /// Stores where possible: the keychain, else the vault if it's unlocked. + pub fn set(&self, key: &str, value: &str) { + let _ = self.set_checked(key, value); + } + + pub fn set_checked(&self, key: &str, value: &str) -> Result<()> { + if self.keychain_ok && self.keychain.set(key, value).is_ok() { + return Ok(()); + } + if !self.vault.is_unlocked() { + return Err("secret store is locked; unlock it first".into()); + } + self.vault.set(key, value) + } + + pub fn delete(&self, key: &str) { + if self.keychain_ok { + self.keychain.delete(key); + } + let _ = self.vault.delete(key); + } + + pub fn delete_profile(&self, profile_id: &str) { + let _ = self.vault.delete_prefix(&format!("profile:{profile_id}:")); + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/rust/core/src/sessions.rs b/rust/core/src/sessions.rs new file mode 100644 index 0000000..a4a237e --- /dev/null +++ b/rust/core/src/sessions.rs @@ -0,0 +1,1163 @@ +//! The session manager: one entry per open tab, each owning a protocol client. A port of +//! internal/sessions — same lifecycle, reconnect backoff, login scripts, recording hooks and +//! credential resolution — reporting to the UI through `Event`s. + +use crate::clients::{self, Callbacks, github, irc, raw, serial, telnet}; +use crate::clients::local::LocalShell; +use crate::clients::ssh::{HostKeyCheck, HostKeyInfo, SecretFn, SshClient}; +use crate::models::*; +use crate::recording::{self, Recorder}; +use crate::secrets::Secrets; +use crate::storage::Store; +use crate::Result; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +/// Everything the UI hears about. Prompts carry their reply channel; dropping it cancels. +pub enum Event { + Data { session: String, data: Vec }, + State { session: String, state: SessionState, error: String, retry: Option }, + Closed { session: String }, + RawData { session: String, data: Vec, direction: String, timestamp: i64 }, + Debug { session: String, message: String }, + GithubStatus { session: String, status: String }, + GithubRepos { session: String, login: String, repos: Vec }, + GithubError { session: String, error: String }, + Irc { session: String, event: irc::Event }, + ForwardStarted { session: String, forward: PortForward }, + ForwardStopped { session: String, forward_id: String }, + ForwardError { session: String, forward_id: String, label: String, error: String }, + RecordingStarted { session: String }, + RecordingStopped { session: String, duration: f64, events: usize }, + SftpProgress { session: String, progress: clients::sftp::Progress }, + SecretPrompt { session: String, title: String, message: String, reply: mpsc::Sender }, + HostKeyVerify { session: String, host: String, key_type: String, fingerprint: String, reply: mpsc::Sender }, + VncReady { session: String }, + VncFrame { session: String }, +} + +#[derive(Clone, Copy, Debug)] +pub struct Retry { + pub attempt: i64, + pub max: i64, + pub delay_secs: u64, +} + +pub type EventSink = Arc; +/// Asks the user for a credential (blocks the calling worker thread). +pub type Prompt = Arc Result + Send + Sync>; + +#[derive(Clone)] +enum Client { + None, + Ssh(Arc), + Telnet(Arc), + Raw(Arc), + Serial(Arc), + Local(Arc), + Github(Arc, Arc), + Irc(Arc, Option>), + Vnc(Arc, Option>), +} + +impl Client { + fn close(&self) { + match self { + Client::None => {} + Client::Ssh(c) => c.close(), + Client::Telnet(c) => c.close(), + Client::Raw(c) => c.close(), + Client::Serial(c) => c.close(), + Client::Local(c) => c.close(), + Client::Github(_, stop) => stop.store(true, Ordering::SeqCst), + Client::Irc(c, tunnel) => { + c.close(); + if let Some(t) = tunnel { + t.close(); + } + } + Client::Vnc(c, tunnel) => { + c.close(); + if let Some(t) = tunnel { + t.close(); + } + } + } + } +} + +#[derive(Default)] +struct LoginBuf { + active: bool, + buf: Vec, +} + +struct Session { + info: SessionInfo, + /// Distinguishes this Session object from a later one with the same id (Go compared pointers). + epoch: u64, + closed: bool, + client: Client, + profile: Option, + local_shell: String, + local_cwd: Option, + recorder: Option>, + reconnect_count: i64, + cancel_reconnect: Option>, + pending_resize: Option<(u16, u16)>, + login: Arc>, +} + +pub struct Manager { + sessions: Mutex>, + store: Arc, + secrets: Arc, + recordings: recording::Store, + sink: Mutex>, + next_epoch: std::sync::atomic::AtomicU64, +} + +fn err(msg: String) -> Result { + Err(msg.into()) +} + +impl Manager { + pub fn new(store: Arc, secrets: Arc, recordings_dir: PathBuf) -> Arc { + Arc::new(Manager { + sessions: Mutex::new(HashMap::new()), + store, + secrets, + recordings: recording::Store::new(recordings_dir), + sink: Mutex::new(None), + next_epoch: 1.into(), + }) + } + + pub fn set_sink(&self, sink: EventSink) { + *self.sink.lock().unwrap() = Some(sink); + } + + fn emit(&self, e: Event) { + let sink = self.sink.lock().unwrap().clone(); + if let Some(s) = sink { + s(e); + } + } + + fn emit_state(&self, id: &str, state: SessionState, error: &str) { + self.emit(Event::State { session: id.into(), state, error: error.into(), retry: None }); + } + + /// True while `id` still names the Session object with `epoch` and it isn't closed. + fn is_current(&self, id: &str, epoch: u64) -> bool { + self.sessions.lock().unwrap().get(id).is_some_and(|s| s.epoch == epoch && !s.closed) + } + + fn with(&self, id: &str, f: impl FnOnce(&mut Session) -> R) -> Result { + let mut map = self.sessions.lock().unwrap(); + match map.get_mut(id) { + Some(s) => Ok(f(s)), + None => err(format!("sessions: session {id:?} not found")), + } + } + + fn client(&self, id: &str) -> Result { + self.with(id, |s| s.client.clone()) + } + + fn new_session(&self, info: SessionInfo, profile: Option, shell: String) -> u64 { + let epoch = self.next_epoch.fetch_add(1, Ordering::SeqCst); + let s = Session { + info, + epoch, + closed: false, + client: Client::None, + profile, + local_shell: shell, + local_cwd: None, + recorder: None, + reconnect_count: 0, + cancel_reconnect: None, + pending_resize: None, + login: Arc::default(), + }; + let id = s.info.id.clone(); + self.sessions.lock().unwrap().insert(id, s); + epoch + } + + /// Opens a session for `profile` and connects it on a worker thread. Returns the session id + /// immediately; progress arrives as `Event::State`. + pub fn connect(self: &Arc, profile: Profile, prompt: Prompt) -> String { + let id = new_id(); + let info = SessionInfo { + id: id.clone(), + profile_id: profile.id.clone(), + profile_name: profile.display_name.clone(), + protocol: profile.protocol, + host: profile.host.clone(), + port: profile.port, + state: SessionState::Connecting, + tab_name: profile.tab_name(), + pinned: false, + connected_at: now(), + error: String::new(), + }; + let epoch = self.new_session(info, Some(profile.clone()), String::new()); + self.emit_state(&id, SessionState::Connecting, ""); + let me = self.clone(); + let id2 = id.clone(); + std::thread::spawn(move || { + let r = me.connect_protocol(&id2, epoch, &profile, prompt); + me.finish_connect(&id2, epoch, r, |me| me.store.add_audit("connect", &profile.id, &id2, &format!("Connected via {} to {}", profile.protocol.id(), profile.host))); + }); + id + } + + fn finish_connect(&self, id: &str, epoch: u64, r: Result, on_ok: impl FnOnce(&Self)) { + let mut map = self.sessions.lock().unwrap(); + let current = map.get_mut(id).filter(|s| s.epoch == epoch && !s.closed); + match (r, current) { + (Ok(client), None) => { + drop(map); + client.close(); + } + (Err(_), None) => {} + (Err(e), Some(s)) => { + s.info.state = SessionState::Error; + s.info.error = e.to_string(); + drop(map); + self.emit_state(id, SessionState::Error, &e.to_string()); + } + (Ok(client), Some(s)) => { + s.client = client.clone(); + s.info.state = SessionState::Connected; + s.info.connected_at = now(); + s.reconnect_count = 0; + let pending = s.pending_resize.take(); + drop(map); + if let Some((c, r)) = pending { + let _ = self.resize_client(&client, c, r); + } + self.emit_state(id, SessionState::Connected, ""); + on_ok(self); + } + } + } + + fn connect_protocol(self: &Arc, id: &str, epoch: u64, p: &Profile, prompt: Prompt) -> Result { + match p.protocol { + Protocol::Ssh => self.connect_ssh(id, epoch, p, prompt), + Protocol::Telnet => { + let debug = p.telnet.as_ref().is_some_and(|t| t.negotiation_debug).then(|| { + let (me, id) = (self.clone(), id.to_string()); + Arc::new(move |m: String| me.emit(Event::Debug { session: id.clone(), message: m })) as clients::DebugFn + }); + let c = Arc::new(telnet::TelnetClient::new(p, self.terminal_callbacks(id, epoch), debug)); + c.connect()?; + self.start_login_scripts(id, epoch, p.telnet.as_ref().map(|t| t.login_scripts.clone()).unwrap_or_default()); + Ok(Client::Telnet(c)) + } + Protocol::Raw => { + let cb = self.terminal_callbacks(id, epoch); + let (me, sid) = (self.clone(), id.to_string()); + let on_data: raw::RawDataFn = Arc::new(move |d, dir, ts| { + me.emit(Event::RawData { session: sid.clone(), data: d.to_vec(), direction: dir.into(), timestamp: ts }) + }); + let c = Arc::new(raw::RawClient::new(p, on_data, cb.on_error, cb.on_close)); + c.connect()?; + Ok(Client::Raw(c)) + } + Protocol::Serial => { + let c = Arc::new(serial::SerialClient::new(p, self.terminal_callbacks(id, epoch))); + c.connect()?; + Ok(Client::Serial(c)) + } + Protocol::Github => self.connect_github(id, epoch, p, prompt), + Protocol::Irc => self.connect_irc(id, epoch, p, prompt), + Protocol::Vnc => self.connect_vnc(id, epoch, p, prompt), + Protocol::Local => err("unsupported protocol \"local\"".into()), + } + } + + /// Callbacks for terminal-style clients: output to the UI, recording and login scripts. + fn terminal_callbacks(self: &Arc, id: &str, epoch: u64) -> Callbacks { + let login = self.sessions.lock().unwrap().get(id).map(|s| s.login.clone()).unwrap_or_default(); + let (me, sid) = (self.clone(), id.to_string()); + let on_data = Arc::new(move |d: &[u8]| { + me.emit(Event::Data { session: sid.clone(), data: d.to_vec() }); + let rec = me.sessions.lock().unwrap().get(&sid).and_then(|s| s.recorder.clone()); + if let Some(r) = rec { + r.write_output(d); + } + let mut l = login.lock().unwrap(); + if l.active { + l.buf.extend_from_slice(d); + if l.buf.len() > 16384 { + let cut = l.buf.len() - 16384; + l.buf.drain(..cut); + } + } + }); + let (me, sid) = (self.clone(), id.to_string()); + let on_error = Arc::new(move |e: String| { + let ok = me + .with(&sid, |s| { + if s.epoch != epoch { + return false; + } + s.info.state = SessionState::Error; + s.info.error = e.clone(); + true + }) + .unwrap_or(false); + if ok { + me.emit_state(&sid, SessionState::Error, &e); + } + }); + let (me, sid) = (self.clone(), id.to_string()); + let on_close = Arc::new(move || { + let me = me.clone(); + let sid = sid.clone(); + // Close callbacks can fire inside a client's own lock; handle them off-thread. + std::thread::spawn(move || me.handle_closed(&sid, epoch)); + }); + Callbacks { on_data, on_error, on_close } + } + + // --- credentials -------------------------------------------------------------------- + + /// Resolves a credential per its SecretMode (Go's buildSecretCallback): saved values come from + /// the keychain then the vault; session-only values are asked once per connection. + pub fn secret_fn(&self, profile_id: &str, kind: &str, mode: SecretMode, prompt: Prompt) -> SecretFn { + let key = format!("profile:{profile_id}:{kind}"); + let (secrets, store) = (self.secrets.clone(), self.store.clone()); + let (pid, kind) = (profile_id.to_string(), kind.to_string()); + let cache: Arc>> = Arc::default(); + Arc::new(move || { + if mode == SecretMode::Saved + && let Some(v) = secrets.get(&key) + { + store.add_audit("secret.access", &pid, "", &format!("Accessed {kind} credential")); + return Ok(v); + } + if mode == SecretMode::SessionOnly + && let Some(v) = cache.lock().unwrap().clone() + { + return Ok(v); + } + let v = prompt(&format!("Enter {kind}:"))?; + match mode { + SecretMode::Saved => secrets.set(&key, &v), + SecretMode::SessionOnly => *cache.lock().unwrap() = Some(v.clone()), + SecretMode::AskEveryTime => {} + } + Ok(v) + }) + } + + /// Blocks until the user answers a credential prompt (5 minutes, or cancel). + pub fn prompt(&self, session: &str, title: &str, message: &str, timeout: Duration) -> Result { + let (tx, rx) = mpsc::channel(); + self.emit(Event::SecretPrompt { session: session.into(), title: title.into(), message: message.into(), reply: tx }); + match rx.recv_timeout(timeout) { + Ok(v) => Ok(v), + Err(mpsc::RecvTimeoutError::Timeout) => err("secret prompt timed out".into()), + Err(_) => err("user cancelled secret prompt".into()), + } + } + + /// The standard interactive prompt for a session's credentials. + pub fn prompt_for(self: &Arc, session: &str, title: &str, timeout: Duration) -> Prompt { + let (me, sid, title) = (self.clone(), session.to_string(), title.to_string()); + Arc::new(move |msg| me.prompt(&sid, &title, msg, timeout)) + } + + /// Host-key policy check against the known-hosts table (Go's HostKeyVerifier). + pub fn host_key_check(self: &Arc, session: &str, policy: HostKeyPolicy) -> HostKeyCheck { + let (me, sid) = (self.clone(), session.to_string()); + Arc::new(move |k: &HostKeyInfo| { + let stored = me.store.get_known_host(&k.host, k.port as i64).map_err(|e| format!("failed to query known hosts: {e}"))?; + let save = || { + let n = now(); + me.store + .save_known_host(&KnownHost { + id: String::new(), + host: k.host.clone(), + port: k.port as i64, + key_type: k.key_type.clone(), + fingerprint: k.fingerprint.clone(), + public_key: k.public_key_b64.clone(), + first_seen: n, + last_seen: n, + trusted: true, + }) + .map_err(|e| e.to_string()) + }; + let verify = |mut s: KnownHost| { + if s.fingerprint != k.fingerprint { + return Err(format!( + "host key for {} has changed! stored fingerprint: {}, received: {} — possible MITM attack", + k.host, s.fingerprint, k.fingerprint + )); + } + s.last_seen = now(); + let _ = me.store.save_known_host(&s); + Ok(()) + }; + match (policy, stored) { + (HostKeyPolicy::Insecure, None) | (HostKeyPolicy::AcceptNew, None) => save(), + (HostKeyPolicy::Insecure, Some(mut s)) => { + s.last_seen = now(); + let _ = me.store.save_known_host(&s); + Ok(()) + } + (HostKeyPolicy::Strict, None) => Err(format!("host key for {}:{} is not known (policy: strict)", k.host, k.port)), + (HostKeyPolicy::Ask, None) => { + let (tx, rx) = mpsc::channel(); + me.emit(Event::HostKeyVerify { + session: sid.clone(), + host: k.host.clone(), + key_type: k.key_type.clone(), + fingerprint: k.fingerprint.clone(), + reply: tx, + }); + match rx.recv_timeout(Duration::from_secs(60)) { + Ok(true) => save(), + Ok(false) | Err(mpsc::RecvTimeoutError::Disconnected) => Err(format!("host key for {}:{} rejected by user", k.host, k.port)), + Err(_) => Err("host key prompt failed: host key prompt timed out waiting for user response".into()), + } + } + (_, Some(s)) => verify(s), + } + }) + } + + // --- per-protocol connect ----------------------------------------------------------- + + fn connect_ssh(self: &Arc, id: &str, epoch: u64, p: &Profile, prompt: Prompt) -> Result { + let mut p = p.clone(); + let sc = p.ssh.get_or_insert_with(SshConfig::defaults); + if sc.totp_secret.is_empty() + && let Some(t) = self.secrets.get(&format!("profile:{}:totp", p.id)) + { + sc.totp_secret = t; + } + let sc = sc.clone(); + let password = self.secret_fn(&p.id, "password", sc.secret_mode, prompt.clone()); + let passphrase = self.secret_fn(&p.id, "passphrase", sc.key_secret_mode, prompt); + let c = SshClient::new(&p, self.terminal_callbacks(id, epoch)); + c.connect(self.host_key_check(id, sc.host_key_policy), Some(password), Some(passphrase))?; + for pf in sc.port_forwards.iter().filter(|f| f.enabled) { + match c.start_forward(pf.clone()) { + Ok(()) => self.emit(Event::ForwardStarted { session: id.into(), forward: pf.clone() }), + Err(e) => self.emit(Event::ForwardError { session: id.into(), forward_id: pf.id.clone(), label: pf.label.clone(), error: e.to_string() }), + } + } + self.start_login_scripts(id, epoch, sc.login_scripts.clone()); + Ok(Client::Ssh(c)) + } + + /// An SSH connection used only to tunnel another protocol (IRC, VNC). + #[allow(clippy::too_many_arguments)] + fn tunnel(self: &Arc, id: &str, profile_id: &str, host: &str, port: i64, user: &str, method: &str, key: &str, agent: bool, mode: SecretMode, kind: &str, prompt: Prompt) -> Result> { + let mut tp = Profile::new(Protocol::Ssh); + tp.host = host.into(); + tp.port = if port == 0 { 22 } else { port }; + tp.username = user.into(); + let c = tp.ssh.as_mut().unwrap(); + c.auth_methods = vec![if method.is_empty() { "publickey".into() } else { method.into() }]; + c.private_key_path = key.into(); + c.agent_auth = agent; + let password = (method == "password").then(|| self.secret_fn(profile_id, &format!("{kind}-ssh-password"), mode, prompt.clone())); + let passphrase = self.secret_fn(profile_id, &format!("{kind}-ssh-passphrase"), mode, prompt); + let t = SshClient::new(&tp, Callbacks::noop()); + t.connect_tunnel_only(self.host_key_check(id, HostKeyPolicy::Ask), password, Some(passphrase))?; + Ok(t) + } + + fn connect_github(self: &Arc, id: &str, epoch: u64, p: &Profile, prompt: Prompt) -> Result { + let gh = p.github.clone().ok_or_else(|| format!("GitHub configuration missing on profile {:?}", p.id))?; + let token = self.secret_fn(&p.id, "github-token", gh.secret_mode, prompt.clone())().map_err(|e| format!("obtaining GitHub token: {e}"))?; + if token.is_empty() { + return err("GitHub token cannot be empty".into()); + } + let mut client = github::Client::new(&gh.base_url, &token); + let login = match client.validate_token() { + Ok(l) => l, + Err(e) => { + let msg = e.to_string().to_lowercase(); + let auth = ["token validation failed", "authentication failed", "http 401", "http 403"].iter().any(|k| msg.contains(k)); + let retry = auth + .then(|| prompt("GitHub token did not work. Please enter a valid token:").ok()) + .flatten() + .filter(|t| !t.is_empty()); + let Some(t) = retry else { return err(format!("GitHub authentication failed: {e}")) }; + if gh.secret_mode == SecretMode::Saved { + self.secrets.set(&format!("profile:{}:github-token", p.id), &t); + } + client = github::Client::new(&gh.base_url, &t); + client.validate_token().map_err(|e| format!("GitHub authentication failed: {e}"))? + } + }; + let client = Arc::new(client); + if !self.is_current(id, epoch) { + return err(format!("session {id:?} was closed while connecting")); + } + let fetch = { + let (me, sid, client) = (self.clone(), id.to_string(), client.clone()); + move |status: &str| { + me.emit(Event::GithubStatus { session: sid.clone(), status: status.into() }); + let (me2, sid2) = (me.clone(), sid.clone()); + client.fetch_repos_with_progress(move |done, total| { + if total > 0 { + me2.emit(Event::GithubStatus { session: sid2.clone(), status: format!("Loading commit info ({done}/{total})") }); + } + }) + } + }; + let repos = fetch("Loading repositories...").map_err(|e| format!("fetching repositories: {e}"))?; + self.emit(Event::GithubRepos { session: id.into(), login: login.clone(), repos }); + let interval = Duration::from_secs(if gh.refresh_interval_secs < 60 { 300 } else { gh.refresh_interval_secs as u64 }); + let stop = Arc::new(AtomicBool::new(false)); + let (me, sid, stop2) = (self.clone(), id.to_string(), stop.clone()); + std::thread::spawn(move || { + loop { + let deadline = std::time::Instant::now() + interval; + while std::time::Instant::now() < deadline { + if stop2.load(Ordering::SeqCst) { + return; + } + std::thread::sleep(Duration::from_millis(500)); + } + match fetch("Refreshing repositories...") { + Ok(repos) => me.emit(Event::GithubRepos { session: sid.clone(), login: login.clone(), repos }), + Err(e) => me.emit(Event::GithubError { session: sid.clone(), error: e.to_string() }), + } + } + }); + Ok(Client::Github(client, stop)) + } + + fn connect_irc(self: &Arc, id: &str, epoch: u64, p: &Profile, prompt: Prompt) -> Result { + let ic = p.irc.clone().ok_or("IRC configuration missing")?; + let server_pass = if ic.server_password_required { + self.secret_fn(&p.id, "irc-server-password", ic.server_password_mode, prompt.clone())().map_err(|e| format!("IRC server password: {e}"))? + } else { + String::new() + }; + let ns_pass = if ic.nick_serv_identify { + self.secret_fn(&p.id, "irc-nickserv-password", ic.nick_serv_mode, prompt.clone())().map_err(|e| format!("NickServ password: {e}"))? + } else { + String::new() + }; + let mut rooms = ic.auto_join_rooms.clone(); + if !p.id.is_empty() { + let last = self.store.irc_last_rooms(&p.id).map_err(|e| format!("loading remembered IRC rooms: {e}"))?; + rooms = crate::storage::dedup_rooms(&[&rooms, &last]); + } + let tunnel = if ic.ssh_tunnel { + Some(self.tunnel(id, &p.id, &ic.ssh_host, ic.ssh_port, &ic.ssh_username, &ic.ssh_auth_method, &ic.ssh_private_key, ic.ssh_agent_auth, ic.ssh_secret_mode, "irc", prompt)?) + } else { + None + }; + let cb = self.terminal_callbacks(id, epoch); + let (me, sid) = (self.clone(), id.to_string()); + let on_event: irc::EventFn = Arc::new(move |e| me.emit(Event::Irc { session: sid.clone(), event: e })); + let (me, sid, pid) = (self.clone(), id.to_string(), p.id.clone()); + let on_rooms: irc::RoomsChangedFn = Arc::new(move |rooms: Vec| { + if pid.is_empty() { + return; + } + if let Err(e) = me.store.save_irc_last_rooms(&pid, &rooms) { + me.emit(Event::Irc { + session: sid.clone(), + event: irc::Event { + kind: "system".into(), + target: irc::SERVER_TARGET.into(), + text: format!("Could not remember open IRC rooms: {e}"), + timestamp: chrono::Utc::now().timestamp_millis(), + ..Default::default() + }, + }); + } + }); + let connector: Option = tunnel.clone().map(|t| { + let (host, port) = (p.host.clone(), p.port as u16); + Arc::new(move || { + let s = t.open_tunnel(&host, port)?; + s.set_read_timeout(Some(Duration::from_millis(50)))?; + Ok(Box::new(s) as Box) + }) as irc::Connector + }); + let cfg = irc::Config { + server: p.host.clone(), + port: p.port as u16, + nick: ic.nick.clone(), + user: if p.username.is_empty() { ic.nick.clone() } else { p.username.clone() }, + real_name: ic.real_name.clone(), + use_tls: ic.use_tls, + server_pass, + auto_join_rooms: rooms, + nickserv_identify: ic.nick_serv_identify, + nickserv_account: ic.nick_serv_account.clone(), + nickserv_password: ns_pass, + connector, + connect_timeout: Duration::from_secs(p.connect_timeout.max(1) as u64), + on_event, + on_error: cb.on_error, + on_close: cb.on_close, + on_rooms_changed: on_rooms, + }; + let c = irc::Client::new(cfg); + if let Err(e) = c.connect() { + if let Some(t) = tunnel { + t.close(); + } + return Err(e); + } + Ok(Client::Irc(c, tunnel)) + } + + fn connect_vnc(self: &Arc, id: &str, epoch: u64, p: &Profile, prompt: Prompt) -> Result { + let vc = p.vnc.clone().ok_or("VNC configuration missing")?; + let host = if vc.vnc_host.is_empty() { p.host.clone() } else { vc.vnc_host.clone() }; + let port = if vc.vnc_port == 0 { 5900 } else { vc.vnc_port } as u16; + let tunnel = if vc.ssh_tunnel { + Some(self.tunnel(id, &p.id, &vc.ssh_host, vc.ssh_port, &vc.ssh_username, &vc.ssh_auth_method, &vc.ssh_private_key, vc.ssh_agent_auth, vc.ssh_secret_mode, "vnc", prompt.clone())?) + } else { + None + }; + let password = self.secret_fn(&p.id, "vnc-password", vc.secret_mode, prompt); + let cb = self.terminal_callbacks(id, epoch); + let (me, sid) = (self.clone(), id.to_string()); + let c = vnc_stub::connect_profile(&host, port, &vc, p.connect_timeout, password, tunnel.clone(), cb, Arc::new(move || me.emit(Event::VncFrame { session: sid.clone() }))); + match c { + Ok(c) => { + self.emit(Event::VncReady { session: id.into() }); + Ok(Client::Vnc(c, tunnel)) + } + Err(e) => { + if let Some(t) = tunnel { + t.close(); + } + Err(e) + } + } + } + + // --- local shell -------------------------------------------------------------------- + + pub fn connect_local(self: &Arc, shell: &str) -> String { + let id = new_id(); + let info = SessionInfo { + id: id.clone(), + profile_id: String::new(), + profile_name: "Local Machine".into(), + protocol: Protocol::Local, + host: "localhost".into(), + port: 0, + state: SessionState::Connecting, + tab_name: "Local".into(), + pinned: false, + connected_at: now(), + error: String::new(), + }; + let epoch = self.new_session(info, None, shell.into()); + self.emit_state(&id, SessionState::Connecting, ""); + let me = self.clone(); + let id2 = id.clone(); + std::thread::spawn(move || me.start_local(&id2, epoch, 80, 24)); + id + } + + /// Starts a fresh shell in an exited local tab, keeping its scrollback and last directory. + pub fn restart_local(self: &Arc, id: &str, cols: u16, rows: u16) -> Result<()> { + let epoch = self.with(id, |s| { + if s.info.protocol != Protocol::Local { + return Err(format!("sessions: session {id:?} is not local")); + } + if matches!(s.client, Client::Local(_)) || matches!(s.info.state, SessionState::Connecting | SessionState::Connected) { + return Err(format!("sessions: local session {id:?} is still running")); + } + s.info.state = SessionState::Connecting; + s.info.error.clear(); + Ok(s.epoch) + })??; + self.emit_state(id, SessionState::Connecting, ""); + let (me, id2) = (self.clone(), id.to_string()); + std::thread::spawn(move || me.start_local(&id2, epoch, cols, rows)); + Ok(()) + } + + fn start_local(self: &Arc, id: &str, epoch: u64, cols: u16, rows: u16) { + let Ok((shell, cwd, pending)) = self.with(id, |s| (s.local_shell.clone(), s.local_cwd.clone(), s.pending_resize.take())) else { return }; + let (cols, rows) = pending.unwrap_or((cols, rows)); + let mut cb = self.terminal_callbacks(id, epoch); + // A local shell exiting keeps its tab ("press enter to restart"). + let (me, sid) = (self.clone(), id.to_string()); + cb.on_close = Arc::new(move || { + let (me, sid) = (me.clone(), sid.clone()); + std::thread::spawn(move || me.local_closed(&sid, epoch)); + }); + let c = LocalShell::new(&shell, cwd, cols, rows, cb); + let r = c.start().map(|_| Client::Local(c.clone())); + self.finish_connect(id, epoch, r, |me| me.store.add_audit("connect", "", id, "Connected via local to localhost")); + } + + fn local_closed(self: &Arc, id: &str, epoch: u64) { + let cwd = self.with(id, |s| { + if s.epoch != epoch || s.closed { + return None; + } + match std::mem::replace(&mut s.client, Client::None) { + Client::Local(c) => Some(c.working_directory()), + other => { + s.client = other; + None + } + } + }); + if let Ok(Some(cwd)) = cwd { + let _ = self.with(id, |s| s.local_cwd = Some(cwd)); + self.handle_closed(id, epoch); + } + } + + // --- lifecycle ---------------------------------------------------------------------- + + /// Explicit close: the tab is gone, so no reconnect. + pub fn disconnect(&self, id: &str) -> Result<()> { + let s = self.sessions.lock().unwrap().remove(id).ok_or_else(|| format!("sessions: session {id:?} not found"))?; + self.close_session(s); + self.store.add_audit("disconnect", "", id, "Session disconnected"); + Ok(()) + } + + /// Closes and re-opens a session from its profile, keeping the tab name and pin; returns the + /// new session id. + pub fn reconnect(self: &Arc, id: &str, prompt: Prompt) -> Result { + let s = self.sessions.lock().unwrap().remove(id).ok_or_else(|| format!("sessions: session {id:?} not found"))?; + let (profile, tab, pinned, proto, shell) = (s.profile.clone(), s.info.tab_name.clone(), s.info.pinned, s.info.protocol, s.local_shell.clone()); + self.close_session(s); + let new = match (proto, profile) { + (Protocol::Local, _) => self.connect_local(&shell), + (_, Some(p)) => self.connect(p, prompt), + _ => return err("sessions: nothing to reconnect".into()), + }; + let _ = self.with(&new, |s| { + s.info.tab_name = tab; + s.info.pinned = pinned; + }); + Ok(new) + } + + fn close_session(&self, mut s: Session) { + s.closed = true; + if let Some(c) = s.cancel_reconnect.take() { + c.store(true, Ordering::SeqCst); + } + if let Some(r) = s.recorder.take() { + let _ = r.close(); + } + s.client.close(); + } + + fn handle_closed(self: &Arc, id: &str, epoch: u64) { + let profile = { + let mut map = self.sessions.lock().unwrap(); + let Some(s) = map.get_mut(id).filter(|s| s.epoch == epoch && !s.closed) else { return }; + s.info.state = SessionState::Disconnected; + if !matches!(s.client, Client::Local(_)) { + s.client = Client::None; + } + s.profile.clone() + }; + self.store.add_audit("disconnect", "", id, "Session disconnected"); + if let Some(p) = profile.filter(|p| p.auto_reconnect()) { + self.schedule_reconnect(id, epoch, p); + return; + } + self.emit(Event::Closed { session: id.into() }); + self.emit_state(id, SessionState::Disconnected, ""); + } + + /// Exponential backoff 1, 2, 4 … 30 s, up to the profile's attempt limit. + fn schedule_reconnect(self: &Arc, id: &str, epoch: u64, p: Profile) { + let max = p.max_reconnect_tries(); + let cancel = Arc::new(AtomicBool::new(false)); + let attempt = self.with(id, |s| { + s.reconnect_count += 1; + if s.reconnect_count > max { + s.info.state = SessionState::Disconnected; + return None; + } + s.info.state = SessionState::Reconnecting; + s.info.error.clear(); + if let Some(c) = s.cancel_reconnect.replace(cancel.clone()) { + c.store(true, Ordering::SeqCst); + } + Some(s.reconnect_count) + }); + let Ok(attempt) = attempt else { return }; + let Some(attempt) = attempt else { + self.emit(Event::Closed { session: id.into() }); + self.emit_state(id, SessionState::Disconnected, "auto-reconnect failed after maximum attempts"); + return; + }; + let delay = (1u64 << (attempt - 1).min(5)).min(30); + self.emit(Event::State { session: id.into(), state: SessionState::Reconnecting, error: String::new(), retry: Some(Retry { attempt, max, delay_secs: delay }) }); + let (me, id) = (self.clone(), id.to_string()); + std::thread::spawn(move || { + let deadline = std::time::Instant::now() + Duration::from_secs(delay); + while std::time::Instant::now() < deadline { + if cancel.load(Ordering::SeqCst) { + return; + } + std::thread::sleep(Duration::from_millis(100)); + } + let ok = me + .with(&id, |s| { + if s.epoch != epoch || s.closed || cancel.load(Ordering::SeqCst) { + return false; + } + s.client = Client::None; + s.info.state = SessionState::Connecting; + true + }) + .unwrap_or(false); + if !ok { + return; + } + me.emit_state(&id, SessionState::Connecting, ""); + let prompt = me.prompt_for(&id, "Reconnection Credentials", Duration::from_secs(60)); + match me.connect_protocol(&id, epoch, &p, prompt) { + Ok(c) => me.finish_connect(&id, epoch, Ok(c), |_| {}), + Err(e) => { + let current = me.with(&id, |s| { + if s.epoch != epoch || s.closed { + return false; + } + s.info.state = SessionState::Error; + s.info.error = e.to_string(); + true + }); + if current.unwrap_or(false) { + me.emit_state(&id, SessionState::Error, &e.to_string()); + me.schedule_reconnect(&id, epoch, p); + } + } + } + }); + } + + pub fn cancel_reconnect(&self, id: &str) { + let found = self + .with(id, |s| { + if let Some(c) = s.cancel_reconnect.take() { + c.store(true, Ordering::SeqCst); + s.reconnect_count = 0; + s.info.state = SessionState::Disconnected; + } + }) + .is_ok(); + if found { + self.emit_state(id, SessionState::Disconnected, ""); + } + } + + pub fn close_all(&self) { + let all: Vec = self.sessions.lock().unwrap().drain().map(|(_, s)| s).collect(); + for s in all { + self.close_session(s); + } + } + + // --- login scripts ------------------------------------------------------------------ + + fn start_login_scripts(self: &Arc, id: &str, epoch: u64, steps: Vec) { + if steps.is_empty() { + return; + } + let Ok(login) = self.with(id, |s| s.login.clone()) else { return }; + *login.lock().unwrap() = LoginBuf { active: true, buf: vec![] }; + let (me, id) = (self.clone(), id.to_string()); + std::thread::spawn(move || { + std::thread::sleep(Duration::from_millis(500)); + for (i, step) in steps.iter().enumerate() { + // Wait until the connect has been recorded as Connected. + let mut waited = 0; + while me.with(&id, |s| s.info.state).ok() == Some(SessionState::Connecting) && waited < 100 { + std::thread::sleep(Duration::from_millis(50)); + waited += 1; + } + if !me.is_current(&id, epoch) || me.with(&id, |s| s.info.state).ok() != Some(SessionState::Connected) { + break; + } + match step.kind.as_str() { + "wait" => { + let timeout = Duration::from_secs(if step.timeout <= 0 { 10 } else { step.timeout as u64 }); + if !wait_for_output(&login, &step.value, timeout) { + me.emit(Event::Debug { session: id.clone(), message: format!("login script step {} timed out waiting for {:?}", i + 1, step.value) }); + break; + } + } + "send" => { + if let Err(e) = me.write(&id, format!("{}\n", step.value).as_bytes()) { + me.emit(Event::Debug { session: id.clone(), message: format!("login script step {} failed to send: {e}", i + 1) }); + break; + } + std::thread::sleep(Duration::from_millis(200)); + } + _ => {} + } + } + *login.lock().unwrap() = LoginBuf::default(); + }); + } + + // --- I/O ---------------------------------------------------------------------------- + + pub fn write(&self, id: &str, data: &[u8]) -> Result<()> { + let (client, rec) = self.with(id, |s| (s.client.clone(), s.recorder.clone()))?; + if let Some(r) = rec { + r.write_input(data); + } + match client { + Client::Ssh(c) => c.write(data), + Client::Telnet(c) => c.write(data), + Client::Raw(c) => c.write(data), + Client::Serial(c) => c.write(data), + Client::Local(c) => c.write(data), + _ => err(format!("sessions: no active client for session {id:?}")), + } + } + + fn resize_client(&self, c: &Client, cols: u16, rows: u16) -> Result<()> { + if cols == 0 || rows == 0 { + return Ok(()); + } + match c { + Client::Ssh(c) => c.resize(cols as u32, rows as u32), + Client::Telnet(c) => c.resize(cols, rows), + Client::Local(c) => c.resize(cols, rows), + _ => Ok(()), + } + } + + /// Resizes the remote PTY; before a client exists the size is kept and applied on connect. + pub fn resize(&self, id: &str, cols: u16, rows: u16) -> Result<()> { + let c = self.with(id, |s| { + if matches!(s.client, Client::None) { + s.pending_resize = Some((cols, rows)); + } + s.client.clone() + })?; + self.resize_client(&c, cols, rows) + } + + pub fn info(&self, id: &str) -> Option { + self.sessions.lock().unwrap().get(id).map(|s| s.info.clone()) + } + + pub fn list(&self) -> Vec { + self.sessions.lock().unwrap().values().map(|s| s.info.clone()).collect() + } + + pub fn profile(&self, id: &str) -> Option { + self.sessions.lock().unwrap().get(id).and_then(|s| s.profile.clone()) + } + + pub fn rename(&self, id: &str, name: &str) -> Result<()> { + self.with(id, |s| s.info.tab_name = name.into()) + } + + pub fn pin(&self, id: &str, pinned: bool) -> Result<()> { + self.with(id, |s| s.info.pinned = pinned) + } + + pub fn send_break(&self, id: &str) -> Result<()> { + match self.client(id)? { + Client::Telnet(c) => c.send_break(), + Client::Serial(c) => c.send_break(), + _ => err(format!("sessions: session {id:?} does not support break")), + } + } + + pub fn set_serial_lines(&self, id: &str, dtr: bool, rts: bool) -> Result<()> { + match self.client(id)? { + Client::Serial(c) => c.set_lines(dtr, rts), + _ => err(format!("sessions: session {id:?} is not a serial connection")), + } + } + + // --- RAW ---------------------------------------------------------------------------- + + fn raw(&self, id: &str) -> Result> { + match self.client(id)? { + Client::Raw(c) => Ok(c), + _ => err(format!("sessions: session {id:?} is not a RAW connection")), + } + } + + pub fn raw_write_text(&self, id: &str, s: &str) -> Result<()> { + self.raw(id)?.write_text(s) + } + + pub fn raw_write_hex(&self, id: &str, s: &str) -> Result<()> { + self.raw(id)?.write_hex(s) + } + + pub fn raw_write_escaped(&self, id: &str, s: &str) -> Result<()> { + self.raw(id)?.write_escaped(s) + } + + pub fn raw_history(&self, id: &str) -> Result> { + Ok(self.raw(id)?.history()) + } + + pub fn raw_clear_history(&self, id: &str) -> Result<()> { + self.raw(id)?.clear_history(); + Ok(()) + } + + pub fn raw_save_capture(&self, id: &str, path: &std::path::Path) -> Result<()> { + self.raw(id)?.save_capture(path) + } + + // --- IRC ---------------------------------------------------------------------------- + + pub fn irc(&self, id: &str) -> Result> { + match self.client(id)? { + Client::Irc(c, _) => Ok(c), + _ => err(format!("sessions: session {id:?} is not an IRC connection")), + } + } + + // --- GitHub ------------------------------------------------------------------------- + + pub fn github(&self, id: &str) -> Result> { + match self.client(id)? { + Client::Github(c, _) => Ok(c), + _ => err(format!("sessions: session {id:?} is not a GitHub connection")), + } + } + + pub fn vnc(&self, id: &str) -> Result> { + match self.client(id)? { + Client::Vnc(c, _) => Ok(c), + _ => err(format!("sessions: session {id:?} is not a VNC connection")), + } + } + + // --- SSH extras --------------------------------------------------------------------- + + pub fn ssh(&self, id: &str) -> Result> { + match self.client(id)? { + Client::Ssh(c) => Ok(c), + _ => err(format!("sessions: session {id:?} is not an SSH connection")), + } + } + + pub fn ping(&self, id: &str) -> Result { + self.ssh(id)?.ping() + } + + pub fn cwd(&self, id: &str) -> Result { + self.ssh(id)?.run_command("pwd") + } + + pub fn start_forward(&self, id: &str, rule: PortForward) -> Result<()> { + self.ssh(id)?.start_forward(rule.clone())?; + self.emit(Event::ForwardStarted { session: id.into(), forward: rule }); + Ok(()) + } + + pub fn stop_forward(&self, id: &str, fid: &str) -> Result<()> { + self.ssh(id)?.stop_forward(fid)?; + self.emit(Event::ForwardStopped { session: id.into(), forward_id: fid.into() }); + Ok(()) + } + + pub fn forwards(&self, id: &str) -> Result> { + Ok(self.ssh(id)?.active_forwards()) + } + + pub fn sftp_download(&self, id: &str, remote: &str, local: &std::path::Path) -> Result<()> { + let s = self.ssh(id)?.sftp()?; + clients::sftp::download(&s, remote, local, &|p| self.emit(Event::SftpProgress { session: id.into(), progress: p })) + } + + pub fn sftp_upload(&self, id: &str, local: &std::path::Path, remote: &str) -> Result<()> { + let s = self.ssh(id)?.sftp()?; + clients::sftp::upload(&s, local, remote, &|p| self.emit(Event::SftpProgress { session: id.into(), progress: p })) + } + + // --- recording ---------------------------------------------------------------------- + + pub fn start_recording(&self, id: &str, cols: u16, rows: u16) -> Result<()> { + let (exists, title) = self.with(id, |s| (s.recorder.is_some(), format!("{} — {}", s.info.profile_name, s.info.host)))?; + if exists { + return err(format!("sessions: session {id:?} is already being recorded")); + } + let r = Arc::new(Recorder::new(self.recordings.dir(), id, &title, cols as i64, rows as i64).map_err(|e| format!("sessions: failed to start recording: {e}"))?); + self.with(id, |s| s.recorder = Some(r))?; + self.emit(Event::RecordingStarted { session: id.into() }); + Ok(()) + } + + pub fn stop_recording(&self, id: &str) -> Result<()> { + let r = self.with(id, |s| s.recorder.take())?.ok_or_else(|| format!("sessions: session {id:?} is not being recorded"))?; + r.close().map_err(|e| format!("sessions: failed to stop recording: {e}"))?; + self.emit(Event::RecordingStopped { session: id.into(), duration: r.duration(), events: r.event_count() }); + Ok(()) + } + + pub fn is_recording(&self, id: &str) -> bool { + self.with(id, |s| s.recorder.is_some()).unwrap_or(false) + } + + pub fn recordings(&self) -> &recording::Store { + &self.recordings + } +} + +// ponytail: placeholder until the VNC client lands; replaced by clients::vnc. +mod vnc_stub { + use crate::clients::Callbacks; + use crate::clients::ssh::{SecretFn, SshClient}; + use std::sync::Arc; + pub struct VncClient; + impl VncClient { + pub fn close(&self) {} + } + #[allow(clippy::too_many_arguments)] + pub fn connect_profile(_: &str, _: u16, _: &crate::models::VncConfig, _: i64, _: SecretFn, _: Option>, _: Callbacks, _: Arc) -> crate::Result> { + Err("VNC is not available yet".into()) + } +} + +fn wait_for_output(login: &Mutex, pattern: &str, timeout: Duration) -> bool { + if pattern.is_empty() { + return true; + } + let deadline = std::time::Instant::now() + timeout; + while std::time::Instant::now() < deadline { + { + let mut l = login.lock().unwrap(); + let text = String::from_utf8_lossy(&l.buf).into_owned(); + if let Some(i) = text.find(pattern) { + let consumed = text[..i + pattern.len()].len(); + let n = consumed.min(l.buf.len()); + l.buf.drain(..n); + return true; + } + } + std::thread::sleep(Duration::from_millis(100)); + } + false +} diff --git a/rust/core/tests/sessions.rs b/rust/core/tests/sessions.rs new file mode 100644 index 0000000..aba647e --- /dev/null +++ b/rust/core/tests/sessions.rs @@ -0,0 +1,131 @@ +//! Session manager end to end: events, writes, host-key prompts, auto-reconnect. + +use nexus_core::models::{Profile, Protocol, SessionState}; +use nexus_core::secrets::{Keychain, Secrets, Vault}; +use nexus_core::sessions::{Event, Manager, Prompt}; +use nexus_core::storage::Store; +use std::io::{Read, Write}; +use std::net::TcpListener; +use std::sync::mpsc; +use std::sync::{Arc, Mutex}; +use std::time::Duration; + +fn manager(name: &str) -> (Arc, mpsc::Receiver, std::path::PathBuf) { + let dir = std::env::temp_dir().join(format!("nexus-mgr-{name}-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&dir); + std::fs::create_dir_all(&dir).unwrap(); + let store = Arc::new(Store::open(&dir.join("nexus.db")).unwrap()); + let secrets = Arc::new(Secrets { vault: Vault::new(dir.join("vault")), keychain: Keychain::new("NexusTerminalTest", None), keychain_ok: false }); + let m = Manager::new(store, secrets, dir.join("recordings")); + let (tx, rx) = mpsc::channel(); + let tx = Mutex::new(tx); + m.set_sink(Arc::new(move |e| { + let _ = tx.lock().unwrap().send(e); + })); + (m, rx, dir) +} + +fn no_prompt() -> Prompt { + Arc::new(|_| Err("no prompt in tests".into())) +} + +/// Waits for an event matching `f`, answering host-key prompts with `accept`. +fn wait(rx: &mpsc::Receiver, mut f: impl FnMut(&Event) -> Option) -> T { + let deadline = std::time::Instant::now() + Duration::from_secs(15); + loop { + let e = rx.recv_timeout(deadline.saturating_duration_since(std::time::Instant::now())).expect("timed out waiting for event"); + if let Some(t) = f(&e) { + return t; + } + if let Event::HostKeyVerify { reply, .. } = e { + let _ = reply.send(true); + } + } +} + +fn state(e: &Event, want: SessionState) -> Option { + match e { + Event::State { state, error, session, .. } if *state == want => Some(format!("{session}:{error}")), + _ => None, + } +} + +#[cfg(unix)] +#[test] +fn local_shell_round_trip_and_restart() { + let (m, rx, dir) = manager("local"); + let id = m.connect_local("/bin/sh"); + m.resize(&id, 100, 30).unwrap(); + wait(&rx, |e| state(e, SessionState::Connected)); + m.start_recording(&id, 100, 30).unwrap(); + m.write(&id, b"echo mgr-$((6*7))\n").unwrap(); + let mut seen = Vec::new(); + wait(&rx, |e| match e { + Event::Data { data, .. } => { + seen.extend_from_slice(data); + String::from_utf8_lossy(&seen).contains("mgr-42").then_some(()) + } + _ => None, + }); + m.stop_recording(&id).unwrap(); + assert_eq!(m.recordings().list().unwrap().len(), 1); + m.write(&id, b"exit\n").unwrap(); + wait(&rx, |e| matches!(e, Event::Closed { .. }).then_some(())); + assert_eq!(m.info(&id).unwrap().state, SessionState::Disconnected); + m.restart_local(&id, 80, 24).unwrap(); + wait(&rx, |e| state(e, SessionState::Connected)); + m.disconnect(&id).unwrap(); + assert!(m.info(&id).is_none()); + let _ = std::fs::remove_dir_all(dir); +} + +#[test] +fn raw_session_and_auto_reconnect() { + let (m, rx, dir) = manager("raw"); + let l = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = l.local_addr().unwrap().port(); + std::thread::spawn(move || { + // First connection: echo one message then hang up; the second stays open. + for (n, s) in l.incoming().flatten().enumerate() { + let mut s = s; + let mut buf = [0u8; 64]; + if let Ok(k) = s.read(&mut buf) { + let _ = s.write_all(&buf[..k]); + } + if n > 0 { + std::thread::sleep(Duration::from_secs(30)); + } + } + }); + let mut p = Profile::new(Protocol::Raw); + p.host = "127.0.0.1".into(); + p.port = port as i64; + p.raw.as_mut().unwrap().auto_reconnect = true; + let id = m.connect(p, no_prompt()); + wait(&rx, |e| state(e, SessionState::Connected)); + m.raw_write_text(&id, "ping").unwrap(); + wait(&rx, |e| matches!(e, Event::RawData { direction, data, .. } if direction == "recv" && data.starts_with(b"ping")).then_some(())); + // Server hung up: reconnecting with a backoff, then connected again under the same id. + let retry = wait(&rx, |e| match e { + Event::State { state: SessionState::Reconnecting, retry, .. } => *retry, + _ => None, + }); + assert_eq!((retry.attempt, retry.delay_secs), (1, 1)); + let back = wait(&rx, |e| state(e, SessionState::Connected)); + assert!(back.starts_with(&id)); + m.disconnect(&id).unwrap(); + let _ = std::fs::remove_dir_all(dir); +} + +#[test] +fn connect_failure_reports_error_state() { + let (m, rx, dir) = manager("fail"); + let port = TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port(); + let mut p = Profile::new(Protocol::Telnet); + p.host = "127.0.0.1".into(); + p.port = port as i64; + let id = m.connect(p, no_prompt()); + let err = wait(&rx, |e| state(e, SessionState::Error)); + assert!(err.starts_with(&id) && err.len() > id.len() + 1, "{err}"); + let _ = std::fs::remove_dir_all(dir); +} From 81360cc4c25cc55cd73a4c74248e7660215d35a1 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:36:32 +0100 Subject: [PATCH 14/45] rust: native VNC (RFB) client --- rust/Cargo.lock | 5 + rust/core/Cargo.toml | 3 + rust/core/src/clients/mod.rs | 1 + rust/core/src/clients/vnc.rs | 1043 ++++++++++++++++++++++++++++++++++ 4 files changed, 1052 insertions(+) create mode 100644 rust/core/src/clients/vnc.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 1f5dbd0..d51ab0d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2321,6 +2321,8 @@ dependencies = [ "num-traits", "png", "tiff", + "zune-core", + "zune-jpeg", ] [[package]] @@ -2817,7 +2819,10 @@ dependencies = [ "argon2 0.5.3", "base64 0.22.1", "chrono", + "des", + "flate2", "hmac 0.13.0", + "image", "keyring", "libc", "portable-pty", diff --git a/rust/core/Cargo.toml b/rust/core/Cargo.toml index b198b60..c95c29b 100644 --- a/rust/core/Cargo.toml +++ b/rust/core/Cargo.toml @@ -23,6 +23,9 @@ hmac = "0.13.0" sha1 = "0.11.0" portable-pty = "0.9.0" serialport = { version = "4", default-features = false } +des = "0.9" +flate2 = "1" +image = { version = "0.25", default-features = false, features = ["jpeg"] } rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] } webpki-roots = "1" diff --git a/rust/core/src/clients/mod.rs b/rust/core/src/clients/mod.rs index 69f0f2b..1fac69d 100644 --- a/rust/core/src/clients/mod.rs +++ b/rust/core/src/clients/mod.rs @@ -42,6 +42,7 @@ impl Callbacks { pub mod raw; pub mod serial; pub mod telnet; +pub mod vnc; /// Called with protocol negotiation debug messages (Go's `onDebug`). pub type DebugFn = Arc; diff --git a/rust/core/src/clients/vnc.rs b/rust/core/src/clients/vnc.rs new file mode 100644 index 0000000..1efa569 --- /dev/null +++ b/rust/core/src/clients/vnc.rs @@ -0,0 +1,1043 @@ +//! Native VNC (RFB 3.3/3.7/3.8) client: None and VNC Authentication, Raw, CopyRect, Hextile, ZRLE +//! and Tight (zlib + JPEG), plus the DesktopSize, ExtendedDesktopSize and LastRect pseudo-encodings. +//! +//! One I/O thread owns the transport (TCP, or a caller-supplied stream such as an SSH tunnel). It +//! decodes into a private framebuffer and, after each FramebufferUpdate, copies the changed rows into +//! the shared `Arc>` the UI reads, so the lock is never held across network reads. +//! Outgoing input is queued and written whenever a read polls out, so the transport is never split. + +use std::io::{BufReader, ErrorKind, Read, Write}; +use std::sync::mpsc::{self, Receiver, Sender, TryRecvError}; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::time::Duration; + +use flate2::{Decompress, FlushDecompress}; + +pub use super::irc::{Connector, ReadWrite}; +use super::{CloseFn, ErrorFn}; + +const READ_POLL: Duration = Duration::from_millis(50); + +pub type FrameFn = Arc; +pub type ResizeFn = Arc; +pub type ClipboardFn = Arc; + +#[derive(Clone)] +pub struct Config { + pub host: String, + pub port: u16, + pub password: Option, + /// ClientInit's shared flag: leave other viewers connected. + pub shared: bool, + pub view_only: bool, + /// Tight JPEG quality, 0–9. + pub quality: u8, + /// Provides the transport instead of a direct TCP connect (e.g. an SSH direct-tcpip stream). + pub connector: Option, + pub connect_timeout: Duration, + /// Called after each FramebufferUpdate has been applied to the shared framebuffer. + pub on_frame: FrameFn, + pub on_resize: ResizeFn, + pub on_clipboard: ClipboardFn, + pub on_error: ErrorFn, + pub on_close: CloseFn, +} + +impl Default for Config { + fn default() -> Config { + Config { + host: String::new(), + port: 5900, + password: None, + shared: true, + view_only: false, + quality: 6, + connector: None, + connect_timeout: Duration::ZERO, + on_frame: Arc::new(|| {}), + on_resize: Arc::new(|_, _| {}), + on_clipboard: Arc::new(|_| {}), + on_error: Arc::new(|_| {}), + on_close: Arc::new(|| {}), + } + } +} + +/// The remote desktop as RGBA, row-major, `width * height * 4` bytes. The I/O thread sets `dirty` +/// after each update; the UI clears it once it has uploaded the texture. +#[derive(Clone, Debug, Default)] +pub struct Framebuffer { + pub width: u16, + pub height: u16, + pub rgba: Vec, + pub dirty: bool, +} + +impl Framebuffer { + fn new(width: u16, height: u16) -> Framebuffer { + let mut rgba = vec![0u8; width as usize * height as usize * 4]; + rgba.chunks_exact_mut(4).for_each(|p| p[3] = 255); + Framebuffer { width, height, rgba, dirty: true } + } + + fn check(&self, x: u16, y: u16, w: u16, h: u16) -> crate::Result<()> { + if x as usize + w as usize > self.width as usize || y as usize + h as usize > self.height as usize { + return Err(format!("rectangle {w}x{h}+{x}+{y} outside the {}x{} framebuffer", self.width, self.height).into()); + } + Ok(()) + } + + /// Writes `w*h` pixels of `bpp` bytes each (R, G, B first; anything after is ignored). + fn put(&mut self, x: u16, y: u16, w: u16, h: u16, px: &[u8], bpp: usize) { + let (w, stride) = (w as usize, self.width as usize); + for row in 0..h as usize { + let dst = ((y as usize + row) * stride + x as usize) * 4; + for (d, s) in self.rgba[dst..dst + w * 4].chunks_exact_mut(4).zip(px[row * w * bpp..].chunks_exact(bpp)) { + d[..3].copy_from_slice(&s[..3]); + d[3] = 255; + } + } + } + + fn fill(&mut self, x: u16, y: u16, w: u16, h: u16, rgb: &[u8]) { + let stride = self.width as usize; + for row in 0..h as usize { + let dst = ((y as usize + row) * stride + x as usize) * 4; + for d in self.rgba[dst..dst + w as usize * 4].chunks_exact_mut(4) { + d[..3].copy_from_slice(&rgb[..3]); + d[3] = 255; + } + } + } +} + +struct State { + connected: bool, + closing: bool, + ready: Option>>, + name: String, +} + +pub struct Client { + cfg: Config, + fb: Arc>, + state: Mutex, + out: Mutex>>>, +} + +impl Client { + pub fn new(mut cfg: Config) -> Arc { + if cfg.connect_timeout.is_zero() { + cfg.connect_timeout = Duration::from_secs(30); + } + cfg.quality = cfg.quality.min(9); + Arc::new(Client { + cfg, + fb: Arc::new(Mutex::new(Framebuffer::default())), + state: Mutex::new(State { connected: false, closing: false, ready: None, name: String::new() }), + out: Mutex::new(None), + }) + } + + fn st(&self) -> MutexGuard<'_, State> { + self.state.lock().unwrap_or_else(|e| e.into_inner()) + } + + /// Connects and blocks until the handshake completes (ServerInit received), fails, or times out. + pub fn connect(self: &Arc) -> crate::Result<()> { + let (ready_tx, ready_rx) = mpsc::channel(); + let (out_tx, out_rx) = mpsc::channel(); + self.st().ready = Some(ready_tx); + *self.out.lock().unwrap_or_else(|e| e.into_inner()) = Some(out_tx); + + let client = self.clone(); + std::thread::spawn(move || client.io_thread(out_rx)); + + let timeout = self.cfg.connect_timeout; + match ready_rx.recv_timeout(timeout) { + Ok(Ok(())) => Ok(()), + Ok(Err(e)) => Err(e.into()), + Err(_) => { + self.close(); + Err(format!("VNC connection timed out after {timeout:?}").into()) + } + } + } + + /// Disconnects. Idempotent; `on_close` fires from the I/O thread. + pub fn close(&self) { + { + let mut st = self.st(); + if st.closing { + return; + } + st.closing = true; + } + // Dropping the sender makes the I/O thread's next poll end the connection. + *self.out.lock().unwrap_or_else(|e| e.into_inner()) = None; + } + + pub fn framebuffer(&self) -> Arc> { + self.fb.clone() + } + + pub fn desktop_name(&self) -> String { + self.st().name.clone() + } + + /// PointerEvent. `buttons` bit 0 is left, 1 middle, 2 right, 3/4 wheel up/down. + pub fn pointer(&self, x: u16, y: u16, buttons: u8) -> crate::Result<()> { + if self.cfg.view_only { + return Ok(()); + } + let mut m = vec![5, buttons]; + m.extend_from_slice(&x.to_be_bytes()); + m.extend_from_slice(&y.to_be_bytes()); + self.queue(m) + } + + /// KeyEvent with an X11 keysym (see `keysym_for`). + pub fn key(&self, keysym: u32, down: bool) -> crate::Result<()> { + if self.cfg.view_only { + return Ok(()); + } + let mut m = vec![4, down as u8, 0, 0]; + m.extend_from_slice(&keysym.to_be_bytes()); + self.queue(m) + } + + /// ClientCutText. RFB clipboard text is Latin-1; other characters become `?`. + pub fn send_clipboard(&self, text: &str) -> crate::Result<()> { + if self.cfg.view_only { + return Ok(()); + } + let bytes: Vec = text.chars().map(|c| u8::try_from(c).unwrap_or(b'?')).collect(); + let mut m = vec![6, 0, 0, 0]; + m.extend_from_slice(&(bytes.len() as u32).to_be_bytes()); + m.extend_from_slice(&bytes); + self.queue(m) + } + + /// Asks for a non-incremental update of the whole screen. + pub fn request_full_update(&self) -> crate::Result<()> { + let (w, h) = { + let fb = self.fb.lock().unwrap_or_else(|e| e.into_inner()); + (fb.width, fb.height) + }; + self.queue(update_request(false, w, h)) + } + + fn queue(&self, msg: Vec) -> crate::Result<()> { + let guard = self.out.lock().unwrap_or_else(|e| e.into_inner()); + if guard.as_ref().is_some_and(|tx| tx.send(msg).is_ok()) { Ok(()) } else { Err("not connected".into()) } + } + + fn io_thread(self: Arc, out_rx: Receiver>) { + let result = self.open_transport().and_then(|io| self.run(io, out_rx)); + *self.out.lock().unwrap_or_else(|e| e.into_inner()) = None; + let (was_closing, was_connected, ready) = { + let mut st = self.st(); + let r = (st.closing, st.connected, st.ready.take()); + st.connected = false; + r + }; + if let Err(e) = result { + if !was_closing { + let msg = e.to_string(); + (self.cfg.on_error)(msg.clone()); + if let Some(ready) = ready { + let _ = ready.send(Err(msg)); + } + } + } + if was_connected { + (self.cfg.on_close)(); + } + } + + fn open_transport(&self) -> crate::Result> { + if let Some(connector) = &self.cfg.connector { + return connector(); + } + let (addr, tcp) = super::dial(&self.cfg.host, self.cfg.port as i64, self.cfg.connect_timeout.as_secs().max(1) as i64); + let tcp = tcp.map_err(|e| format!("dial tcp {addr}: {e}"))?; + tcp.set_read_timeout(Some(READ_POLL))?; + tcp.set_write_timeout(Some(self.cfg.connect_timeout))?; + let _ = tcp.set_nodelay(true); + Ok(Box::new(tcp)) + } + + fn run(&self, io: Box, out_rx: Receiver>) -> crate::Result<()> { + let mut r = BufReader::with_capacity(1 << 16, Conn { io, out_rx }); + let (w, h) = self.handshake(&mut r)?; + let mut local = Framebuffer::new(w, h); + *self.fb.lock().unwrap_or_else(|e| e.into_inner()) = local.clone(); + (self.cfg.on_resize)(w, h); + { + let mut st = self.st(); + if st.closing { + return Ok(()); + } + st.connected = true; + if let Some(ready) = st.ready.take() { + let _ = ready.send(Ok(())); + } + } + let mut dec = Decoders::default(); + r.get_mut().send(&update_request(false, w, h))?; + loop { + match read_u8(&mut r)? { + 0 => { + let (y0, y1, resized) = read_update(&mut r, &mut local, &mut dec)?; + { + let mut fb = self.fb.lock().unwrap_or_else(|e| e.into_inner()); + if resized { + *fb = local.clone(); + } else if y0 < y1 { + let row = local.width as usize * 4; + fb.rgba[y0 * row..y1 * row].copy_from_slice(&local.rgba[y0 * row..y1 * row]); + } + fb.dirty = true; + } + if resized { + (self.cfg.on_resize)(local.width, local.height); + } + (self.cfg.on_frame)(); + r.get_mut().send(&update_request(!resized, local.width, local.height))?; + } + 1 => { + // SetColourMapEntries: unused with true colour. + let hdr = read_n(&mut r, 5)?; + read_n(&mut r, u16::from_be_bytes([hdr[3], hdr[4]]) as usize * 6)?; + } + 2 => {} // Bell + 3 => { + read_n(&mut r, 3)?; + let len = read_u32(&mut r)? as usize; + if len > 16 << 20 { + return Err("server clipboard text too long".into()); + } + let text: String = read_n(&mut r, len)?.iter().map(|&b| b as char).collect(); + (self.cfg.on_clipboard)(text); + } + t => return Err(format!("unsupported server message type {t}").into()), + } + } + } + + /// ProtocolVersion, security, ClientInit/ServerInit, SetPixelFormat and SetEncodings. + fn handshake(&self, r: &mut BufReader) -> crate::Result<(u16, u16)> { + let ver = read_n(r, 12)?; + let ver = String::from_utf8_lossy(&ver); + let minor = match ver.strip_prefix("RFB 003.").and_then(|s| s.trim_end().parse::().ok()) { + Some(m) => m, + None => return Err(format!("not a VNC server (got {:?})", ver.trim_end()).into()), + }; + // 3.889 is Apple's 3.8; anything between 3.3 and 3.7 is treated as 3.3, as RFB specifies. + let minor = if minor >= 8 { 8 } else if minor == 7 { 7 } else { 3 }; + r.get_mut().send(format!("RFB 003.00{minor}\n").as_bytes())?; + + let security = if minor == 3 { + match read_u32(r)? { + 0 => return Err(format!("VNC server refused the connection: {}", read_string(r)?).into()), + t => t, + } + } else { + let n = read_u8(r)? as usize; + if n == 0 { + return Err(format!("VNC server refused the connection: {}", read_string(r)?).into()); + } + let types = read_n(r, n)?; + let pick = if types.contains(&1) && (self.cfg.password.is_none() || !types.contains(&2)) { + 1 + } else if types.contains(&2) { + 2 + } else { + return Err(unsupported_security(&types).into()); + }; + r.get_mut().send(&[pick])?; + pick as u32 + }; + + match security { + 1 => {} + 2 => { + let password = self.cfg.password.as_deref().ok_or("the VNC server requires a password")?; + let challenge: [u8; 16] = read_n(r, 16)?.try_into().unwrap(); + r.get_mut().send(&vnc_auth_response(password, &challenge))?; + } + t => return Err(unsupported_security(&[t as u8]).into()), + } + if minor == 8 || security == 2 { + if read_u32(r)? != 0 { + let reason = if minor == 8 { read_string(r)? } else { String::new() }; + return Err(if reason.is_empty() { + "VNC authentication failed (wrong password?)".into() + } else { + format!("VNC authentication failed: {reason}").into() + }); + } + } + + r.get_mut().send(&[self.cfg.shared as u8])?; + let init = read_n(r, 20)?; + let (w, h) = (u16::from_be_bytes([init[0], init[1]]), u16::from_be_bytes([init[2], init[3]])); + self.st().name = read_string(r)?; + + // 32bpp, depth 24, little-endian true colour with red in the low byte: pixels arrive as R,G,B,X. + let mut m = vec![0, 0, 0, 0, 32, 24, 0, 1, 0, 255, 0, 255, 0, 255, 0, 8, 16, 0, 0, 0]; + let mut encodings: Vec = vec![7, 16, 5, 1, 0, -223, -308, -224]; + encodings.push(-32 + self.cfg.quality as i32); // JPEG quality level + encodings.push(-256 + 2); // compression level 2, as the noVNC viewer used + m.extend_from_slice(&[2, 0]); + m.extend_from_slice(&(encodings.len() as u16).to_be_bytes()); + encodings.iter().for_each(|e| m.extend_from_slice(&e.to_be_bytes())); + r.get_mut().send(&m)?; + Ok((w, h)) + } +} + +fn unsupported_security(types: &[u8]) -> String { + if types.contains(&30) { + return "the VNC server only offers Apple Remote Desktop authentication, which isn't supported; enable \"VNC viewers may control screen with password\" in Screen Sharing settings".into(); + } + format!("the VNC server offers no supported security type (offered {types:?}; supported: None, VNC password)") +} + +/// VNC Authentication: DES-encrypt the challenge with the password (first 8 bytes, zero padded) +/// as the key, each key byte bit-reversed. +pub fn vnc_auth_response(password: &str, challenge: &[u8; 16]) -> [u8; 16] { + use des::cipher::{BlockCipherEncrypt, KeyInit}; + let mut key = [0u8; 8]; + for (k, b) in key.iter_mut().zip(password.bytes()) { + *k = b.reverse_bits(); + } + let cipher = des::Des::new(&key.into()); + let mut out = *challenge; + for chunk in out.chunks_exact_mut(8) { + let mut block: [u8; 8] = chunk.try_into().unwrap(); + let b = (&mut block).into(); + cipher.encrypt_block(b); + chunk.copy_from_slice(&block); + } + out +} + +fn update_request(incremental: bool, w: u16, h: u16) -> Vec { + let mut m = vec![3, incremental as u8, 0, 0, 0, 0]; + m.extend_from_slice(&w.to_be_bytes()); + m.extend_from_slice(&h.to_be_bytes()); + m +} + +/// The transport as seen by the decoder: reads that poll out flush the outgoing queue and retry, +/// and a dropped queue (`Client::close`) ends the connection. +struct Conn { + io: Box, + out_rx: Receiver>, +} + +impl Conn { + fn send(&mut self, data: &[u8]) -> std::io::Result<()> { + self.io.write_all(data)?; + self.io.flush() + } +} + +impl Read for Conn { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + loop { + match self.io.read(buf) { + Ok(0) => return Err(std::io::Error::new(ErrorKind::UnexpectedEof, "connection closed by server")), + Ok(n) => return Ok(n), + Err(e) if matches!(e.kind(), ErrorKind::WouldBlock | ErrorKind::TimedOut | ErrorKind::Interrupted) => {} + Err(e) => return Err(e), + } + loop { + match self.out_rx.try_recv() { + Ok(msg) => self.io.write_all(&msg)?, + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => return Err(std::io::Error::other("closed")), + } + } + self.io.flush()?; + } + } +} + +fn read_n(r: &mut impl Read, n: usize) -> crate::Result> { + let mut v = vec![0u8; n]; + r.read_exact(&mut v)?; + Ok(v) +} + +fn read_u8(r: &mut impl Read) -> crate::Result { + Ok(read_n(r, 1)?[0]) +} + +fn read_u16(r: &mut impl Read) -> crate::Result { + let b = read_n(r, 2)?; + Ok(u16::from_be_bytes([b[0], b[1]])) +} + +fn read_u32(r: &mut impl Read) -> crate::Result { + let b = read_n(r, 4)?; + Ok(u32::from_be_bytes([b[0], b[1], b[2], b[3]])) +} + +fn read_string(r: &mut impl Read) -> crate::Result { + let len = read_u32(r)? as usize; + if len > 1 << 20 { + return Err("server string too long".into()); + } + Ok(String::from_utf8_lossy(&read_n(r, len)?).into_owned()) +} + +/// Persistent zlib streams: ZRLE has one, Tight four. +struct Decoders { + zrle: Decompress, + tight: [Decompress; 4], + buf: Vec, +} + +impl Default for Decoders { + fn default() -> Decoders { + Decoders { + zrle: Decompress::new(true), + tight: std::array::from_fn(|_| Decompress::new(true)), + buf: Vec::new(), + } + } +} + +/// Inflates one server chunk (which ends at a sync flush) into `out`. +fn inflate(z: &mut Decompress, mut input: &[u8], out: &mut Vec) -> crate::Result<()> { + out.clear(); + loop { + out.reserve(64 << 10); + let (i0, o0) = (z.total_in(), z.total_out()); + z.decompress_vec(input, out, FlushDecompress::Sync).map_err(|e| format!("zlib: {e}"))?; + input = &input[(z.total_in() - i0) as usize..]; + let stalled = z.total_in() == i0 && z.total_out() == o0; + if (input.is_empty() && out.len() < out.capacity()) || stalled { + return Ok(()); + } + } +} + +/// Reads one FramebufferUpdate (after the type byte) into `fb`. Returns the changed row range and +/// whether the desktop was resized. +fn read_update(r: &mut impl Read, fb: &mut Framebuffer, dec: &mut Decoders) -> crate::Result<(usize, usize, bool)> { + read_u8(r)?; + let n = read_u16(r)?; + let (mut y0, mut y1, mut resized) = (usize::MAX, 0, false); + for _ in 0..n { + let h = read_n(r, 12)?; + let be = |i: usize| u16::from_be_bytes([h[i], h[i + 1]]); + let (x, y, w, ht) = (be(0), be(2), be(4), be(6)); + let enc = i32::from_be_bytes([h[8], h[9], h[10], h[11]]); + match enc { + -224 => break, // LastRect + -223 | -308 => { + if enc == -308 { + let screens = read_n(r, 4)?[0] as usize; + read_n(r, screens * 16)?; + } + // ExtendedDesktopSize y is the status; non-zero answers a SetDesktopSize we never send. + if enc == -223 || y == 0 { + *fb = Framebuffer::new(w, ht); + resized = true; + } + continue; + } + _ => {} + } + fb.check(x, y, w, ht)?; + match enc { + 0 => { + let px = read_n(r, w as usize * ht as usize * 4)?; + fb.put(x, y, w, ht, &px, 4); + } + 1 => { + let (sx, sy) = (read_u16(r)?, read_u16(r)?); + fb.check(sx, sy, w, ht)?; + let row = w as usize * 4; + let stride = fb.width as usize * 4; + let src: Vec = (0..ht as usize) + .flat_map(|i| { + let o = (sy as usize + i) * stride + sx as usize * 4; + fb.rgba[o..o + row].to_vec() + }) + .collect(); + fb.put(x, y, w, ht, &src, 4); + } + 5 => hextile(r, fb, x, y, w, ht)?, + 7 => tight(r, fb, dec, x, y, w, ht)?, + 16 => { + let len = read_u32(r)? as usize; + let data = read_n(r, len)?; + inflate(&mut dec.zrle, &data, &mut dec.buf)?; + zrle(&dec.buf, fb, x, y, w, ht)?; + } + e => return Err(format!("unsupported encoding {e}").into()), + } + y0 = y0.min(y as usize); + y1 = y1.max(y as usize + ht as usize); + } + Ok((y0.min(y1), y1, resized)) +} + +fn hextile(r: &mut impl Read, fb: &mut Framebuffer, x: u16, y: u16, w: u16, h: u16) -> crate::Result<()> { + let (mut bg, mut fg) = ([0u8; 4], [0u8; 4]); + for ty in (y..y + h).step_by(16) { + for tx in (x..x + w).step_by(16) { + let (tw, th) = (16.min(x + w - tx), 16.min(y + h - ty)); + let sub = read_u8(r)?; + if sub & 1 != 0 { + let px = read_n(r, tw as usize * th as usize * 4)?; + fb.put(tx, ty, tw, th, &px, 4); + continue; + } + if sub & 2 != 0 { + bg.copy_from_slice(&read_n(r, 4)?); + } + if sub & 4 != 0 { + fg.copy_from_slice(&read_n(r, 4)?); + } + fb.fill(tx, ty, tw, th, &bg); + if sub & 8 != 0 { + for _ in 0..read_u8(r)? { + let colour = if sub & 16 != 0 { read_n(r, 4)? } else { fg.to_vec() }; + let b = read_n(r, 2)?; + let (sx, sy, sw, sh) = ((b[0] >> 4) as u16, (b[0] & 15) as u16, (b[1] >> 4) as u16 + 1, (b[1] & 15) as u16 + 1); + if sx + sw > tw || sy + sh > th { + return Err("hextile subrect outside its tile".into()); + } + fb.fill(tx + sx, ty + sy, sw, sh, &colour); + } + } + } + } + Ok(()) +} + +/// ZRLE: 64×64 tiles of 3-byte CPIXELs (R, G, B with our pixel format). +fn zrle(data: &[u8], fb: &mut Framebuffer, x: u16, y: u16, w: u16, h: u16) -> crate::Result<()> { + let mut p = 0usize; + let mut take = |n: usize| take(data, &mut p, n); + for ty in (y..y + h).step_by(64) { + for tx in (x..x + w).step_by(64) { + let (tw, th) = (64.min(x + w - tx), 64.min(y + h - ty)); + let count = tw as usize * th as usize; + let sub = take(1)?[0]; + let mut px = Vec::with_capacity(count * 3); + match sub { + 0 => px.extend_from_slice(take(count * 3)?), + 1 => fb.fill(tx, ty, tw, th, take(3)?), + 2..=16 => { + let palette = take(sub as usize * 3)?.to_vec(); + let bits = match sub { + 2 => 1, + 3..=4 => 2, + _ => 4, + }; + let row_bytes = (tw as usize * bits).div_ceil(8); + let packed = take(row_bytes * th as usize)?; + for row in packed.chunks_exact(row_bytes) { + for i in 0..tw as usize { + let bit = i * bits; + let idx = (row[bit / 8] >> (8 - bits - bit % 8)) as usize & ((1 << bits) - 1); + px.extend_from_slice(palette.get(idx * 3..idx * 3 + 3).ok_or("ZRLE palette index out of range")?); + } + } + } + 128 | 130..=255 => { + let palette = if sub == 128 { Vec::new() } else { take((sub as usize - 128) * 3)?.to_vec() }; + while px.len() < count * 3 { + let (colour, run) = if sub == 128 { + (take(3)?.to_vec(), Some(())) + } else { + let b = take(1)?[0]; + let i = (b & 127) as usize; + (palette.get(i * 3..i * 3 + 3).ok_or("ZRLE palette index out of range")?.to_vec(), (b & 128 != 0).then_some(())) + }; + let mut len = 1; + if run.is_some() { + loop { + let b = take(1)?[0]; + len += b as usize; + if b != 255 { + break; + } + } + } + if px.len() + len * 3 > count * 3 { + return Err("ZRLE run overflows its tile".into()); + } + (0..len).for_each(|_| px.extend_from_slice(&colour)); + } + } + s => return Err(format!("invalid ZRLE subencoding {s}").into()), + } + if sub != 1 { + fb.put(tx, ty, tw, th, &px, 3); + } + } + } + Ok(()) +} + +fn take<'a>(data: &'a [u8], p: &mut usize, n: usize) -> crate::Result<&'a [u8]> { + let s = data.get(*p..*p + n).ok_or("truncated ZRLE data")?; + *p += n; + Ok(s) +} + +fn compact_len(r: &mut impl Read) -> crate::Result { + let mut len = 0usize; + for i in 0..3 { + let b = read_u8(r)? as usize; + if i == 2 { + return Ok(len | b << 14); + } + len |= (b & 127) << (7 * i); + if b & 128 == 0 { + break; + } + } + Ok(len) +} + +/// Tight: fill, JPEG, or basic compression (copy/palette/gradient filters over four zlib streams). +fn tight(r: &mut impl Read, fb: &mut Framebuffer, dec: &mut Decoders, x: u16, y: u16, w: u16, h: u16) -> crate::Result<()> { + let ctl = read_u8(r)?; + for (i, z) in dec.tight.iter_mut().enumerate() { + if ctl & (1 << i) != 0 { + *z = Decompress::new(true); + } + } + let (wu, hu) = (w as usize, h as usize); + match ctl >> 4 { + 8 => { + let rgb = read_n(r, 3)?; + fb.fill(x, y, w, h, &rgb); + return Ok(()); + } + 9 => { + let len = compact_len(r)?; + let jpeg = read_n(r, len)?; + let img = image::load_from_memory_with_format(&jpeg, image::ImageFormat::Jpeg) + .map_err(|e| format!("Tight JPEG: {e}"))? + .to_rgb8(); + if img.width() as usize != wu || img.height() as usize != hu { + return Err("Tight JPEG size doesn't match its rectangle".into()); + } + fb.put(x, y, w, h, img.as_raw(), 3); + return Ok(()); + } + c if c & 8 != 0 => return Err(format!("unsupported Tight compression type {c:#x}").into()), + _ => {} + } + let filter = if ctl & 0x40 != 0 { read_u8(r)? } else { 0 }; + let palette = if filter == 1 { + let n = read_u8(r)? as usize + 1; + read_n(r, n * 3)? + } else { + Vec::new() + }; + let size = match (filter, palette.len() / 3) { + (0 | 2, _) => wu * hu * 3, + (1, 2) => wu.div_ceil(8) * hu, + (1, _) => wu * hu, + (f, _) => return Err(format!("unsupported Tight filter {f}").into()), + }; + let data = if size < 12 { + read_n(r, size)? + } else { + let len = compact_len(r)?; + let compressed = read_n(r, len)?; + inflate(&mut dec.tight[(ctl >> 4 & 3) as usize], &compressed, &mut dec.buf)?; + std::mem::take(&mut dec.buf) + }; + if data.len() < size { + return Err("truncated Tight data".into()); + } + let px: Vec = match filter { + 1 => { + let mut px = Vec::with_capacity(wu * hu * 3); + let mut colour = |i: usize| -> crate::Result<()> { + px.extend_from_slice(palette.get(i * 3..i * 3 + 3).ok_or("Tight palette index out of range")?); + Ok(()) + }; + if palette.len() == 6 { + for row in data[..size].chunks_exact(wu.div_ceil(8)) { + for i in 0..wu { + colour((row[i / 8] >> (7 - i % 8)) as usize & 1)?; + } + } + } else { + data[..size].iter().try_for_each(|&i| colour(i as usize))?; + } + px + } + 2 => { + let mut px = vec![0u8; size]; + for j in 0..hu { + for i in 0..wu { + for c in 0..3 { + let at = |i: usize, j: usize| px[(j * wu + i) * 3 + c] as i32; + let left = if i > 0 { at(i - 1, j) } else { 0 }; + let up = if j > 0 { at(i, j - 1) } else { 0 }; + let ul = if i > 0 && j > 0 { at(i - 1, j - 1) } else { 0 }; + let pred = (left + up - ul).clamp(0, 255) as u8; + px[(j * wu + i) * 3 + c] = pred.wrapping_add(data[(j * wu + i) * 3 + c]); + } + } + } + px + } + _ => data, + }; + fb.put(x, y, w, h, &px, 3); + Ok(()) +} + +/// The X11 keysym for a key: a named key (DOM `KeyboardEvent.key` / egui names), otherwise the +/// typed character (Latin-1 maps directly, other Unicode to `0x01000000 | codepoint`). +pub fn keysym_for(name: &str, ch: Option) -> Option { + let named = match name { + "Enter" | "Return" => 0xff0d, + "Backspace" => 0xff08, + "Tab" => 0xff09, + "Escape" | "Esc" => 0xff1b, + "ArrowLeft" | "Left" => 0xff51, + "ArrowUp" | "Up" => 0xff52, + "ArrowRight" | "Right" => 0xff53, + "ArrowDown" | "Down" => 0xff54, + "Home" => 0xff50, + "End" => 0xff57, + "PageUp" => 0xff55, + "PageDown" => 0xff56, + "Insert" => 0xff63, + "Delete" => 0xffff, + "Shift" | "ShiftLeft" => 0xffe1, + "ShiftRight" => 0xffe2, + "Control" | "Ctrl" | "ControlLeft" => 0xffe3, + "ControlRight" => 0xffe4, + "Alt" | "AltLeft" => 0xffe9, + "AltRight" => 0xffea, + "AltGraph" => 0xfe03, + "Super" | "Meta" | "Command" | "OS" | "SuperLeft" | "MetaLeft" => 0xffeb, + "SuperRight" | "MetaRight" => 0xffec, + "CapsLock" => 0xffe5, + "Space" => 0x20, + _ => match name.strip_prefix('F').and_then(|n| n.parse::().ok()) { + Some(n @ 1..=12) => 0xffbe + n - 1, + _ => 0, + }, + }; + if named != 0 { + return Some(named); + } + let c = ch? as u32; + match c { + 0x20..=0x7e | 0xa0..=0xff => Some(c), + 0..=0x1f | 0x7f..=0x9f => None, + _ => Some(0x0100_0000 | c), + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::net::TcpListener; + + #[test] + fn des_response_matches_known_vector() { + // openssl enc -des-ecb -K 0e86ceceeef64e26 (bit-reversed "password") over "0123456789abcdef". + let got = vnc_auth_response("password", b"0123456789abcdef"); + let want = [0x56, 0x45, 0xab, 0xeb, 0x5f, 0x1e, 0x64, 0x75, 0xe8, 0xfe, 0xb1, 0x1b, 0xeb, 0x66, 0xea, 0x19]; + assert_eq!(got, want); + // Only the first 8 password bytes count. + assert_eq!(vnc_auth_response("password-too-long", b"0123456789abcdef"), want); + } + + #[test] + fn keysyms() { + assert_eq!(keysym_for("Enter", None), Some(0xff0d)); + assert_eq!(keysym_for("F1", None), Some(0xffbe)); + assert_eq!(keysym_for("F12", None), Some(0xffc9)); + assert_eq!(keysym_for("F13", None), None); + assert_eq!(keysym_for("Control", None), Some(0xffe3)); + assert_eq!(keysym_for("ArrowDown", None), Some(0xff54)); + assert_eq!(keysym_for("a", Some('a')), Some(0x61)); + assert_eq!(keysym_for("", Some('é')), Some(0xe9)); + assert_eq!(keysym_for("", Some('€')), Some(0x0100_20ac)); + assert_eq!(keysym_for("", Some('\u{1}')), None); + } + + #[test] + fn compact_lengths() { + assert_eq!(compact_len(&mut &[0x05][..]).unwrap(), 5); + assert_eq!(compact_len(&mut &[0x90, 0x4e][..]).unwrap(), 10000); + assert_eq!(compact_len(&mut &[0xff, 0xff, 0xff][..]).unwrap(), 4194303); + } + + fn zlib(data: &[u8]) -> Vec { + let mut z = flate2::write::ZlibEncoder::new(Vec::new(), flate2::Compression::default()); + z.write_all(data).unwrap(); + z.finish().unwrap() + } + + fn rect(m: &mut Vec, x: u16, y: u16, w: u16, h: u16, enc: i32) { + for v in [x, y, w, h] { + m.extend_from_slice(&v.to_be_bytes()); + } + m.extend_from_slice(&enc.to_be_bytes()); + } + + fn fake_server(listener: TcpListener, password: &'static str) -> std::thread::JoinHandle> { + std::thread::spawn(move || { + let (mut s, _) = listener.accept().unwrap(); + s.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); + let read = |s: &mut std::net::TcpStream, n: usize| { + let mut b = vec![0u8; n]; + s.read_exact(&mut b).unwrap(); + b + }; + s.write_all(b"RFB 003.008\n").unwrap(); + assert_eq!(read(&mut s, 12), b"RFB 003.008\n"); + s.write_all(&[1, 2]).unwrap(); + assert_eq!(read(&mut s, 1), [2]); + let challenge = *b"0123456789abcdef"; + s.write_all(&challenge).unwrap(); + if read(&mut s, 16) != vnc_auth_response(password, &challenge) { + s.write_all(&[0, 0, 0, 1, 0, 0, 0, 14]).unwrap(); + s.write_all(b"wrong password").unwrap(); + return Vec::new(); + } + s.write_all(&[0, 0, 0, 0]).unwrap(); + assert_eq!(read(&mut s, 1), [1]); // shared + let mut init = vec![0, 4, 0, 4]; + init.extend_from_slice(&[32, 24, 0, 1, 0, 255, 0, 255, 0, 255, 16, 8, 0, 0, 0, 0]); + init.extend_from_slice(&[0, 0, 0, 4]); + init.extend_from_slice(b"test"); + s.write_all(&init).unwrap(); + let pf = read(&mut s, 20); + assert_eq!(pf[0], 0); + let enc_hdr = read(&mut s, 4); + assert_eq!(enc_hdr[0], 2); + read(&mut s, u16::from_be_bytes([enc_hdr[2], enc_hdr[3]]) as usize * 4); + assert_eq!(read(&mut s, 10), update_request(false, 4, 4)); + + let mut m = vec![0, 0, 0, 5]; + rect(&mut m, 0, 0, 2, 2, 0); // Raw: red, green / blue, white + m.extend_from_slice(&[255, 0, 0, 0, 0, 255, 0, 0, 0, 0, 255, 0, 255, 255, 255, 0]); + rect(&mut m, 2, 2, 2, 2, 1); // CopyRect from (0,0) + m.extend_from_slice(&[0, 0, 0, 0]); + rect(&mut m, 2, 0, 2, 2, 16); // ZRLE solid yellow + let z = zlib(&[1, 255, 255, 0]); + m.extend_from_slice(&(z.len() as u32).to_be_bytes()); + m.extend_from_slice(&z); + rect(&mut m, 0, 2, 1, 2, 5); // Hextile background cyan + m.extend_from_slice(&[2, 0, 255, 255, 0]); + rect(&mut m, 1, 2, 1, 2, 7); // Tight fill magenta + m.extend_from_slice(&[0x80, 255, 0, 255]); + s.write_all(&m).unwrap(); + assert_eq!(read(&mut s, 10), update_request(true, 4, 4)); + read(&mut s, 8) // the key event + }) + } + + #[test] + fn handshake_auth_and_decoding_against_fake_server() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + let server = fake_server(listener, "secret"); + let (frame_tx, frame_rx) = mpsc::channel(); + let (close_tx, close_rx) = mpsc::channel(); + let client = Client::new(Config { + host: "127.0.0.1".into(), + port, + password: Some("secret".into()), + connect_timeout: Duration::from_secs(5), + on_frame: Arc::new(move || frame_tx.send(()).unwrap()), + on_close: Arc::new(move || close_tx.send(()).unwrap()), + ..Default::default() + }); + client.connect().unwrap(); + assert_eq!(client.desktop_name(), "test"); + frame_rx.recv_timeout(Duration::from_secs(5)).unwrap(); + { + let fb = client.framebuffer(); + let fb = fb.lock().unwrap(); + assert_eq!((fb.width, fb.height), (4, 4)); + assert!(fb.dirty); + let px = |x: usize, y: usize| &fb.rgba[(y * 4 + x) * 4..(y * 4 + x) * 4 + 4]; + assert_eq!(px(0, 0), [255, 0, 0, 255]); + assert_eq!(px(1, 1), [255, 255, 255, 255]); + assert_eq!(px(2, 2), [255, 0, 0, 255]); // copied red + assert_eq!(px(3, 2), [0, 255, 0, 255]); // copied green + assert_eq!(px(2, 3), [0, 0, 255, 255]); // copied blue + assert_eq!(px(3, 1), [255, 255, 0, 255]); // ZRLE + assert_eq!(px(0, 3), [0, 255, 255, 255]); // Hextile + assert_eq!(px(1, 2), [255, 0, 255, 255]); // Tight + } + client.key(0xff0d, true).unwrap(); + assert_eq!(server.join().unwrap(), [4, 1, 0, 0, 0, 0, 0xff, 0x0d]); + client.close(); + client.close(); + close_rx.recv_timeout(Duration::from_secs(5)).unwrap(); + assert!(close_rx.recv_timeout(Duration::from_millis(200)).is_err()); + } + + #[test] + fn wrong_password_reports_reason() { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let port = listener.local_addr().unwrap().port(); + let server = fake_server(listener, "secret"); + let client = Client::new(Config { + host: "127.0.0.1".into(), + port, + password: Some("nope".into()), + connect_timeout: Duration::from_secs(5), + ..Default::default() + }); + let err = client.connect().unwrap_err().to_string(); + assert_eq!(err, "VNC authentication failed: wrong password"); + server.join().unwrap(); + } + + #[test] + fn zrle_and_tight_palettes_and_gradient() { + let px = |fb: &Framebuffer, x: usize| fb.rgba[x * 4..x * 4 + 3].to_vec(); + let (red, blue) = ([255u8, 0, 0], [0u8, 0, 255]); + + // ZRLE packed palette (1 bit/pixel): blue, red, blue. + let mut fb = Framebuffer::new(3, 1); + zrle(&[2, 255, 0, 0, 0, 0, 255, 0b1010_0000], &mut fb, 0, 0, 3, 1).unwrap(); + assert_eq!([px(&fb, 0), px(&fb, 1), px(&fb, 2)], [blue.to_vec(), red.to_vec(), blue.to_vec()]); + // ZRLE palette RLE: index 0 run of 2, then index 1 once. + zrle(&[130, 255, 0, 0, 0, 0, 255, 0x80, 1, 1], &mut fb, 0, 0, 3, 1).unwrap(); + assert_eq!([px(&fb, 0), px(&fb, 1), px(&fb, 2)], [red.to_vec(), red.to_vec(), blue.to_vec()]); + + // Tight two-colour palette, uncompressed (< 12 bytes): red, blue, red. + let mut dec = Decoders::default(); + let msg = [0x40, 1, 1, 255, 0, 0, 0, 0, 255, 0b0100_0000]; + tight(&mut &msg[..], &mut fb, &mut dec, 0, 0, 3, 1).unwrap(); + assert_eq!([px(&fb, 0), px(&fb, 1), px(&fb, 2)], [red.to_vec(), blue.to_vec(), red.to_vec()]); + + // Tight gradient filter over zlib stream 1: a flat 2x2 colour. + let mut fb = Framebuffer::new(2, 2); + let z = zlib(&[10, 20, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0]); + let mut msg = vec![0x50, 2, z.len() as u8]; + msg.extend_from_slice(&z); + tight(&mut &msg[..], &mut fb, &mut dec, 0, 0, 2, 2).unwrap(); + assert!((0..4).all(|i| px(&fb, i) == [10, 20, 30])); + } + + #[test] + fn apple_only_security_is_explained() { + assert!(unsupported_security(&[30, 33]).contains("Apple Remote Desktop")); + } +} From d654671bc104d9b74077eb4082550f7f185be646 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:36:37 +0100 Subject: [PATCH 15/45] rust: Service facade (profiles, credentials, TOTP, import/export, SSH keys, SFTP, updates) --- rust/core/src/lib.rs | 1 + rust/core/src/service.rs | 402 ++++++++++++++++++++++++++++++++++++ rust/core/src/sessions.rs | 7 +- rust/core/tests/sessions.rs | 10 +- 4 files changed, 410 insertions(+), 10 deletions(-) create mode 100644 rust/core/src/service.rs diff --git a/rust/core/src/lib.rs b/rust/core/src/lib.rs index 9b1131f..2a4ec6e 100644 --- a/rust/core/src/lib.rs +++ b/rust/core/src/lib.rs @@ -9,6 +9,7 @@ pub mod keyfiles; pub mod models; pub mod recording; pub mod secrets; +pub mod service; pub mod sessions; pub mod sshconfig; pub mod storage; diff --git a/rust/core/src/service.rs b/rust/core/src/service.rs new file mode 100644 index 0000000..2d385e7 --- /dev/null +++ b/rust/core/src/service.rs @@ -0,0 +1,402 @@ +//! The app-facing API (a port of internal/api.Service minus the Wails plumbing): profiles, +//! sessions, credentials, known hosts, preferences, snippets, import/export, SSH keys, updates. +//! File dialogs live in the UI; methods here take paths. + +use crate::clients::sftp::{self, FileEntry}; +use crate::config::AppConfig; +use crate::models::*; +use crate::secrets::{KEYCHAIN_SERVICE, Keychain, LEGACY_KEYCHAIN_SERVICE, Secrets, Vault}; +use crate::sessions::Manager; +use crate::storage::Store; +use crate::{Result, exportimport, keyfiles, sshconfig, updater, validation}; +use std::path::{Path, PathBuf}; +use std::sync::Arc; +use std::time::{Duration, Instant}; + +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +const MAX_IMPORT_JSON_BYTES: usize = 5 * 1024 * 1024; + +pub struct Service { + pub config: AppConfig, + pub store: Arc, + pub secrets: Arc, + pub sessions: Arc, +} + +#[derive(Clone, Debug)] +pub struct SshKeyInfo { + pub name: String, + pub kind: String, + pub public_key: String, + pub private_key_path: PathBuf, +} + +impl Service { + pub fn open(config: AppConfig) -> Result { + let store = Arc::new(Store::open(&config.db_path)?); + let secrets = Arc::new(Secrets::new(Vault::new(config.vault_dir.clone()), Keychain::new(KEYCHAIN_SERVICE, Some(LEGACY_KEYCHAIN_SERVICE)))); + let sessions = Manager::new(store.clone(), secrets.clone(), config.recordings_dir.clone()); + Ok(Service { config, store, secrets, sessions }) + } + + // --- profiles ----------------------------------------------------------------------- + + pub fn create_profile(&self, mut p: Profile) -> Result { + if p.id.is_empty() { + p.id = new_id(); + } + p.normalize(); + p.created_at = now(); + p.updated_at = p.created_at; + validation::validate_profile(&p)?; + self.store.create_profile(&p).map_err(|e| format!("failed to create profile: {e}"))?; + self.store.add_audit("profile.create", &p.id, "", &format!("Created profile {:?}", p.display_name)); + Ok(p) + } + + pub fn update_profile(&self, mut p: Profile) -> Result { + p.normalize(); + p.updated_at = now(); + validation::validate_profile(&p)?; + self.store.update_profile(&p).map_err(|e| format!("failed to update profile: {e}"))?; + self.store.add_audit("profile.update", &p.id, "", &format!("Updated profile {:?}", p.display_name)); + Ok(p) + } + + pub fn delete_profile(&self, id: &str) -> Result<()> { + let p = self.store.get_profile(id).map_err(|e| format!("profile not found: {e}"))?; + self.store.delete_profile(id).map_err(|e| format!("failed to delete profile: {e}"))?; + self.secrets.delete_profile(id); + self.store.add_audit("profile.delete", id, "", &format!("Deleted profile {:?}", p.display_name)); + Ok(()) + } + + pub fn duplicate_profile(&self, id: &str) -> Result { + let mut d = self.store.get_profile(id).map_err(|e| format!("profile not found: {e}"))?; + d.id = new_id(); + d.display_name += " (copy)"; + d.created_at = now(); + d.updated_at = d.created_at; + d.last_connected = None; + self.store.create_profile(&d).map_err(|e| format!("failed to duplicate profile: {e}"))?; + self.store.add_audit("profile.create", &d.id, "", &format!("Created profile {:?}", d.display_name)); + Ok(d) + } + + pub fn profiles(&self) -> Vec { + self.store.list_profiles().unwrap_or_default() + } + + pub fn set_favourite(&self, id: &str, fav: bool) -> Result<()> { + self.store.set_favourite(id, fav) + } + + // --- sessions ----------------------------------------------------------------------- + + /// Opens a saved profile; returns the new session id. + pub fn connect_profile(&self, id: &str) -> Result { + let mut p = self.store.get_profile(id).map_err(|e| format!("profile not found: {e}"))?; + let sid = self.sessions.connect(p.clone()); + p.last_connected = Some(now()); + p.updated_at = now(); + let _ = self.store.update_profile(&p); + Ok(sid) + } + + /// Opens an unsaved profile (Quick Connect). + pub fn connect_quick(&self, mut p: Profile) -> String { + if p.id.is_empty() { + p.id = new_id(); + } + p.normalize(); + self.sessions.connect(p) + } + + pub fn connect_local(&self) -> String { + let shell = self.store.preferences().local_shell; + self.sessions.connect_local(&shell) + } + + pub fn reconnect(&self, id: &str) -> Result { + self.sessions.reconnect(id) + } + + // --- credentials -------------------------------------------------------------------- + + pub fn set_profile_secret(&self, profile_id: &str, kind: &str, value: &str) -> Result<()> { + self.secrets.set_checked(&format!("profile:{profile_id}:{kind}"), value) + } + + pub fn has_totp(&self, profile_id: &str) -> bool { + self.secrets.get(&format!("profile:{profile_id}:totp")).is_some() + } + + pub fn totp_code(&self, profile_id: &str) -> Result { + let s = self.secrets.get(&format!("profile:{profile_id}:totp")).ok_or("no TOTP secret configured for this profile")?; + crate::clients::ssh::totp_code(&s).map_err(|e| format!("failed to generate TOTP code: {e}").into()) + } + + pub fn delete_totp(&self, profile_id: &str) { + self.secrets.delete(&format!("profile:{profile_id}:totp")); + } + + // --- known hosts, prefs, snippets, audit -------------------------------------------- + + pub fn known_hosts(&self) -> Vec { + self.store.list_known_hosts().unwrap_or_default() + } + + pub fn delete_known_host(&self, id: &str) -> Result<()> { + self.store.delete_known_host(id) + } + + pub fn preferences(&self) -> AppPreferences { + self.store.preferences() + } + + pub fn save_preferences(&self, p: &AppPreferences) -> Result<()> { + self.store.save_preferences(p) + } + + pub fn snippets(&self) -> Vec { + self.store.list_snippets().unwrap_or_default() + } + + pub fn save_snippet(&self, mut s: Snippet) -> Result { + if s.name.trim().is_empty() || s.command.trim().is_empty() { + return Err("snippet name and command are required".into()); + } + s.updated_at = now(); + if s.id.is_empty() { + s.id = new_id(); + s.created_at = s.updated_at; + self.store.create_snippet(&s)?; + } else { + self.store.update_snippet(&s)?; + } + Ok(s) + } + + pub fn delete_snippet(&self, id: &str) -> Result<()> { + self.store.delete_snippet(id) + } + + // --- import / export ---------------------------------------------------------------- + + fn import(&self, mut profiles: Vec, strategy: &str) -> Result { + if !matches!(strategy, "skip" | "overwrite" | "duplicate") { + return Err(format!("unknown import strategy {strategy:?} (expected skip, overwrite, or duplicate)").into()); + } + for (i, p) in profiles.iter_mut().enumerate() { + p.normalize(); + validation::validate_profile(p).map_err(|e| format!("profile import entry {i} is invalid: {e}"))?; + } + let mut n = 0; + for mut p in profiles { + let exists = self.store.get_profile(&p.id).is_ok(); + let ok = match strategy { + "skip" if exists => continue, + "overwrite" if exists => { + p.updated_at = now(); + self.store.update_profile(&p).is_ok() + } + "duplicate" => { + p.id = new_id(); + p.created_at = now(); + p.updated_at = p.created_at; + self.store.create_profile(&p).is_ok() + } + _ => self.store.create_profile(&p).is_ok(), + }; + n += ok as usize; + } + self.store.add_audit("import", "", "", &format!("Imported {n} profile(s)")); + Ok(n) + } + + /// Plain JSON (as the old Export button wrote): secrets are redacted unless asked for. + pub fn export_json(&self, include_secrets: bool) -> Result { + let mut ps = self.profiles(); + if !include_secrets { + for p in &mut ps { + if let Some(s) = &mut p.ssh { + s.proxy_password.clear(); + s.totp_secret.clear(); + } + } + } + self.store.add_audit("export", "", "", &format!("Exported {} profile(s) {}", ps.len(), if include_secrets { "with secrets" } else { "without secrets" })); + Ok(serde_json::to_string_pretty(&ps)?) + } + + pub fn import_json(&self, json: &str, strategy: &str) -> Result { + if json.len() > MAX_IMPORT_JSON_BYTES { + return Err(format!("import data is too large (max {MAX_IMPORT_JSON_BYTES} bytes)").into()); + } + let ps: Vec> = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {e}"))?; + let ps = ps.into_iter().enumerate().map(|(i, p)| p.ok_or_else(|| format!("profile import entry {i} is null"))).collect::, _>>()?; + self.import(ps, strategy) + } + + /// Encrypted export of the given profiles (all when empty) to `path` (`.nexus` appended). + pub fn export_encrypted(&self, ids: &[String], password: &str, path: &Path) -> Result { + let ps: Vec = if ids.is_empty() { self.profiles() } else { ids.iter().filter_map(|id| self.store.get_profile(id).ok()).collect() }; + if ps.is_empty() { + return Err("no profiles to export".into()); + } + let mut path = path.to_path_buf(); + if path.extension().and_then(|e| e.to_str()) != Some(exportimport::EXTENSION) { + path.as_mut_os_string().push(format!(".{}", exportimport::EXTENSION)); + } + exportimport::encrypt_and_export(&ps, password, &path)?; + self.store.add_audit("export", "", "", &format!("Exported {} profile(s) without secrets", ps.len())); + Ok(path) + } + + /// Imports a `.nexus` (or legacy `.link`) export. + pub fn import_encrypted(&self, path: &Path, password: &str, strategy: &str) -> Result { + let ps = exportimport::decrypt_and_import(path, password)?; + self.import(ps, strategy) + } + + pub fn import_ssh_config(&self, path: Option<&Path>, folder: &str) -> Result { + let path = path.map(Path::to_path_buf).or_else(sshconfig::default_config_path).ok_or("no ~/.ssh/config found")?; + let blocks = sshconfig::parse_file(&path)?; + let mut n = 0; + for p in sshconfig::to_profiles(&blocks, folder) { + n += self.store.create_profile(&p).is_ok() as usize; + } + self.store.add_audit("import", "", "", &format!("Imported {n} profile(s)")); + Ok(n) + } + + // --- SSH keys (via ssh-keygen, as before) ------------------------------------------- + + fn ssh_dir() -> Result { + let home = std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" }).ok_or("unable to determine home directory")?; + Ok(PathBuf::from(home).join(".ssh")) + } + + pub fn ssh_key_pairs(&self) -> Result> { + let dir = Self::ssh_dir()?; + let Ok(rd) = std::fs::read_dir(&dir) else { return Ok(vec![]) }; + let mut out = vec![]; + let mut names: Vec = rd.flatten().filter_map(|e| e.file_name().to_str().and_then(|n| n.strip_suffix(".pub")).map(String::from)).collect(); + names.sort(); + for base in names { + let priv_path = dir.join(&base); + let Ok(pubk) = std::fs::read_to_string(dir.join(format!("{base}.pub"))) else { continue }; + if !priv_path.is_file() || pubk.len() > 64 * 1024 { + continue; + } + let pubk = pubk.trim().to_string(); + let algo = pubk.split_whitespace().next().unwrap_or(""); + let kind = ["ed25519", "ecdsa", "rsa", "dsa"].into_iter().find(|k| algo.contains(k)).unwrap_or("unknown"); + out.push(SshKeyInfo { name: base, kind: kind.into(), public_key: pubk, private_key_path: priv_path }); + } + Ok(out) + } + + pub fn generate_ssh_key(&self, name: &str, kind: &str, bits: u32, passphrase: &str) -> Result<()> { + let name = keyfiles::validate_ssh_key_name(name)?; + let kind = if kind.trim().is_empty() { "ed25519".to_string() } else { kind.trim().to_lowercase() }; + let bits = match kind.as_str() { + "ed25519" | "ecdsa" => bits, + "rsa" if bits == 0 => 4096, + "rsa" if [2048, 3072, 4096].contains(&bits) => bits, + "rsa" => return Err(format!("unsupported RSA key size {bits} (use 2048, 3072, or 4096)").into()), + k => return Err(format!("unsupported SSH key type {k:?}").into()), + }; + let dir = Self::ssh_dir()?; + std::fs::create_dir_all(&dir)?; + let path = dir.join(&name); + if path.exists() { + return Err(format!("key {name:?} already exists").into()); + } + let mut cmd = std::process::Command::new("ssh-keygen"); + cmd.args(["-q", "-t", &kind, "-f"]).arg(&path).args(["-C", &format!("{name}@nexus-terminal"), "-N", passphrase]); + if kind == "rsa" { + cmd.args(["-b", &bits.to_string()]); + } + let out = cmd.stdin(std::process::Stdio::null()).output().map_err(|e| format!("ssh-keygen failed: {e}"))?; + if !out.status.success() { + return Err(format!("ssh-keygen failed: {}", String::from_utf8_lossy(&out.stderr).trim()).into()); + } + Ok(()) + } + + pub fn delete_ssh_key(&self, name: &str) -> Result<()> { + let name = keyfiles::validate_ssh_key_name(name)?; + let p = Self::ssh_dir()?.join(&name); + let mut errs = vec![]; + for (what, f) in [("private key", p.clone()), ("public key", p.with_extension("pub"))] { + if let Err(e) = std::fs::remove_file(&f) + && e.kind() != std::io::ErrorKind::NotFound + { + errs.push(format!("{what}: {e}")); + } + } + if errs.is_empty() { Ok(()) } else { Err(format!("failed to delete: {}", errs.join("; ")).into()) } + } + + // --- SFTP --------------------------------------------------------------------------- + + pub fn sftp_list(&self, sid: &str, path: &str) -> Result> { + sftp::list(&self.sessions.ssh(sid)?.sftp()?, path) + } + + pub fn sftp_pwd(&self, sid: &str) -> Result { + sftp::pwd(&self.sessions.ssh(sid)?.sftp()?) + } + + pub fn sftp_mkdir(&self, sid: &str, path: &str) -> Result<()> { + sftp::mkdir(&self.sessions.ssh(sid)?.sftp()?, path) + } + + pub fn sftp_remove(&self, sid: &str, path: &str) -> Result<()> { + sftp::remove(&self.sessions.ssh(sid)?.sftp()?, path) + } + + pub fn sftp_rename(&self, sid: &str, from: &str, to: &str) -> Result<()> { + sftp::rename(&self.sessions.ssh(sid)?.sftp()?, from, to) + } + + /// Uploads a local file into `remote_dir`, keeping its file name. + pub fn sftp_upload_path(&self, sid: &str, remote_dir: &str, local: &Path) -> Result { + let name = keyfiles::safe_sftp_upload_name(&local.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default())?; + let remote = sftp::join(remote_dir, &name); + self.sessions.sftp_upload(sid, local, &remote)?; + Ok(remote) + } + + // --- misc --------------------------------------------------------------------------- + + /// TCP reachability and latency in milliseconds. + pub fn test_connection(&self, host: &str, port: i64) -> Result { + if host.is_empty() { + return Err("host is required".into()); + } + if !(1..=65535).contains(&port) { + return Err("port must be between 1 and 65535".into()); + } + let addr = if host.contains(':') { format!("[{host}]:{port}") } else { format!("{host}:{port}") }; + let start = Instant::now(); + let mut last = None; + for sa in std::net::ToSocketAddrs::to_socket_addrs(&addr.as_str())? { + match std::net::TcpStream::connect_timeout(&sa, Duration::from_secs(10)) { + Ok(_) => return Ok(start.elapsed().as_millis()), + Err(e) => last = Some(e), + } + } + Err(format!("connection to {addr} failed after {}ms: {}", start.elapsed().as_millis(), last.map_or("no addresses".into(), |e| e.to_string())).into()) + } + + pub fn check_for_updates(&self) -> Result { + let url = option_env!("NEXUS_UPDATE_URL").unwrap_or(updater::DEFAULT_UPDATE_URL); + updater::check_url(&updater::expand_update_url(url), VERSION) + } + + pub fn audit_entries(&self, limit: i64) -> Vec { + self.store.list_audit(limit).unwrap_or_default() + } +} diff --git a/rust/core/src/sessions.rs b/rust/core/src/sessions.rs index a4a237e..6264555 100644 --- a/rust/core/src/sessions.rs +++ b/rust/core/src/sessions.rs @@ -192,8 +192,9 @@ impl Manager { /// Opens a session for `profile` and connects it on a worker thread. Returns the session id /// immediately; progress arrives as `Event::State`. - pub fn connect(self: &Arc, profile: Profile, prompt: Prompt) -> String { + pub fn connect(self: &Arc, profile: Profile) -> String { let id = new_id(); + let prompt = self.prompt_for(&id, "Credentials Required", Duration::from_secs(300)); let info = SessionInfo { id: id.clone(), profile_id: profile.id.clone(), @@ -740,13 +741,13 @@ impl Manager { /// Closes and re-opens a session from its profile, keeping the tab name and pin; returns the /// new session id. - pub fn reconnect(self: &Arc, id: &str, prompt: Prompt) -> Result { + pub fn reconnect(self: &Arc, id: &str) -> Result { let s = self.sessions.lock().unwrap().remove(id).ok_or_else(|| format!("sessions: session {id:?} not found"))?; let (profile, tab, pinned, proto, shell) = (s.profile.clone(), s.info.tab_name.clone(), s.info.pinned, s.info.protocol, s.local_shell.clone()); self.close_session(s); let new = match (proto, profile) { (Protocol::Local, _) => self.connect_local(&shell), - (_, Some(p)) => self.connect(p, prompt), + (_, Some(p)) => self.connect(p), _ => return err("sessions: nothing to reconnect".into()), }; let _ = self.with(&new, |s| { diff --git a/rust/core/tests/sessions.rs b/rust/core/tests/sessions.rs index aba647e..be579fc 100644 --- a/rust/core/tests/sessions.rs +++ b/rust/core/tests/sessions.rs @@ -2,7 +2,7 @@ use nexus_core::models::{Profile, Protocol, SessionState}; use nexus_core::secrets::{Keychain, Secrets, Vault}; -use nexus_core::sessions::{Event, Manager, Prompt}; +use nexus_core::sessions::{Event, Manager}; use nexus_core::storage::Store; use std::io::{Read, Write}; use std::net::TcpListener; @@ -25,10 +25,6 @@ fn manager(name: &str) -> (Arc, mpsc::Receiver, std::path::PathB (m, rx, dir) } -fn no_prompt() -> Prompt { - Arc::new(|_| Err("no prompt in tests".into())) -} - /// Waits for an event matching `f`, answering host-key prompts with `accept`. fn wait(rx: &mpsc::Receiver, mut f: impl FnMut(&Event) -> Option) -> T { let deadline = std::time::Instant::now() + Duration::from_secs(15); @@ -101,7 +97,7 @@ fn raw_session_and_auto_reconnect() { p.host = "127.0.0.1".into(); p.port = port as i64; p.raw.as_mut().unwrap().auto_reconnect = true; - let id = m.connect(p, no_prompt()); + let id = m.connect(p); wait(&rx, |e| state(e, SessionState::Connected)); m.raw_write_text(&id, "ping").unwrap(); wait(&rx, |e| matches!(e, Event::RawData { direction, data, .. } if direction == "recv" && data.starts_with(b"ping")).then_some(())); @@ -124,7 +120,7 @@ fn connect_failure_reports_error_state() { let mut p = Profile::new(Protocol::Telnet); p.host = "127.0.0.1".into(); p.port = port as i64; - let id = m.connect(p, no_prompt()); + let id = m.connect(p); let err = wait(&rx, |e| state(e, SessionState::Error)); assert!(err.starts_with(&id) && err.len() > id.len() + 1, "{err}"); let _ = std::fs::remove_dir_all(dir); From 327a3a814c777c629973b6434a6a227d83f3543a Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:37:04 +0100 Subject: [PATCH 16/45] rust: app design system (theme, widgets) from Leviathan --- rust/app/src/theme.rs | 266 ++++++++++++++++++++++++++++++++++++++++ rust/app/src/widgets.rs | 146 ++++++++++++++++++++++ 2 files changed, 412 insertions(+) create mode 100644 rust/app/src/theme.rs create mode 100644 rust/app/src/widgets.rs diff --git a/rust/app/src/theme.rs b/rust/app/src/theme.rs new file mode 100644 index 0000000..28a5b46 --- /dev/null +++ b/rust/app/src/theme.rs @@ -0,0 +1,266 @@ +//! Design system: colour tokens, radii and typography (Inter for UI, JetBrains Mono for bytes). +//! Widgets read `Tokens::get(ctx)` instead of hard-coding colours, so every theme applies +//! everywhere. Copied from Leviathan Desktop so both apps share one look. + +use egui::{Color32, FontData, FontDefinitions, FontFamily, FontId, Stroke, TextStyle, Visuals}; +use std::sync::Arc; + +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum ThemeKind { + /// Charcoal panels, blue accent: a pro-tool dark. + #[default] + Pro, + /// Near-black surfaces, violet accent. + Midnight, + Light, +} + +impl ThemeKind { + pub const ALL: [ThemeKind; 3] = [ThemeKind::Pro, ThemeKind::Midnight, ThemeKind::Light]; + pub fn label(self) -> &'static str { + match self { + ThemeKind::Pro => "Pro (dark)", + ThemeKind::Midnight => "Midnight", + ThemeKind::Light => "Light", + } + } + pub fn id(self) -> &'static str { + match self { + ThemeKind::Pro => "pro", + ThemeKind::Midnight => "midnight", + ThemeKind::Light => "light", + } + } + pub fn from_id(s: &str) -> Option { + Self::ALL.into_iter().find(|k| k.id() == s) + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Tokens { + pub kind: ThemeKind, + pub chrome: Color32, + pub dock: Color32, + pub card: Color32, + pub card_border: Color32, + pub tab_strip: Color32, + pub field: Color32, + pub field_border: Color32, + pub hover: Color32, + pub row_selected: Color32, + pub text: Color32, + pub text_dim: Color32, + pub text_faint: Color32, + pub accent: Color32, + pub accent_soft: Color32, + pub accent_text: Color32, + pub danger: Color32, + pub warning: Color32, + pub success: Color32, + pub separator: Color32, + /// Hex view background and byte-class colours. + pub canvas: Color32, + pub byte_null: Color32, + pub byte_print: Color32, + pub byte_space: Color32, + pub byte_ctrl: Color32, + pub byte_high: Color32, + pub byte_ff: Color32, + pub hit: Color32, + pub bookmark: Color32, + pub patched: Color32, + pub radius: f32, +} + +impl Tokens { + pub fn for_kind(kind: ThemeKind) -> Self { + let rgb = Color32::from_rgb; + match kind { + ThemeKind::Pro => Tokens { + kind, + chrome: rgb(38, 38, 40), + dock: rgb(30, 30, 32), + card: rgb(44, 44, 47), + card_border: rgb(28, 28, 30), + tab_strip: rgb(34, 34, 36), + field: rgb(30, 30, 32), + field_border: rgb(72, 72, 76), + hover: rgb(60, 60, 64), + row_selected: rgb(40, 66, 102), + text: rgb(226, 226, 230), + text_dim: rgb(170, 170, 178), + text_faint: rgb(118, 118, 126), + accent: rgb(55, 142, 240), + accent_soft: Color32::from_rgba_unmultiplied(55, 142, 240, 56), + accent_text: Color32::WHITE, + danger: rgb(236, 91, 98), + warning: rgb(232, 176, 70), + success: rgb(80, 196, 128), + separator: rgb(24, 24, 26), + canvas: rgb(26, 26, 28), + byte_null: rgb(84, 84, 92), + byte_print: rgb(214, 222, 235), + byte_space: rgb(120, 190, 160), + byte_ctrl: rgb(232, 176, 70), + byte_high: rgb(196, 140, 236), + byte_ff: rgb(236, 110, 110), + hit: Color32::from_rgba_unmultiplied(232, 176, 70, 90), + bookmark: rgb(80, 196, 128), + patched: rgb(236, 91, 98), + radius: 4.0, + }, + ThemeKind::Midnight => Tokens { + kind, + chrome: rgb(18, 18, 21), + dock: rgb(14, 14, 16), + card: rgb(24, 24, 28), + card_border: rgb(40, 40, 46), + tab_strip: rgb(18, 18, 21), + field: rgb(32, 32, 37), + field_border: rgb(52, 52, 60), + hover: rgb(42, 42, 50), + row_selected: rgb(52, 44, 96), + text: rgb(236, 236, 242), + text_dim: rgb(150, 150, 162), + text_faint: rgb(96, 96, 108), + accent: rgb(139, 124, 246), + accent_soft: Color32::from_rgba_unmultiplied(139, 124, 246, 50), + accent_text: rgb(240, 238, 255), + separator: rgb(34, 34, 40), + canvas: rgb(12, 12, 14), + byte_null: rgb(70, 70, 82), + byte_print: rgb(220, 224, 240), + byte_space: rgb(110, 200, 180), + byte_ctrl: rgb(240, 190, 90), + byte_high: rgb(170, 150, 255), + byte_ff: rgb(255, 120, 140), + hit: Color32::from_rgba_unmultiplied(240, 190, 90, 80), + bookmark: rgb(110, 220, 150), + patched: rgb(255, 110, 120), + ..Tokens::for_kind(ThemeKind::Pro) + }, + ThemeKind::Light => Tokens { + kind, + chrome: rgb(236, 236, 240), + dock: rgb(226, 226, 231), + card: rgb(250, 250, 252), + card_border: rgb(214, 214, 220), + tab_strip: rgb(232, 232, 236), + field: rgb(255, 255, 255), + field_border: rgb(200, 200, 208), + hover: rgb(222, 222, 230), + row_selected: rgb(204, 222, 250), + text: rgb(28, 28, 32), + text_dim: rgb(84, 84, 96), + text_faint: rgb(140, 140, 150), + accent: rgb(30, 110, 220), + accent_soft: Color32::from_rgba_unmultiplied(30, 110, 220, 40), + accent_text: Color32::WHITE, + danger: rgb(200, 50, 56), + warning: rgb(176, 120, 10), + success: rgb(30, 140, 80), + separator: rgb(214, 214, 220), + canvas: rgb(252, 252, 253), + byte_null: rgb(180, 180, 190), + byte_print: rgb(30, 34, 44), + byte_space: rgb(20, 130, 100), + byte_ctrl: rgb(176, 110, 0), + byte_high: rgb(120, 70, 200), + byte_ff: rgb(200, 50, 56), + hit: Color32::from_rgba_unmultiplied(240, 180, 40, 110), + bookmark: rgb(30, 150, 80), + patched: rgb(210, 40, 50), + radius: 4.0, + }, + } + } + + pub fn get(ctx: &egui::Context) -> Tokens { + ctx.data(|d| d.get_temp::(egui::Id::new("nexus-theme"))).unwrap_or_else(|| Tokens::for_kind(ThemeKind::Pro)) + } + + pub fn dark(&self) -> bool { + self.kind != ThemeKind::Light + } +} + +pub fn install_fonts(ctx: &egui::Context) { + let mut fonts = FontDefinitions::default(); + let mut add = |name: &str, bytes: &'static [u8]| { + fonts.font_data.insert(name.into(), Arc::new(FontData::from_static(bytes))); + }; + add("Inter", include_bytes!("../assets/fonts/Inter-Regular.ttf")); + add("Inter-SemiBold", include_bytes!("../assets/fonts/Inter-SemiBold.ttf")); + add("JetBrainsMono", include_bytes!("../assets/fonts/JetBrainsMono-Regular.ttf")); + egui_phosphor::add_to_fonts(&mut fonts, egui_phosphor::Variant::Regular); + // The bundled Inter has its private-use-area cmap stripped, so phosphor's icons win there. + fonts.families.entry(FontFamily::Proportional).or_default().insert(0, "Inter".into()); + fonts.families.entry(FontFamily::Monospace).or_default().insert(0, "JetBrainsMono".into()); + let mut semibold = vec!["Inter-SemiBold".to_owned()]; + semibold.extend(fonts.families[&FontFamily::Proportional].iter().cloned()); + fonts.families.insert(FontFamily::Name("semibold".into()), semibold); + ctx.set_fonts(fonts); +} + +pub fn semibold(size: f32) -> FontId { + FontId::new(size, FontFamily::Name("semibold".into())) +} + +pub fn mono(size: f32) -> FontId { + FontId::monospace(size) +} + +pub fn apply(ctx: &egui::Context, kind: ThemeKind) { + let t = Tokens::for_kind(kind); + ctx.data_mut(|d| d.insert_temp(egui::Id::new("nexus-theme"), t)); + let mut v = if t.dark() { Visuals::dark() } else { Visuals::light() }; + v.panel_fill = t.card; + v.window_fill = t.card; + v.window_stroke = Stroke::new(1.0, t.card_border); + v.extreme_bg_color = t.field; + v.faint_bg_color = t.tab_strip; + v.code_bg_color = t.field; + v.hyperlink_color = t.accent; + v.selection.bg_fill = t.accent_soft; + v.selection.stroke = Stroke::new(1.0, t.accent); + v.override_text_color = Some(t.text); + v.warn_fg_color = t.warning; + v.error_fg_color = t.danger; + let r = egui::CornerRadius::same(t.radius as u8); + v.window_corner_radius = egui::CornerRadius::same(8); + v.menu_corner_radius = egui::CornerRadius::same(6); + for (w, fill) in [ + (&mut v.widgets.noninteractive, t.card), + (&mut v.widgets.inactive, t.field), + (&mut v.widgets.hovered, t.hover), + (&mut v.widgets.active, t.hover), + (&mut v.widgets.open, t.hover), + ] { + w.bg_fill = fill; + w.weak_bg_fill = fill; + w.corner_radius = r; + w.fg_stroke.color = t.text; + } + v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, t.separator); + v.widgets.noninteractive.fg_stroke.color = t.text_dim; + v.widgets.inactive.bg_stroke = Stroke::new(1.0, t.field_border); + v.widgets.hovered.bg_stroke = Stroke::new(1.0, t.text_faint); + v.widgets.active.bg_stroke = Stroke::new(1.0, t.accent); + v.popup_shadow = egui::epaint::Shadow { offset: [0, 6], blur: 18, spread: 0, color: Color32::from_black_alpha(if t.dark() { 120 } else { 40 }) }; + v.window_shadow = v.popup_shadow; + ctx.set_visuals(v); + ctx.all_styles_mut(|s| { + s.text_styles = [ + (TextStyle::Small, FontId::proportional(11.0)), + (TextStyle::Body, FontId::proportional(13.0)), + (TextStyle::Button, FontId::proportional(13.0)), + (TextStyle::Heading, semibold(16.0)), + (TextStyle::Monospace, FontId::monospace(12.5)), + ] + .into(); + s.spacing.item_spacing = egui::vec2(8.0, 6.0); + s.spacing.button_padding = egui::vec2(10.0, 4.0); + s.spacing.interact_size.y = 24.0; + s.spacing.scroll = egui::style::ScrollStyle::thin(); + }); +} diff --git a/rust/app/src/widgets.rs b/rust/app/src/widgets.rs new file mode 100644 index 0000000..1a7bf1e --- /dev/null +++ b/rust/app/src/widgets.rs @@ -0,0 +1,146 @@ +//! Shared widgets: section headers, buttons, chips, key/value grids, virtual lists, empty states. +//! Colours always come from `Tokens`. + +use crate::theme::{self, Tokens}; +use egui::{Align, Color32, Layout, Response, RichText, Sense, Ui, vec2}; +use egui_phosphor::regular as icon; + +/// Small uppercase section header with a hairline. +pub fn section(ui: &mut Ui, title: &str) { + let t = Tokens::get(ui.ctx()); + ui.add_space(4.0); + ui.label(RichText::new(title.to_uppercase()).font(theme::semibold(10.5)).color(t.text_faint).extra_letter_spacing(0.6)); + ui.add_space(1.0); +} + +/// An icon + label button in the secondary style. +pub fn button(ui: &mut Ui, glyph: &str, label: &str) -> Response { + ui.add(egui::Button::new(format!("{glyph} {label}"))) +} + +/// Filled accent button for a panel's primary action. +pub fn primary(ui: &mut Ui, glyph: &str, label: &str) -> Response { + let t = Tokens::get(ui.ctx()); + ui.add(egui::Button::new(RichText::new(format!("{glyph} {label}")).color(t.accent_text)).fill(t.accent)) +} + +/// A square icon-only button with a tooltip. +pub fn icon_button(ui: &mut Ui, glyph: &str, tip: &str) -> Response { + let t = Tokens::get(ui.ctx()); + let (rect, resp) = ui.allocate_exact_size(vec2(24.0, 24.0), Sense::click()); + if resp.hovered() { + ui.painter().rect_filled(rect, t.radius, t.hover); + } + let c = if resp.hovered() { t.text } else { t.text_dim }; + ui.painter().text(rect.center(), egui::Align2::CENTER_CENTER, glyph, egui::FontId::proportional(15.0), c); + resp.on_hover_text(tip) +} + +pub fn chip(ui: &mut Ui, text: &str, color: Color32) -> Response { + let font = theme::semibold(10.5); + let galley = ui.painter().layout_no_wrap(text.to_owned(), font, color); + let (rect, resp) = ui.allocate_exact_size(galley.size() + vec2(12.0, 4.0), Sense::hover()); + ui.painter().rect_filled(rect, 9.0, color.gamma_multiply(0.18)); + ui.painter().galley(rect.center() - galley.size() / 2.0, galley, color); + resp +} + +/// Two-column key/value grid; clicking a value copies it. +pub fn kv(ui: &mut Ui, id: &str, rows: &[(&str, String)]) { + let t = Tokens::get(ui.ctx()); + egui::Grid::new(id).num_columns(2).spacing(vec2(12.0, 4.0)).striped(false).show(ui, |ui| { + for (k, v) in rows { + ui.label(RichText::new(*k).color(t.text_dim)); + let r = ui.add(egui::Label::new(RichText::new(v).font(theme::mono(12.0))).sense(Sense::click()).wrap()); + if r.on_hover_text("Click to copy").clicked() { + ui.ctx().copy_text(v.clone()); + } + ui.end_row(); + } + }); +} + +/// A virtualised list of `n` rows of height `h`; `row` draws row `i` and returns whether it +/// was clicked. Hover and selection are painted for you. +pub fn rows(ui: &mut Ui, id: &str, n: usize, h: f32, selected: Option, mut row: impl FnMut(&mut Ui, usize)) -> Option { + let t = Tokens::get(ui.ctx()); + let mut clicked = None; + egui::ScrollArea::vertical().id_salt(id).auto_shrink([false, false]).show_rows(ui, h, n, |ui, range| { + for i in range { + let (rect, resp) = ui.allocate_exact_size(vec2(ui.available_width(), h), Sense::click()); + if selected == Some(i) { + ui.painter().rect_filled(rect, 3.0, t.row_selected); + } else if resp.hovered() { + ui.painter().rect_filled(rect, 3.0, t.hover.gamma_multiply(0.6)); + } else if i % 2 == 1 { + ui.painter().rect_filled(rect, 0.0, t.text.gamma_multiply(0.025)); + } + let mut child = ui.new_child(egui::UiBuilder::new().max_rect(rect.shrink2(vec2(6.0, 0.0))).layout(Layout::left_to_right(Align::Center))); + child.set_clip_rect(rect.intersect(ui.clip_rect())); + // A row never grows: long text truncates (hover shows it in full where the panel adds it). + child.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); + row(&mut child, i); + if resp.clicked() { + clicked = Some(i); + } + } + }); + clicked +} + +/// Centered empty state. +pub fn empty(ui: &mut Ui, glyph: &str, title: &str, hint: &str) { + let t = Tokens::get(ui.ctx()); + ui.vertical_centered(|ui| { + ui.add_space(28.0); + ui.label(RichText::new(glyph).size(30.0).color(t.text_faint)); + ui.add_space(4.0); + ui.label(RichText::new(title).font(theme::semibold(13.5)).color(t.text_dim)); + ui.label(RichText::new(hint).color(t.text_faint).size(12.0)); + }); +} + +/// Dim helper text. +pub fn hint(ui: &mut Ui, text: &str) { + let t = Tokens::get(ui.ctx()); + ui.label(RichText::new(text).color(t.text_faint).size(11.5)); +} + +/// A small counter line ("1,234 results"), with an optional truncation note. +pub fn count(ui: &mut Ui, n: usize, noun: &str, capped: bool) { + let t = Tokens::get(ui.ctx()); + let s = format!("{} {noun}{}", group(n as u64), if capped { " (capped)" } else { "" }); + ui.label(RichText::new(s).color(if capped { t.warning } else { t.text_dim }).size(11.5)); +} + +/// 1234567 → "1,234,567" +pub fn group(n: u64) -> String { + let s = n.to_string(); + let mut out = String::new(); + for (i, c) in s.chars().enumerate() { + if i > 0 && (s.len() - i).is_multiple_of(3) { + out.push(','); + } + out.push(c); + } + out +} + +pub fn human_size(n: u64) -> String { + const U: [&str; 5] = ["B", "KiB", "MiB", "GiB", "TiB"]; + let mut v = n as f64; + let mut i = 0; + while v >= 1024.0 && i < 4 { + v /= 1024.0; + i += 1; + } + if i == 0 { format!("{n} B") } else { format!("{v:.1} {}", U[i]) } +} + +#[cfg(test)] +mod tests { + #[test] + fn helpers() { + assert_eq!(super::group(1234567), "1,234,567"); + } +} From c36f4633a12f1ce94dbaf9aaa67277c6ec6b2dfb Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:38:07 +0100 Subject: [PATCH 17/45] rust: wire VNC client into the session manager (password asked only when required, SSH tunnel) --- rust/core/src/sessions.rs | 63 +++++++++++++++++++++++++++++---------- 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/rust/core/src/sessions.rs b/rust/core/src/sessions.rs index 6264555..79eb11b 100644 --- a/rust/core/src/sessions.rs +++ b/rust/core/src/sessions.rs @@ -61,7 +61,7 @@ enum Client { Local(Arc), Github(Arc, Arc), Irc(Arc, Option>), - Vnc(Arc, Option>), + Vnc(Arc, Option>), } impl Client { @@ -636,7 +636,7 @@ impl Manager { let password = self.secret_fn(&p.id, "vnc-password", vc.secret_mode, prompt); let cb = self.terminal_callbacks(id, epoch); let (me, sid) = (self.clone(), id.to_string()); - let c = vnc_stub::connect_profile(&host, port, &vc, p.connect_timeout, password, tunnel.clone(), cb, Arc::new(move || me.emit(Event::VncFrame { session: sid.clone() }))); + let c = connect_vnc_client(&host, port, &vc, p.connect_timeout, password, tunnel.clone(), cb, Arc::new(move || me.emit(Event::VncFrame { session: sid.clone() }))); match c { Ok(c) => { self.emit(Event::VncReady { session: id.into() }); @@ -1048,7 +1048,7 @@ impl Manager { } } - pub fn vnc(&self, id: &str) -> Result> { + pub fn vnc(&self, id: &str) -> Result> { match self.client(id)? { Client::Vnc(c, _) => Ok(c), _ => err(format!("sessions: session {id:?} is not a VNC connection")), @@ -1127,18 +1127,51 @@ impl Manager { } } -// ponytail: placeholder until the VNC client lands; replaced by clients::vnc. -mod vnc_stub { - use crate::clients::Callbacks; - use crate::clients::ssh::{SecretFn, SshClient}; - use std::sync::Arc; - pub struct VncClient; - impl VncClient { - pub fn close(&self) {} - } - #[allow(clippy::too_many_arguments)] - pub fn connect_profile(_: &str, _: u16, _: &crate::models::VncConfig, _: i64, _: SecretFn, _: Option>, _: Callbacks, _: Arc) -> crate::Result> { - Err("VNC is not available yet".into()) +/// Builds and connects a VNC client from a profile's settings, optionally through an SSH tunnel. +#[allow(clippy::too_many_arguments)] +fn connect_vnc_client( + host: &str, + port: u16, + vc: &VncConfig, + timeout: i64, + password: SecretFn, + tunnel: Option>, + cb: Callbacks, + on_frame: Arc, +) -> Result> { + let connector: Option = tunnel.map(|t| { + let host = host.to_string(); + Arc::new(move || { + let s = t.open_tunnel(&host, port)?; + s.set_read_timeout(Some(Duration::from_millis(50)))?; + Ok(Box::new(s) as Box) + }) as irc::Connector + }); + let mut cfg = clients::vnc::Config { + host: host.into(), + port, + password: None, + shared: vc.shared_session, + view_only: vc.view_only, + quality: vc.quality.clamp(0, 9) as u8, + connector, + connect_timeout: Duration::from_secs(timeout.max(1) as u64), + on_frame, + on_error: cb.on_error, + on_close: cb.on_close, + ..Default::default() + }; + let c = clients::vnc::Client::new(cfg.clone()); + match c.connect() { + Ok(()) => Ok(c), + // Ask for the password only when the server wants one. + Err(e) if e.to_string().to_lowercase().contains("password") => { + cfg.password = Some(password()?); + let c = clients::vnc::Client::new(cfg); + c.connect()?; + Ok(c) + } + Err(e) => Err(e), } } From 43acc57c5188357b6c7a2531866e34db100f3441 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:48:33 +0100 Subject: [PATCH 18/45] rust: Nexus Terminal app shell (title bar, sidebar, tabs, split panes, terminal view, palette, prompts, dialogs, toasts, status bar) --- rust/Cargo.lock | 2 + rust/app/Cargo.toml | 2 + rust/app/assets/icon/icon-256.png | Bin 0 -> 22157 bytes rust/app/assets/icon/icon-512.png | Bin 0 -> 54200 bytes rust/app/src/app.rs | 819 ++++++++++++++++++++ rust/app/src/chrome.rs | 963 ++++++++++++++++++++++++ rust/app/src/forwards.rs | 17 + rust/app/src/github_view.rs | 25 + rust/app/src/irc_view.rs | 23 + rust/app/src/main.rs | 98 ++- rust/app/src/profile_editor.rs | 31 + rust/app/src/quick_connect.rs | 26 + rust/app/src/raw_view.rs | 22 + rust/app/src/recordings.rs | 26 + rust/app/src/settings.rs | 26 + rust/app/src/sftp.rs | 41 + rust/app/src/sidebar.rs | 305 ++++++++ rust/app/src/snippets.rs | 26 + rust/app/src/splits.rs | 319 ++++++++ rust/app/src/ssh_keys.rs | 26 + rust/app/src/state.rs | 216 ++++++ rust/app/src/tabs.rs | 448 +++++++++++ rust/app/src/terminal.rs | 14 +- rust/app/src/transfer.rs | 16 + rust/app/src/triggers.rs | 25 + rust/app/src/views.rs | 55 ++ rust/app/src/vnc_view.rs | 22 + rust/app/src/widgets.rs | 1 - rust/packaging/macos/NexusTerminal.icns | Bin 0 -> 314885 bytes rust/packaging/nexus.svg | 137 ++++ rust/packaging/windows/nexus.ico | Bin 0 -> 372526 bytes 31 files changed, 3723 insertions(+), 8 deletions(-) create mode 100644 rust/app/assets/icon/icon-256.png create mode 100644 rust/app/assets/icon/icon-512.png create mode 100644 rust/app/src/app.rs create mode 100644 rust/app/src/chrome.rs create mode 100644 rust/app/src/forwards.rs create mode 100644 rust/app/src/github_view.rs create mode 100644 rust/app/src/irc_view.rs create mode 100644 rust/app/src/profile_editor.rs create mode 100644 rust/app/src/quick_connect.rs create mode 100644 rust/app/src/raw_view.rs create mode 100644 rust/app/src/recordings.rs create mode 100644 rust/app/src/settings.rs create mode 100644 rust/app/src/sftp.rs create mode 100644 rust/app/src/sidebar.rs create mode 100644 rust/app/src/snippets.rs create mode 100644 rust/app/src/splits.rs create mode 100644 rust/app/src/ssh_keys.rs create mode 100644 rust/app/src/state.rs create mode 100644 rust/app/src/tabs.rs create mode 100644 rust/app/src/transfer.rs create mode 100644 rust/app/src/triggers.rs create mode 100644 rust/app/src/views.rs create mode 100644 rust/app/src/vnc_view.rs create mode 100644 rust/packaging/macos/NexusTerminal.icns create mode 100644 rust/packaging/nexus.svg create mode 100644 rust/packaging/windows/nexus.ico diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d51ab0d..985c147 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2799,6 +2799,8 @@ name = "nexus" version = "0.1.0" dependencies = [ "alacritty_terminal", + "arboard", + "chrono", "eframe", "egui", "egui-phosphor", diff --git a/rust/app/Cargo.toml b/rust/app/Cargo.toml index fb60451..e1fe582 100644 --- a/rust/app/Cargo.toml +++ b/rust/app/Cargo.toml @@ -19,6 +19,8 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" regex = "1" alacritty_terminal = "0.26.0" +chrono = "0.4.45" +arboard = "3.6.1" [build-dependencies] winresource = "0.1" diff --git a/rust/app/assets/icon/icon-256.png b/rust/app/assets/icon/icon-256.png new file mode 100644 index 0000000000000000000000000000000000000000..f3d119f53dee4ef47198165a7a95f8b827a6d2f0 GIT binary patch literal 22157 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 literal 0 HcmV?d00001 diff --git a/rust/app/assets/icon/icon-512.png b/rust/app/assets/icon/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..829caabfb85585a37d59793762e2b0c15d36731b GIT binary patch literal 54200 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 literal 0 HcmV?d00001 diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs new file mode 100644 index 0000000..02ba590 --- /dev/null +++ b/rust/app/src/app.rs @@ -0,0 +1,819 @@ +//! The main window: title bar, connection sidebar, tab strip and workspace (terminals, split +//! panes, protocol viewers), right dock (SFTP or the profile editor), status bar, command +//! palette, prompts and toasts. Session events from nexus-core are drained here every frame. + +use crate::state::{Pane, Request, SettingsTab, Shared, Tab, ToastKind}; +use crate::terminal::{CursorStyle, MenuAction, TermView}; +use crate::theme::{self, ThemeKind, Tokens}; +use crate::views::View; +use crate::{chrome, forwards, profile_editor, quick_connect, recordings, settings, sftp, sidebar, snippets, ssh_keys, tabs, transfer, triggers}; +use egui::{Key, KeyboardShortcut, Modifiers, Ui}; +use nexus_core::models::{Profile, Protocol, SessionInfo, SessionState}; +use nexus_core::service::Service; +use nexus_core::sessions::Event; +use std::collections::VecDeque; +use std::path::PathBuf; +use std::sync::{Arc, Mutex, mpsc}; + +pub struct Options { + pub screenshot: Option, + pub theme: Option, +} + +/// Errors from worker threads, drained into toasts each frame. +static POSTED: Mutex> = Mutex::new(Vec::new()); + +pub fn post_error(e: String) { + POSTED.lock().unwrap().push(e); +} + +/// A credential or host-key question from a connecting session. +pub enum Prompt { + Secret { session: String, title: String, message: String, value: String, reply: mpsc::Sender }, + HostKey { host: String, key_type: String, fingerprint: String, reply: mpsc::Sender }, +} + +/// Modal dialogs owned by the main window. +pub enum Dialog { + /// A profile already has a live session. + Reuse { profile: String, existing: String }, + /// Unencrypted VNC without an SSH tunnel. + DirectVnc { profile: String }, + ConfirmClose { session: String }, + ConfirmDisconnectMany { sessions: Vec }, + Rename { session: String, name: String }, + Broadcast { picks: Vec<(String, String, bool)> }, + /// Guided help for an auth or host-key failure. + Guided { session: String, title: String, body: String, known_hosts: bool }, + Delete { profiles: Vec }, + Input { title: String, label: String, value: String, then: InputThen }, +} + +pub enum InputThen { + Tags { profiles: Vec, replace: bool }, + MoveGroup { profiles: Vec }, +} + +pub struct App { + pub s: Shared, + rx: mpsc::Receiver, + pub theme: ThemeKind, + pub editor: profile_editor::ProfileEditor, + pub quick: quick_connect::QuickConnect, + pub settings: settings::Settings, + pub keys: ssh_keys::SshKeys, + pub transfer: transfer::Transfer, + pub sftp: sftp::Sftp, + pub forwards: forwards::Forwards, + pub snippets: snippets::Snippets, + pub triggers: triggers::Triggers, + pub recordings: recordings::Recordings, + pub sidebar: sidebar::Sidebar, + pub palette: Option, + pub prompts: VecDeque, + pub dialog: Option, + pub tabs_ui: tabs::TabsUi, + pub latency: Option<(String, Result)>, + pub update: Option, + screenshot: Option<(PathBuf, u32)>, + shot_settle: u32, +} + +const fn sc(m: Modifiers, k: Key) -> KeyboardShortcut { + KeyboardShortcut::new(m, k) +} +const CMD: Modifiers = Modifiers::COMMAND; +const CMD_SHIFT: Modifiers = Modifiers::COMMAND.plus(Modifiers::SHIFT); + +impl App { + pub fn new(cc: &eframe::CreationContext, svc: Arc, opts: Options) -> App { + theme::install_fonts(&cc.egui_ctx); + let (tx, rx) = mpsc::channel(); + let ctx = cc.egui_ctx.clone(); + let tx = Mutex::new(tx); + svc.sessions.set_sink(Arc::new(move |e| { + let _ = tx.lock().unwrap().send(e); + ctx.request_repaint(); + })); + let s = Shared::new(svc.clone(), cc.egui_ctx.clone()); + let theme = opts.theme.as_deref().and_then(ThemeKind::from_id).unwrap_or_else(|| theme_for(&s.prefs.theme)); + theme::apply(&cc.egui_ctx, theme); + let mut app = App { + s, + rx, + theme, + editor: Default::default(), + quick: Default::default(), + settings: Default::default(), + keys: Default::default(), + transfer: Default::default(), + sftp: Default::default(), + forwards: Default::default(), + snippets: Default::default(), + triggers: Default::default(), + recordings: Default::default(), + sidebar: Default::default(), + palette: None, + prompts: VecDeque::new(), + dialog: None, + tabs_ui: Default::default(), + latency: None, + update: None, + screenshot: opts.screenshot.map(|p| (p, 0)), + shot_settle: 0, + }; + if let Some(from) = &svc.config.migrated_from { + app.s.success(format!("Imported your Alchementary Link data from {}", from.display())); + } + // Background update check, once per launch. + if app.screenshot.is_none() { + let svc2 = svc.clone(); + let ctx = cc.egui_ctx.clone(); + std::thread::spawn(move || { + if let Ok(info) = svc2.check_for_updates() { + ctx.data_mut(|d| d.insert_temp(egui::Id::new("nexus-update"), Arc::new(info))); + ctx.request_repaint(); + } + }); + } + app + } + + // --- sessions ------------------------------------------------------------------------- + + fn view_for(&self, info: &SessionInfo, profile: Option<&Profile>) -> View { + let prefs = &self.s.prefs; + match (info.protocol, profile) { + (Protocol::Raw, Some(p)) => View::Raw(Box::new(crate::raw_view::RawView::new(&info.id, p))), + (Protocol::Irc, Some(p)) => View::Irc(Box::new(crate::irc_view::IrcView::new(&info.id, p))), + (Protocol::Github, Some(p)) => View::Github(Box::new(crate::github_view::GithubView::new(&info.id, &p.display_name))), + (Protocol::Vnc, Some(p)) => View::Vnc(Box::new(crate::vnc_view::VncView::new(&info.id, p))), + (_, p) => { + let (scroll, size, cursor) = match p { + Some(p) => (p.terminal_scrollback, p.terminal_font_size, p.terminal_cursor_style.clone()), + None => (prefs.default_scrollback, prefs.default_font_size, prefs.default_cursor_style.clone()), + }; + View::Terminal(Box::new(TermView::new(scroll.max(0) as usize, size as f32, CursorStyle::from_id(&cursor)))) + } + } + } + + /// Registers a just-opened session as a pane (and a tab unless it's a split child). + pub fn add_session(&mut self, id: &str, tab: bool) { + let Some(info) = self.s.svc.sessions.info(id) else { return }; + let profile = self.s.svc.sessions.profile(id); + let view = self.view_for(&info, profile.as_ref()); + self.s.panes.insert(id.into(), Pane { info, view, recording: false, local_exited: false, connected_since: None, highlight_until: 0.0 }); + if tab { + self.s.tabs.push(Tab { id: id.into(), activity: false, split: None }); + self.s.active = Some(id.into()); + self.s.focused = Some(id.into()); + } + } + + /// Connect a saved profile, asking first if it already has a live session. + pub fn connect_profile(&mut self, id: &str, skip_checks: bool) { + let Some(p) = self.s.profile(id).cloned() else { + self.s.error("Profile not found"); + return; + }; + if !skip_checks { + let live = self.s.panes.values().find(|pn| { + pn.info.profile_id == id && matches!(self.s.session_state(&pn.info.id), Some(SessionState::Connected | SessionState::Connecting | SessionState::Reconnecting)) + }); + if let Some(pn) = live { + self.dialog = Some(Dialog::Reuse { profile: id.into(), existing: pn.info.id.clone() }); + return; + } + if p.protocol == Protocol::Vnc && !p.vnc.as_ref().is_some_and(|v| v.ssh_tunnel) { + self.dialog = Some(Dialog::DirectVnc { profile: id.into() }); + return; + } + } + match self.s.svc.connect_profile(id) { + Ok(sid) => { + self.add_session(&sid, true); + self.s.reload_profiles(); + self.s.info(format!("Connecting to {}…", if p.display_name.is_empty() { &p.host } else { &p.display_name })); + } + Err(e) => self.s.error(format!("Connection failed: {e}")), + } + } + + pub fn connect_local(&mut self) { + let sid = self.s.svc.connect_local(); + self.add_session(&sid, true); + self.s.info("Opening local terminal…"); + } + + /// Closes a session's UI and backend. Tabs close their split children too. + pub fn close_session(&mut self, id: &str) { + let children: Vec = self.s.tab(id).and_then(|t| t.split.as_ref()).map(|sp| sp.sessions()).unwrap_or_default(); + for c in children.iter().filter(|c| *c != id) { + self.close_one(c); + } + if let Some(pos) = self.s.tabs.iter().position(|t| t.id == id) { + self.s.tabs.remove(pos); + if self.s.active.as_deref() == Some(id) { + // The nearest neighbour becomes active. + let next = self.s.tabs.get(pos).or_else(|| self.s.tabs.get(pos.wrapping_sub(1))).map(|t| t.id.clone()); + self.s.active = next.clone(); + self.s.focused = next; + } + } else { + crate::splits::remove_session(&mut self.s, id); + } + self.close_one(id); + } + + fn close_one(&mut self, id: &str) { + self.sftp.close(id); + self.triggers.cleanup(id); + self.s.panes.remove(id); + if self.s.focused.as_deref() == Some(id) { + self.s.focused = self.s.active.clone(); + } + let svc = self.s.svc.clone(); + let id = id.to_string(); + std::thread::spawn(move || { + let _ = svc.sessions.disconnect(&id); + }); + } + + /// Close with the "confirm on close" preference (not for GitHub tabs). + pub fn request_close(&mut self, id: &str) { + let proto = self.s.pane(id).map(|p| p.info.protocol); + let live = matches!(self.s.session_state(id), Some(SessionState::Connected)); + if self.s.prefs.confirm_on_close && live && proto != Some(Protocol::Github) { + self.dialog = Some(Dialog::ConfirmClose { session: id.into() }); + } else { + self.close_session(id); + } + } + + pub fn reconnect(&mut self, id: &str) { + let old = self.s.pane(id).map(|p| p.info.tab_name.clone()); + match self.s.svc.reconnect(id) { + Ok(new) => { + // Keep the tab in place; swap in the new session id. + let info = self.s.svc.sessions.info(&new); + if let (Some(mut pane), Some(info)) = (self.s.panes.remove(id), info) { + pane.info = info; + pane.local_exited = false; + if let View::Terminal(t) = &mut pane.view { + t.feed(b"\r\n\x1b[2m--- reconnecting ---\x1b[0m\r\n"); + } + self.s.panes.insert(new.clone(), pane); + } + for t in &mut self.s.tabs { + if t.id == id { + t.id = new.clone(); + } + if let Some(sp) = &mut t.split { + sp.rename(id, &new); + } + } + if self.s.active.as_deref() == Some(id) { + self.s.active = Some(new.clone()); + } + if self.s.focused.as_deref() == Some(id) { + self.s.focused = Some(new.clone()); + } + self.s.info(format!("Reconnecting {}…", old.unwrap_or_default())); + } + Err(e) => self.s.error(format!("Reconnect failed: {e}")), + } + } + + /// Sends user input to a session and, in broadcast mode, to the other targets. + pub fn send_input(&mut self, from: &str, data: &[u8]) { + if data.is_empty() { + return; + } + let svc = self.s.svc.clone(); + let _ = svc.sessions.write(from, data); + if self.s.broadcast { + for id in self.s.all_sessions() { + if id == from || !self.s.broadcast_targets.as_ref().is_none_or(|t| t.contains(&id)) { + continue; + } + if self.s.session_state(&id) == Some(SessionState::Connected) && self.s.pane(&id).is_some_and(|p| matches!(p.view, View::Terminal(_))) { + let _ = svc.sessions.write(&id, data); + } + } + } + } + + // --- events --------------------------------------------------------------------------- + + fn drain_events(&mut self) { + for e in POSTED.lock().unwrap().drain(..) { + self.s.error(e); + } + while let Ok(e) = self.rx.try_recv() { + self.on_event(e); + } + if let Some(info) = self.s.ctx.data_mut(|d| d.remove_temp::>(egui::Id::new("nexus-update"))) { + self.update = Some((*info).clone()); + } + } + + fn mark_activity(&mut self, session: &str) { + let active = self.s.active.clone(); + if let Some(t) = self.s.tabs.iter_mut().find(|t| t.id == session || t.split.as_ref().is_some_and(|sp| sp.sessions().iter().any(|x| x == session))) { + if active.as_deref() != Some(&t.id) { + t.activity = true; + } + } + } + + fn on_event(&mut self, e: Event) { + match e { + Event::Data { session, data } => { + if let Some(View::Terminal(t)) = self.s.panes.get_mut(&session).map(|p| &mut p.view) { + t.feed(&data); + } + self.mark_activity(&session); + let now = self.s.now; + if self.triggers.check(&mut self.s, &session, &data) + && let Some(p) = self.s.pane_mut(&session) + { + p.highlight_until = now + 1.5; + } + } + Event::State { session, state, error, retry } => self.on_state(&session, state, &error, retry), + Event::Closed { session } => { + self.sftp.close(&session); + self.triggers.cleanup(&session); + } + Event::RawData { session, data, direction, timestamp } => { + if let Some(View::Raw(v)) = self.s.panes.get_mut(&session).map(|p| &mut p.view) { + v.on_data(&data, &direction, timestamp); + } + self.mark_activity(&session); + } + Event::Debug { .. } => {} + Event::GithubStatus { session, status } => { + if let Some(View::Github(v)) = self.s.panes.get_mut(&session).map(|p| &mut p.view) { + v.on_status(status); + } + } + Event::GithubRepos { session, login, repos } => { + if let Some(View::Github(v)) = self.s.panes.get_mut(&session).map(|p| &mut p.view) { + v.on_repos(login, repos); + } + } + Event::GithubError { session, error } => { + if let Some(View::Github(v)) = self.s.panes.get_mut(&session).map(|p| &mut p.view) { + v.on_error(error); + } + } + Event::Irc { session, event } => { + if let Some(View::Irc(v)) = self.s.panes.get_mut(&session).map(|p| &mut p.view) { + v.on_event(event); + } + self.mark_activity(&session); + } + Event::ForwardStarted { forward, .. } => { + let label = if forward.label.is_empty() { + format!("{}:{} → {}:{}", forward.bind_host, forward.bind_port, forward.dest_host, forward.dest_port) + } else { + forward.label.clone() + }; + self.s.success(format!("Forward started: {label}")); + } + Event::ForwardStopped { forward_id, .. } => self.s.info(format!("Forward stopped: {forward_id}")), + Event::ForwardError { error, .. } => self.s.error(format!("Forward error: {error}")), + Event::RecordingStarted { session } => { + if let Some(p) = self.s.pane_mut(&session) { + p.recording = true; + } + } + Event::RecordingStopped { session, duration, events } => { + if let Some(p) = self.s.pane_mut(&session) { + p.recording = false; + } + self.s.success(format!("Recording saved ({:.0}s, {events} events)", duration)); + } + Event::SftpProgress { session, progress } => self.sftp.on_progress(&session, progress), + Event::SecretPrompt { session, title, message, reply } => { + self.prompts.push_back(Prompt::Secret { session, title, message, value: String::new(), reply }); + } + Event::HostKeyVerify { host, key_type, fingerprint, reply, .. } => { + self.prompts.push_back(Prompt::HostKey { host, key_type, fingerprint, reply }); + } + Event::VncReady { session } => { + if let Some(View::Vnc(v)) = self.s.panes.get_mut(&session).map(|p| &mut p.view) { + v.on_ready(); + } + } + Event::VncFrame { .. } => {} + } + } + + fn on_state(&mut self, session: &str, state: SessionState, error: &str, retry: Option) { + // A reconnect replaces the info wholesale. + if let Some(info) = self.s.svc.sessions.info(session) + && let Some(p) = self.s.pane_mut(session) + { + p.info = info; + p.info.state = state; + if !error.is_empty() { + p.info.error = error.into(); + } + } + let Some(p) = self.s.pane_mut(session) else { return }; + let local = p.info.protocol == Protocol::Local; + match state { + SessionState::Connected => { + p.local_exited = false; + p.connected_since = Some(chrono::Local::now()); + // The PTY was created before the view knew its size: send it now. + if let View::Terminal(t) = &mut p.view { + let (c, r) = t.size(); + let _ = self.s.svc.sessions.resize(session, c, r); + } + } + SessionState::Disconnected if local => { + p.local_exited = true; + if let View::Terminal(t) = &mut p.view { + t.feed(b"\r\n\x1b[2m[Exited - press enter to restart]\x1b[0m\r\n"); + } + } + SessionState::Reconnecting => { + if let Some(r) = retry { + self.s.info(format!("Reconnecting (attempt {}/{}) in {}s…", r.attempt, r.max, r.delay_secs)); + } + } + SessionState::Error if !error.is_empty() => { + if let Some((title, body, kh)) = classify_error(error) { + self.dialog = Some(Dialog::Guided { session: session.into(), title: title.into(), body: format!("{body}\n\n{error}"), known_hosts: kh }); + } + } + _ => {} + } + } + + // --- requests, shortcuts, commands ---------------------------------------------------- + + fn handle_requests(&mut self) { + for r in std::mem::take(&mut self.s.requests) { + match r { + Request::NewProfile { protocol, folder } => self.editor.open_new(&self.s, protocol, &folder), + Request::EditProfile(id) => self.editor.open_edit(&self.s, &id), + Request::Connect(id) => self.connect_profile(&id, false), + Request::ConnectQuick(p) => { + let name = if p.display_name.is_empty() { p.host.clone() } else { p.display_name.clone() }; + let sid = self.s.svc.connect_quick(p); + self.add_session(&sid, true); + self.s.info(format!("Connecting to {name}…")); + } + Request::ConnectLocal => self.connect_local(), + Request::ReloadProfiles => self.s.reload_profiles(), + Request::ReloadPreferences => { + self.s.prefs = self.s.svc.preferences(); + self.theme = theme_for(&self.s.prefs.theme); + theme::apply(&self.s.ctx, self.theme); + } + Request::QuickConnect => self.quick.open(), + Request::Settings(t) => self.settings.open(t), + Request::SshKeys => self.keys.open(), + Request::ExportEncrypted(ids) => self.transfer.export_encrypted(ids), + Request::ImportEncrypted => self.transfer.import_encrypted(), + Request::ExportJson => self.transfer.export_json(), + Request::ImportJson => self.transfer.import_json(), + Request::Recordings => self.recordings.open(), + Request::Snippets => self.snippets.open(), + Request::Triggers => self.triggers.open(), + Request::Sftp(id) => self.sftp.toggle(&id), + Request::PortForwards(id) => self.forwards.open(&id), + Request::SendToActive(text) => match self.s.focused_session() { + Some(id) if self.s.session_state(&id) == Some(SessionState::Connected) => self.send_input(&id, text.as_bytes()), + _ => self.s.error("No active connected session"), + }, + Request::Write(id, data) => self.send_input(&id, &data), + Request::Disconnect(id) => self.close_session(&id), + Request::CommandPalette => self.palette = Some(chrome::Palette::default()), + Request::About => self.settings.open(SettingsTab::About), + } + } + } + + fn shortcuts(&mut self, ctx: &egui::Context) { + let pressed = |s: KeyboardShortcut| ctx.input_mut(|i| i.consume_shortcut(&s)); + if pressed(sc(CMD, Key::K)) { + self.palette = if self.palette.is_some() { None } else { Some(chrome::Palette::default()) }; + } + if pressed(sc(CMD, Key::N)) { + self.s.request(Request::NewProfile { protocol: None, folder: String::new() }); + } + if pressed(sc(CMD, Key::W)) + && let Some(id) = self.s.focused_session() + { + self.request_close(&id); + } + if pressed(sc(CMD_SHIFT, Key::Tab)) { + self.cycle_tab(-1); + } + if pressed(sc(CMD, Key::Tab)) { + self.cycle_tab(1); + } + for (i, k) in [Key::Num1, Key::Num2, Key::Num3, Key::Num4, Key::Num5, Key::Num6, Key::Num7, Key::Num8, Key::Num9].into_iter().enumerate() { + if pressed(sc(CMD, k)) + && let Some(t) = tabs::ordered(&self.s).get(i) + { + let id = t.clone(); + self.activate(&id); + } + } + if pressed(sc(CMD, Key::Comma)) { + self.settings.open(SettingsTab::General); + } + if pressed(sc(CMD, Key::F)) { + self.open_find(); + } + if pressed(sc(CMD_SHIFT, Key::D)) { + crate::splits::split_focused(self, crate::splits::Dir::Right); + } + if pressed(sc(CMD_SHIFT, Key::E)) { + crate::splits::split_focused(self, crate::splits::Dir::Down); + } + if pressed(sc(CMD_SHIFT, Key::R)) + && let Some(id) = self.s.focused_session() + { + self.toggle_recording(&id); + } + if pressed(sc(CMD_SHIFT, Key::B)) { + self.toggle_broadcast(); + } + if pressed(sc(CMD_SHIFT, Key::S)) { + self.snippets.open(); + } + if pressed(sc(CMD_SHIFT, Key::Q)) { + self.quick.open(); + } + if pressed(sc(Modifiers::NONE, Key::F11)) { + let full = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false)); + ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!full)); + } + } + + pub fn activate(&mut self, id: &str) { + self.s.active = Some(id.into()); + self.s.focused = Some(id.into()); + if let Some(t) = self.s.tabs.iter_mut().find(|t| t.id == id) { + t.activity = false; + } + if let Some(View::Terminal(t)) = self.s.panes.get_mut(id).map(|p| &mut p.view) { + t.request_focus(); + } + } + + fn cycle_tab(&mut self, dir: i32) { + let order = tabs::ordered(&self.s); + if order.is_empty() { + return; + } + let cur = self.s.active.as_ref().and_then(|a| order.iter().position(|x| x == a)).unwrap_or(0) as i32; + let next = (cur + dir).rem_euclid(order.len() as i32) as usize; + let id = order[next].clone(); + self.activate(&id); + } + + pub fn open_find(&mut self) { + if let Some(id) = self.s.focused_session() + && let Some(View::Terminal(t)) = self.s.panes.get_mut(&id).map(|p| &mut p.view) + { + if t.find.is_none() { + t.find = Some(Default::default()); + } + self.tabs_ui.find_focus = true; + } + } + + pub fn toggle_recording(&mut self, id: &str) { + let svc = self.s.svc.clone(); + if svc.sessions.is_recording(id) { + if let Err(e) = svc.sessions.stop_recording(id) { + self.s.error(e.to_string()); + } + } else { + let (c, r) = match self.s.pane(id).map(|p| &p.view) { + Some(View::Terminal(t)) => t.size(), + _ => (80, 24), + }; + match svc.sessions.start_recording(id, c, r) { + Ok(()) => self.s.info("Recording started"), + Err(e) => self.s.error(e.to_string()), + } + } + } + + pub fn toggle_broadcast(&mut self) { + self.s.broadcast = !self.s.broadcast; + if !self.s.broadcast { + self.s.broadcast_targets = None; + } + self.s.info(if self.s.broadcast { "Broadcast mode enabled — input sent to all sessions" } else { "Broadcast mode disabled" }); + } + + pub fn pick_broadcast_targets(&mut self) { + let focused = self.s.focused_session(); + let picks: Vec<(String, String, bool)> = self + .s + .panes + .values() + .filter(|p| Some(&p.info.id) != focused.as_ref() && self.s.session_state(&p.info.id) == Some(SessionState::Connected)) + .map(|p| (p.info.id.clone(), p.info.tab_name.clone(), self.s.broadcast_targets.as_ref().is_none_or(|t| t.contains(&p.info.id)))) + .collect(); + if picks.is_empty() { + self.s.error("No other connected sessions to broadcast to"); + return; + } + self.dialog = Some(Dialog::Broadcast { picks }); + } + + /// A terminal's context-menu action. + pub fn terminal_menu(&mut self, id: &str, a: MenuAction) { + match a { + MenuAction::Paste => { + if let Ok(text) = arboard::Clipboard::new().and_then(|mut c| c.get_text()) + && let Some(View::Terminal(t)) = self.s.panes.get_mut(id).map(|p| &mut p.view) + { + let mut out = crate::terminal::TermResponse::default(); + t.paste(&text, &mut out); + let input = out.input; + self.send_input(id, &input); + } + } + MenuAction::Find => { + self.s.focused = Some(id.into()); + self.open_find(); + } + MenuAction::SendSnippet => self.snippets.open(), + MenuAction::PortForwards => { + if self.s.pane(id).is_some_and(|p| p.info.protocol == Protocol::Ssh) { + self.forwards.open(id); + } else { + self.s.error("Port forwards are only available on SSH sessions"); + } + } + MenuAction::SplitRight | MenuAction::SplitDown => { + self.s.focused = Some(id.into()); + crate::splits::split_focused(self, if a == MenuAction::SplitRight { crate::splits::Dir::Right } else { crate::splits::Dir::Down }); + } + MenuAction::Export => { + if let Some(View::Terminal(t)) = self.s.panes.get(id).map(|p| &p.view) { + let text = t.buffer_text(); + if let Some(path) = rfd::FileDialog::new().set_file_name("terminal-output.txt").save_file() { + match std::fs::write(&path, text) { + Ok(()) => self.s.success(format!("Saved {}", path.display())), + Err(e) => self.s.error(format!("Export failed: {e}")), + } + } + } + } + MenuAction::Clear => { + if let Some(View::Terminal(t)) = self.s.panes.get_mut(id).map(|p| &mut p.view) { + t.clear(); + } + } + MenuAction::Reset => { + if let Some(View::Terminal(t)) = self.s.panes.get_mut(id).map(|p| &mut p.view) { + t.reset(); + } + } + MenuAction::Copy | MenuAction::SelectAll => {} + } + } + + fn file_drops(&mut self, ctx: &egui::Context) { + let dropped: Vec = ctx.input(|i| i.raw.dropped_files.iter().map(|f| f.path().to_path_buf()).filter(|p| !p.as_os_str().is_empty()).collect()); + if dropped.is_empty() { + return; + } + match self.s.focused_session() { + Some(id) if self.s.pane(&id).is_some_and(|p| matches!(p.view, View::Terminal(_))) => self.sftp.drop_files(&mut self.s, &id, dropped), + _ => self.s.error("Drop files on a terminal session"), + } + } + + // --- screenshot automation --------------------------------------------------------- + + fn screenshot(&mut self, ctx: &egui::Context) { + let Some((path, frames)) = &mut self.screenshot else { return }; + *frames += 1; + if *frames < 40 { + std::thread::sleep(std::time::Duration::from_millis(15)); + ctx.request_repaint(); + return; + } + if self.shot_settle == 0 { + self.shot_settle = 1; + ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot(egui::UserData::default())); + } + let shot = ctx.input(|i| i.events.iter().find_map(|e| if let egui::Event::Screenshot { image, .. } = e { Some(image.clone()) } else { None })); + if let Some(img) = shot { + let [w, h] = img.size; + let raw: Vec = img.pixels.iter().flat_map(|p| p.to_array()).collect(); + if let Err(e) = image::save_buffer(&*path, &raw, w as u32, h as u32, image::ExtendedColorType::Rgba8) { + eprintln!("screenshot: {e}"); + } + ctx.send_viewport_cmd(egui::ViewportCommand::Close); + } + ctx.request_repaint(); + } +} + +/// App chrome theme from the "theme" preference (the web build used dark/light). +pub fn theme_for(pref: &str) -> ThemeKind { + ThemeKind::from_id(pref).unwrap_or(if pref == "light" { ThemeKind::Light } else { ThemeKind::Pro }) +} + +/// Recognises auth and host-key failures for the guided error dialog. +pub fn classify_error(msg: &str) -> Option<(&'static str, &'static str, bool)> { + let m = msg.to_lowercase(); + if ["unable to authenticate", "authentication failed", "permission denied", "no supported methods"].iter().any(|p| m.contains(p)) { + return Some(("Authentication Failed", "The server rejected the provided credentials. Check username, password, keys, and auth methods.", false)); + } + if ["host key", "fingerprint"].iter().any(|p| m.contains(p)) { + return Some(("Host Key Verification Failed", "The server's host key could not be verified. Manage known hosts or update the profile's policy.", true)); + } + None +} + +impl eframe::App for App { + fn persist_egui_memory(&self) -> bool { + self.screenshot.is_none() + } + + fn ui(&mut self, ui: &mut Ui, _frame: &mut eframe::Frame) { + let ctx = ui.ctx().clone(); + let t = Tokens::get(&ctx); + self.s.now = ctx.input(|i| i.time); + self.drain_events(); + self.handle_requests(); + // Dialogs and the palette take the keyboard while open. + if self.palette.is_none() && self.dialog.is_none() && self.prompts.is_empty() { + self.shortcuts(&ctx); + } + if ctx.input(|i| i.key_pressed(Key::Escape)) && ctx.input(|i| i.viewport().fullscreen.unwrap_or(false)) && self.palette.is_none() { + ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false)); + } + self.file_drops(&ctx); + + egui::Panel::top("titlebar").exact_size(38.0).frame(egui::Frame::NONE.fill(t.chrome)).show(ui, |ui| chrome::title_bar(self, ui)); + if self.s.prefs.show_status_bar { + egui::Panel::bottom("status").exact_size(26.0).frame(egui::Frame::NONE.fill(t.chrome)).show(ui, |ui| chrome::status_bar(self, ui)); + } + let width = self.s.prefs.sidebar_width.clamp(180, 500) as f32; + let side = egui::Panel::left("sidebar") + .default_size(width) + .min_size(180.0) + .max_size(500.0) + .resizable(true) + .frame(egui::Frame::NONE.fill(t.dock)) + .show(ui, |ui| sidebar::ui(self, ui)); + let w = side.response.rect.width().round() as i64; + if (w - self.s.prefs.sidebar_width).abs() > 2 && !ctx.input(|i| i.pointer.any_down()) { + self.s.prefs.sidebar_width = w; + let _ = self.s.svc.save_preferences(&self.s.prefs); + } + if self.editor.is_open() { + egui::Panel::right("editor").default_size(460.0).min_size(380.0).max_size(800.0).resizable(true).frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)).show(ui, |ui| { + self.editor.ui(ui, &mut self.s); + }); + } else if let Some(id) = self.sftp.open_for().map(String::from) { + if self.s.pane(&id).is_some() { + egui::Panel::right("sftp").default_size(380.0).min_size(280.0).max_size(800.0).resizable(true).frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)).show(ui, |ui| { + self.sftp.ui(ui, &mut self.s); + }); + } else { + self.sftp.close(&id); + } + } + egui::CentralPanel::default().frame(egui::Frame::NONE.fill(t.dock)).show(ui, |ui| tabs::workspace(self, ui)); + + // Floating windows and modals. + self.quick.ui(&ctx, &mut self.s); + self.settings.ui(&ctx, &mut self.s); + self.keys.ui(&ctx, &mut self.s); + self.transfer.ui(&ctx, &mut self.s); + self.sftp.dialogs(&ctx, &mut self.s); + self.forwards.ui(&ctx, &mut self.s); + self.snippets.ui(&ctx, &mut self.s); + self.triggers.ui(&ctx, &mut self.s); + self.recordings.ui(&ctx, &mut self.s); + chrome::dialogs(self, &ctx); + chrome::prompts(self, &ctx); + chrome::palette(self, &ctx); + chrome::toasts(self, &ctx); + self.screenshot(&ctx); + chrome::resize_edges(&ctx); + // Requests raised while drawing are handled on the next frame. + if !self.s.requests.is_empty() { + ctx.request_repaint(); + } + let _ = ToastKind::Info; + } +} diff --git a/rust/app/src/chrome.rs b/rust/app/src/chrome.rs new file mode 100644 index 0000000..22d8e2e --- /dev/null +++ b/rust/app/src/chrome.rs @@ -0,0 +1,963 @@ +//! Window chrome: custom title bar, status bar, command palette, credential and host-key +//! prompts, the main window's modal dialogs, toasts and resize edges. + +use crate::app::{App, Dialog, InputThen, Prompt}; +use crate::state::{Request, SettingsTab, ToastKind, keys}; +use crate::theme::{self, ThemeKind, Tokens}; +use crate::views::View; +use crate::widgets; +use egui::{Align2, Color32, Key, Rect, RichText, Sense, Stroke, Ui, ViewportCommand, pos2, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::models::{Protocol, SessionState}; + +pub const LOGO_PNG: &[u8] = include_bytes!("../assets/icon/icon-256.png"); + +/// The Nexus mark, uploaded once. +pub fn logo(ctx: &egui::Context) -> egui::TextureHandle { + let id = egui::Id::new("nexus-logo"); + if let Some(t) = ctx.data(|d| d.get_temp::(id)) { + return t; + } + let img = image::load_from_memory(LOGO_PNG).map(|i| i.to_rgba8()).unwrap_or_default(); + let color = egui::ColorImage::from_rgba_unmultiplied([img.width() as usize, img.height() as usize], img.as_raw()); + let opts = egui::TextureOptions { mipmap_mode: Some(egui::TextureFilter::Linear), ..egui::TextureOptions::LINEAR }; + let tex = ctx.load_texture("nexus-logo", color, opts); + ctx.data_mut(|d| d.insert_temp(id, tex.clone())); + tex +} + +fn brand(ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + ui.add(egui::Image::new(&logo(ui.ctx())).fit_to_exact_size(vec2(22.0, 22.0))); + ui.label(RichText::new("Nexus").font(theme::semibold(13.5)).color(t.accent)); + ui.add_space(-6.0); + ui.label(RichText::new("Terminal").font(theme::semibold(13.5))); +} + +fn item(ui: &mut Ui, glyph: &str, label: &str, shortcut: &str) -> bool { + let clicked = ui.add(egui::Button::new(format!("{glyph} {label}")).shortcut_text(keys(shortcut)).min_size(vec2(230.0, 0.0))).clicked(); + if clicked { + ui.close(); + } + clicked +} + +pub fn title_bar(app: &mut App, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + let bar = ui.max_rect(); + let drag = ui.interact(bar, ui.id().with("titlebar"), Sense::click_and_drag()); + if drag.drag_started() { + ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag); + } + if drag.double_clicked() { + let max = ui.ctx().input(|i| i.viewport().maximized.unwrap_or(false)); + ui.ctx().send_viewport_cmd(ViewportCommand::Maximized(!max)); + } + let mut menus_right = bar.left(); + ui.horizontal_centered(|ui| { + ui.add_space(10.0); + brand(ui); + ui.add_space(8.0); + egui::MenuBar::new().ui(ui, |ui| { + ui.menu_button("File", |ui| { + if item(ui, icon::PLUS_CIRCLE, "New connection…", "Mod+N") { + app.s.request(Request::NewProfile { protocol: None, folder: String::new() }); + } + if item(ui, icon::LIGHTNING, "Quick connect…", "Mod+Shift+Q") { + app.quick.open(); + } + if item(ui, icon::TERMINAL_WINDOW, "Local terminal", "") { + app.connect_local(); + } + ui.separator(); + if item(ui, icon::DOWNLOAD_SIMPLE, "Import profiles…", "") { + app.s.request(Request::ImportEncrypted); + } + if item(ui, icon::UPLOAD_SIMPLE, "Export profiles…", "") { + app.s.request(Request::ExportEncrypted(vec![])); + } + if item(ui, icon::FILE_ARROW_DOWN, "Import ~/.ssh/config", "") { + match app.s.svc.import_ssh_config(None, "Imported") { + Ok(n) => { + app.s.success(format!("Imported {n} host(s) from ~/.ssh/config")); + app.s.reload_profiles(); + } + Err(e) => app.s.error(format!("Import failed: {e}")), + } + } + ui.separator(); + if item(ui, icon::GEAR, "Settings", "Mod+,") { + app.settings.open(SettingsTab::General); + } + if item(ui, icon::SIGN_OUT, "Quit", "") { + ui.ctx().send_viewport_cmd(ViewportCommand::Close); + } + }); + ui.menu_button("Session", |ui| { + let focused = app.s.focused_session(); + let ssh = focused.as_deref().and_then(|f| app.s.pane(f)).is_some_and(|p| p.info.protocol == Protocol::Ssh); + if item(ui, icon::MAGNIFYING_GLASS, "Find in terminal", "Mod+F") { + app.open_find(); + } + if item(ui, icon::SQUARE_SPLIT_HORIZONTAL, "Split right", "Mod+Shift+D") { + crate::splits::split_focused(app, crate::splits::Dir::Right); + } + if item(ui, icon::SQUARE_SPLIT_VERTICAL, "Split down", "Mod+Shift+E") { + crate::splits::split_focused(app, crate::splits::Dir::Down); + } + ui.separator(); + if item(ui, icon::BROADCAST, if app.s.broadcast { "Stop broadcast" } else { "Broadcast input" }, "Mod+Shift+B") { + app.toggle_broadcast(); + } + if item(ui, icon::USERS_THREE, "Broadcast targets…", "") { + app.pick_broadcast_targets(); + } + if item(ui, icon::RECORD, "Toggle recording", "Mod+Shift+R") + && let Some(f) = focused.clone() + { + app.toggle_recording(&f); + } + if item(ui, icon::CODE, "Snippets", "Mod+Shift+S") { + app.snippets.open(); + } + if item(ui, icon::LIGHTNING_A, "Trigger rules", "") { + app.triggers.open(); + } + if item(ui, icon::FILM_STRIP, "Recordings", "") { + app.recordings.open(); + } + ui.separator(); + ui.add_enabled_ui(ssh, |ui| { + if item(ui, icon::FOLDER_OPEN, "SFTP browser", "") + && let Some(f) = focused.clone() + { + app.sftp.toggle(&f); + } + if item(ui, icon::ARROWS_LEFT_RIGHT, "Port forwards", "") + && let Some(f) = focused.clone() + { + app.forwards.open(&f); + } + }); + if item(ui, icon::KEY, "SSH key manager", "") { + app.keys.open(); + } + }); + ui.menu_button("View", |ui| { + if item(ui, icon::COMMAND, "Command palette", "Mod+K") { + app.palette = Some(Palette::default()); + } + if item(ui, icon::CORNERS_OUT, "Full screen", "F11") { + let full = ui.ctx().input(|i| i.viewport().fullscreen.unwrap_or(false)); + ui.ctx().send_viewport_cmd(ViewportCommand::Fullscreen(!full)); + } + ui.separator(); + for k in ThemeKind::ALL { + let mark = if k == app.theme { icon::CHECK } else { " " }; + if item(ui, mark, k.label(), "") { + set_theme(app, k); + } + } + ui.menu_button(format!("{} Terminal colours", icon::PALETTE), |ui| { + for th in crate::terminal_themes::THEMES { + let mark = if th.id == app.s.prefs.terminal_theme { icon::CHECK } else { " " }; + if item(ui, mark, th.label, "") { + app.s.prefs.terminal_theme = th.id.into(); + let _ = app.s.svc.save_preferences(&app.s.prefs); + } + } + }); + }); + ui.menu_button("Help", |ui| { + if item(ui, icon::BOOK_OPEN, "Help", "") { + app.settings.open(SettingsTab::Help); + } + if item(ui, icon::INFO, "About Nexus Terminal", "") { + app.settings.open(SettingsTab::About); + } + }); + }); + menus_right = ui.min_rect().right(); + }); + + let mut x = bar.right() - 3.0 * 44.0; + window_controls(ui, Rect::from_min_max(pos2(x, bar.top()), bar.max)); + x -= 34.0; + let (next, glyph) = if app.theme == ThemeKind::Light { (ThemeKind::Pro, icon::MOON) } else { (ThemeKind::Light, icon::SUN) }; + let tr = Rect::from_center_size(pos2(x + 14.0, bar.center().y), vec2(24.0, 24.0)); + if widgets::icon_button(&mut ui.new_child(egui::UiBuilder::new().max_rect(tr)), glyph, "Toggle light / dark").clicked() { + set_theme(app, next); + } + let w = (bar.width() * 0.22).clamp(140.0, 300.0); + x -= w + 8.0; + let r = Rect::from_min_size(pos2(x, bar.center().y - 12.0), vec2(w, 24.0)); + let resp = ui.interact(r, ui.id().with("palette-launch"), Sense::click()); + ui.painter().rect(r, 6.0, if resp.hovered() { t.hover } else { t.field }, Stroke::new(1.0, t.field_border), egui::StrokeKind::Inside); + ui.painter().text(r.left_center() + vec2(9.0, 0.0), Align2::LEFT_CENTER, icon::MAGNIFYING_GLASS, egui::FontId::proportional(13.0), t.text_faint); + ui.painter().with_clip_rect(r.shrink(2.0)).text(r.left_center() + vec2(28.0, 0.0), Align2::LEFT_CENTER, "Search connections and commands", egui::FontId::proportional(12.0), t.text_faint); + if w > 250.0 { + ui.painter().text(r.right_center() - vec2(8.0, 0.0), Align2::RIGHT_CENTER, keys("Mod+K").replace('+', " "), theme::semibold(10.5), t.text_faint); + } + if resp.clicked() { + app.palette = Some(Palette::default()); + } + if let Some(p) = app.s.active.as_deref().and_then(|a| app.s.pane(a)) { + let title = format!("{} — {}", p.info.tab_name, p.info.host); + let gap = Rect::from_min_max(pos2(menus_right + 16.0, bar.top()), pos2(x - 16.0, bar.bottom())); + if gap.width() > 60.0 { + ui.painter().with_clip_rect(gap).text(gap.center(), Align2::CENTER_CENTER, title, egui::FontId::proportional(12.0), t.text_dim); + } + } +} + +pub fn set_theme(app: &mut App, k: ThemeKind) { + app.theme = k; + theme::apply(&app.s.ctx, k); + app.s.prefs.theme = k.id().into(); + let _ = app.s.svc.save_preferences(&app.s.prefs); +} + +fn window_controls(ui: &mut Ui, area: Rect) { + let t = Tokens::get(ui.ctx()); + let ctx = ui.ctx().clone(); + let max = ctx.input(|i| i.viewport().maximized.unwrap_or(false)); + for (k, (glyph, close)) in [(icon::MINUS, false), (if max { icon::CORNERS_IN } else { icon::SQUARE }, false), (icon::X, true)].into_iter().enumerate() { + let r = Rect::from_min_size(pos2(area.left() + k as f32 * 44.0, area.top()), vec2(44.0, area.height())); + let resp = ui.interact(r, ui.id().with(("winctl", k)), Sense::click()); + if resp.hovered() { + ui.painter().rect_filled(r, 0.0, if close { Color32::from_rgb(196, 43, 28) } else { t.hover }); + } + let c = if close && resp.hovered() { Color32::WHITE } else { t.text_dim }; + ui.painter().text(r.center(), Align2::CENTER_CENTER, glyph, egui::FontId::proportional(14.0), c); + if resp.clicked() { + match k { + 0 => ctx.send_viewport_cmd(ViewportCommand::Minimized(true)), + 1 => ctx.send_viewport_cmd(ViewportCommand::Maximized(!max)), + _ => ctx.send_viewport_cmd(ViewportCommand::Close), + } + } + } +} + +/// Invisible window edges that resize the undecorated window. +pub fn resize_edges(ctx: &egui::Context) { + use egui::{CursorIcon, ResizeDirection as D}; + if ctx.input(|i| i.viewport().maximized.unwrap_or(false) || i.viewport().fullscreen.unwrap_or(false)) { + return; + } + let r = ctx.content_rect(); + let e = 5.0; + let zones = [ + (Rect::from_min_max(r.min, r.min + vec2(12.0, 12.0)), D::NorthWest, CursorIcon::ResizeNorthWest), + (Rect::from_min_max(pos2(r.right() - 12.0, r.top()), pos2(r.right(), r.top() + 12.0)), D::NorthEast, CursorIcon::ResizeNorthEast), + (Rect::from_min_max(pos2(r.left(), r.bottom() - 12.0), pos2(r.left() + 12.0, r.bottom())), D::SouthWest, CursorIcon::ResizeSouthWest), + (Rect::from_min_max(r.max - vec2(12.0, 12.0), r.max), D::SouthEast, CursorIcon::ResizeSouthEast), + (Rect::from_min_max(r.min, pos2(r.right(), r.top() + e)), D::North, CursorIcon::ResizeVertical), + (Rect::from_min_max(pos2(r.left(), r.bottom() - e), r.max), D::South, CursorIcon::ResizeVertical), + (Rect::from_min_max(r.min, pos2(r.left() + e, r.bottom())), D::West, CursorIcon::ResizeHorizontal), + (Rect::from_min_max(pos2(r.right() - e, r.top()), r.max), D::East, CursorIcon::ResizeHorizontal), + ]; + let Some(p) = ctx.input(|i| i.pointer.hover_pos()) else { return }; + for (zone, dir, cursor) in zones { + if zone.contains(p) { + ctx.set_cursor_icon(cursor); + if ctx.input(|i| i.pointer.primary_pressed()) { + ctx.send_viewport_cmd(ViewportCommand::BeginResize(dir)); + } + return; + } + } +} + +// --- status bar ----------------------------------------------------------------------------- + +pub fn state_color(t: &Tokens, s: SessionState) -> Color32 { + match s { + SessionState::Connected => t.success, + SessionState::Connecting | SessionState::Reconnecting => t.warning, + SessionState::Error => t.danger, + SessionState::Disconnected => t.text_faint, + } +} + +pub fn state_label(s: SessionState) -> &'static str { + match s { + SessionState::Connecting => "Connecting", + SessionState::Connected => "Connected", + SessionState::Disconnected => "Disconnected", + SessionState::Reconnecting => "Reconnecting", + SessionState::Error => "Error", + } +} + +pub fn status_bar(app: &mut App, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + latency_tick(app); + ui.horizontal_centered(|ui| { + ui.spacing_mut().item_spacing.x = 14.0; + ui.add_space(8.0); + let dim = |s: String| RichText::new(s).size(11.5).color(t.text_dim); + let active = app.s.active.clone(); + match active.as_deref().and_then(|a| app.s.pane(a)) { + Some(p) => { + let st = app.s.session_state(&p.info.id).unwrap_or(p.info.state); + ui.label(RichText::new("●").color(state_color(&t, st)).size(10.0)); + ui.add_space(-10.0); + ui.label(dim(state_label(st).into())); + ui.label(RichText::new(crate::views::protocol_badge(p.info.protocol)).size(11.0).color(crate::views::protocol_color(p.info.protocol))); + if p.recording { + ui.label(RichText::new(format!("{} REC", icon::RECORD)).size(11.0).color(t.danger)); + } + } + None => { + ui.label(dim("No active session".into())); + } + } + if let Some((sid, r)) = &app.latency + && active.as_deref() == Some(sid) + && let Ok(ms) = r + { + let c = if *ms < 100 { t.success } else if *ms < 250 { t.warning } else { t.danger }; + ui.label(RichText::new(format!("{ms} ms")).size(11.5).color(c)); + } + if app.s.broadcast { + ui.label(RichText::new(format!("{} BROADCAST", icon::BROADCAST)).size(11.0).color(t.warning)); + } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.add_space(8.0); + let vault = app.s.svc.secrets.vault.is_unlocked(); + let keychain = app.s.svc.secrets.keychain_ok; + let label = if vault { format!("{} Vault unlocked", icon::LOCK_OPEN) } else { format!("{} Vault locked", icon::LOCK) }; + if ui.add(egui::Label::new(dim(label)).sense(Sense::click())).on_hover_text(if keychain { "Saved credentials use the OS keychain; the vault is the fallback." } else { "No OS keychain available: saved credentials use the encrypted vault." }).clicked() { + app.settings.open(SettingsTab::Security); + } + let total = app.s.panes.len(); + let connected = app.s.panes.keys().filter(|id| app.s.session_state(id) == Some(SessionState::Connected)).count(); + ui.label(dim(format!("{connected}/{total} sessions"))); + if let Some(u) = &app.update + && u.available + { + let txt = format!("{} Update available{}", icon::ARROW_CIRCLE_UP, if u.latest_version.is_empty() { String::new() } else { format!(" ({})", u.latest_version) }); + if ui.add(egui::Label::new(RichText::new(txt).size(11.5).color(t.accent)).sense(Sense::click())).clicked() { + app.settings.open(SettingsTab::About); + } + } + }); + }); +} + +static LATENCY: std::sync::Mutex)>> = std::sync::Mutex::new(None); + +/// Pings the active SSH session every 10 s for the status-bar latency. +fn latency_tick(app: &mut App) { + let id = app.s.active.clone(); + let ssh = id.as_deref().and_then(|a| app.s.pane(a)).is_some_and(|p| p.info.protocol == Protocol::Ssh); + if let Some(r) = LATENCY.lock().unwrap().take() { + app.latency = Some(r); + } + let Some(id) = id.filter(|_| ssh) else { return }; + if app.s.session_state(&id) != Some(SessionState::Connected) { + return; + } + let last = app.s.ctx.data(|d| d.get_temp::<(String, f64)>(egui::Id::new("nexus-latency-at"))); + let due = last.as_ref().is_none_or(|(sid, at)| sid != &id || app.s.now - at > 10.0); + // A failed ping stops pinging that session, as before. + let failed = matches!(&app.latency, Some((sid, Err(_))) if sid == &id); + if !due || failed { + app.s.ctx.request_repaint_after(std::time::Duration::from_secs(10)); + return; + } + app.s.ctx.data_mut(|d| d.insert_temp(egui::Id::new("nexus-latency-at"), (id.clone(), app.s.now))); + let (svc, ctx) = (app.s.svc.clone(), app.s.ctx.clone()); + std::thread::spawn(move || { + let r = svc.sessions.ping(&id).map_err(|e| e.to_string()); + *LATENCY.lock().unwrap() = Some((id, r)); + ctx.request_repaint(); + }); +} + +// --- palette -------------------------------------------------------------------------------- + +#[derive(Default)] +pub struct Palette { + pub query: String, + pub sel: usize, +} + +#[derive(Clone)] +enum Cmd { + Connect(String), + NewProfile(Protocol), + Quick, + Local, + CloseTab, + NextTab, + PrevTab, + Tab(String), + Settings, + Import, + Export, + Lock, + Unlock, + Keys, + Triggers, + Snippets, + Recordings, + SplitRight, + SplitDown, + Clear, + Reset, + Broadcast, + Theme(ThemeKind), + About, + Fullscreen, +} + +/// Fuzzy subsequence match with bonuses for consecutive and leading hits (utils.js fuzzyMatch). +pub fn fuzzy_score(text: &str, query: &str) -> Option { + if query.is_empty() { + return Some(1); + } + let text: Vec = text.to_lowercase().chars().collect(); + let (mut idx, mut score, mut bonus) = (0usize, 0i32, 0i32); + for qc in query.to_lowercase().chars() { + let found = (idx..text.len()).find(|&i| text[i] == qc)?; + bonus = if found == idx { bonus + 1 } else { 0 }; + score += 1 + bonus + if found == 0 { 2 } else { 0 }; + idx = found + 1; + } + Some(score) +} + +fn commands(app: &App) -> Vec<(Cmd, String, &'static str, &'static str, String)> { + let mut v: Vec<(Cmd, String, &'static str, &'static str, String)> = vec![]; + for p in &app.s.profiles { + let name = if p.display_name.is_empty() { &p.host } else { &p.display_name }; + v.push((Cmd::Connect(p.id.clone()), format!("Connect to {name}"), "Connections", icon::PLUGS_CONNECTED, String::new())); + } + for (pr, label) in [(Protocol::Ssh, "New SSH Connection"), (Protocol::Telnet, "New Telnet Connection"), (Protocol::Raw, "New RAW Connection"), (Protocol::Serial, "New Serial Connection")] { + v.push((Cmd::NewProfile(pr), label.into(), "Actions", icon::PLUS_CIRCLE, if pr == Protocol::Ssh { keys("Mod+N") } else { String::new() })); + } + v.push((Cmd::Quick, "Quick Connect…".into(), "Actions", icon::LIGHTNING, keys("Mod+Shift+Q"))); + v.push((Cmd::Local, "Open Local Terminal".into(), "Actions", icon::TERMINAL_WINDOW, String::new())); + v.push((Cmd::Broadcast, "Toggle Broadcast Mode".into(), "Actions", icon::BROADCAST, keys("Mod+Shift+B"))); + v.push((Cmd::Snippets, "Snippet Library".into(), "Actions", icon::CODE, keys("Mod+Shift+S"))); + v.push((Cmd::Recordings, "Recordings".into(), "Actions", icon::FILM_STRIP, String::new())); + if !app.s.tabs.is_empty() { + v.push((Cmd::CloseTab, "Close Tab".into(), "Tabs", icon::X, keys("Mod+W"))); + v.push((Cmd::NextTab, "Next Tab".into(), "Tabs", icon::ARROW_RIGHT, keys("Mod+Tab"))); + v.push((Cmd::PrevTab, "Previous Tab".into(), "Tabs", icon::ARROW_LEFT, keys("Mod+Shift+Tab"))); + for t in &app.s.tabs { + if let Some(p) = app.s.pane(&t.id) { + v.push((Cmd::Tab(t.id.clone()), format!("Switch to {}", p.info.tab_name), "Tabs", icon::BROWSER, String::new())); + } + } + } + v.push((Cmd::Settings, "Settings".into(), "Application", icon::GEAR, keys("Mod+,"))); + v.push((Cmd::Import, "Import Profiles".into(), "Application", icon::DOWNLOAD_SIMPLE, String::new())); + v.push((Cmd::Export, "Export Profiles".into(), "Application", icon::UPLOAD_SIMPLE, String::new())); + for k in ThemeKind::ALL { + v.push((Cmd::Theme(k), format!("Theme: {}", k.label()), "Application", icon::PALETTE, String::new())); + } + v.push((Cmd::Fullscreen, "Toggle Full Screen".into(), "Application", icon::CORNERS_OUT, "F11".into())); + v.push((Cmd::About, "About Nexus Terminal".into(), "Application", icon::INFO, String::new())); + if app.s.svc.secrets.vault.is_unlocked() { + v.push((Cmd::Lock, "Lock Secret Store".into(), "Security", icon::LOCK, String::new())); + } else { + v.push((Cmd::Unlock, "Unlock Secret Store".into(), "Security", icon::LOCK_OPEN, String::new())); + } + v.push((Cmd::Keys, "SSH Key Manager".into(), "Security", icon::KEY, String::new())); + v.push((Cmd::Triggers, "Trigger Rules".into(), "Terminal", icon::LIGHTNING_A, String::new())); + if app.s.focused_session().and_then(|f| app.s.pane(&f)).is_some_and(|p| matches!(p.view, View::Terminal(_))) { + v.push((Cmd::SplitRight, "Split Pane Right".into(), "Terminal", icon::SQUARE_SPLIT_HORIZONTAL, keys("Mod+Shift+D"))); + v.push((Cmd::SplitDown, "Split Pane Down".into(), "Terminal", icon::SQUARE_SPLIT_VERTICAL, keys("Mod+Shift+E"))); + v.push((Cmd::Clear, "Clear Terminal".into(), "Terminal", icon::ERASER, String::new())); + v.push((Cmd::Reset, "Reset Terminal".into(), "Terminal", icon::ARROW_COUNTER_CLOCKWISE, String::new())); + } + v +} + +fn run(app: &mut App, ctx: &egui::Context, c: Cmd) { + match c { + Cmd::Connect(id) => app.connect_profile(&id, false), + Cmd::NewProfile(p) => app.s.request(Request::NewProfile { protocol: Some(p), folder: String::new() }), + Cmd::Quick => app.quick.open(), + Cmd::Local => app.connect_local(), + Cmd::CloseTab => { + if let Some(id) = app.s.focused_session() { + app.request_close(&id); + } + } + Cmd::NextTab | Cmd::PrevTab => { + let order = crate::tabs::ordered(&app.s); + if let Some(cur) = app.s.active.as_ref().and_then(|a| order.iter().position(|x| x == a)) { + let n = order.len() as i32; + let next = (cur as i32 + if matches!(c, Cmd::NextTab) { 1 } else { -1 }).rem_euclid(n) as usize; + let id = order[next].clone(); + app.activate(&id); + } + } + Cmd::Tab(id) => app.activate(&id), + Cmd::Settings => app.settings.open(SettingsTab::General), + Cmd::Import => app.s.request(Request::ImportEncrypted), + Cmd::Export => app.s.request(Request::ExportEncrypted(vec![])), + Cmd::Lock => { + app.s.svc.secrets.vault.lock(); + app.s.info("Secret store locked"); + } + Cmd::Unlock => app.settings.open(SettingsTab::Security), + Cmd::Keys => app.keys.open(), + Cmd::Triggers => app.triggers.open(), + Cmd::Snippets => app.snippets.open(), + Cmd::Recordings => app.recordings.open(), + Cmd::SplitRight => crate::splits::split_focused(app, crate::splits::Dir::Right), + Cmd::SplitDown => crate::splits::split_focused(app, crate::splits::Dir::Down), + Cmd::Clear | Cmd::Reset => { + if let Some(id) = app.s.focused_session() { + app.terminal_menu(&id, if matches!(c, Cmd::Clear) { crate::terminal::MenuAction::Clear } else { crate::terminal::MenuAction::Reset }); + } + } + Cmd::Broadcast => app.toggle_broadcast(), + Cmd::Theme(k) => set_theme(app, k), + Cmd::About => app.settings.open(SettingsTab::About), + Cmd::Fullscreen => { + let full = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false)); + ctx.send_viewport_cmd(ViewportCommand::Fullscreen(!full)); + } + } +} + +pub fn modal_backdrop(ctx: &egui::Context) { + egui::Area::new("backdrop".into()).order(egui::Order::Middle).fixed_pos(pos2(0.0, 0.0)).show(ctx, |ui| { + let r = ctx.content_rect(); + ui.painter().rect_filled(r, 0.0, Color32::from_black_alpha(90)); + ui.allocate_rect(r, Sense::click()); + }); +} + +pub fn palette(app: &mut App, ctx: &egui::Context) { + let Some(mut pal) = app.palette.take() else { return }; + let t = Tokens::get(ctx); + let mut matches: Vec<_> = commands(app) + .into_iter() + .filter_map(|c| fuzzy_score(&format!("{} {}", c.1, c.2), pal.query.trim()).map(|s| (s, c))) + .collect(); + if !pal.query.trim().is_empty() { + matches.sort_by(|a, b| b.0.cmp(&a.0)); + } + let matches: Vec<_> = matches.into_iter().map(|(_, c)| c).collect(); + pal.sel = pal.sel.min(matches.len().saturating_sub(1)); + let mut chosen = None; + let mut close = ctx.input(|i| i.key_pressed(Key::Escape)); + if ctx.input(|i| i.key_pressed(Key::ArrowDown)) { + pal.sel = (pal.sel + 1).min(matches.len().saturating_sub(1)); + } + if ctx.input(|i| i.key_pressed(Key::ArrowUp)) { + pal.sel = pal.sel.saturating_sub(1); + } + if ctx.input(|i| i.key_pressed(Key::Enter)) { + chosen = matches.get(pal.sel).map(|m| m.0.clone()); + } + modal_backdrop(ctx); + egui::Area::new("palette".into()).order(egui::Order::Foreground).anchor(Align2::CENTER_TOP, vec2(0.0, 80.0)).show(ctx, |ui| { + egui::Frame::popup(ui.style()).corner_radius(10).inner_margin(8).show(ui, |ui| { + ui.set_width(580.0); + ui.horizontal(|ui| { + ui.label(RichText::new(icon::MAGNIFYING_GLASS).size(16.0).color(t.text_faint)); + let r = ui.add(egui::TextEdit::singleline(&mut pal.query).hint_text("Connect, switch tab, or run a command…").frame(egui::Frame::NONE).desired_width(f32::INFINITY).font(egui::FontId::proportional(15.0))); + r.request_focus(); + if r.changed() { + pal.sel = 0; + } + }); + ui.separator(); + egui::ScrollArea::vertical().max_height(400.0).show(ui, |ui| { + let mut last = ""; + for (i, (cmd, label, section, glyph, keys)) in matches.iter().enumerate() { + if *section != last { + last = section; + ui.label(RichText::new(section.to_uppercase()).font(theme::semibold(10.0)).color(t.text_faint)); + } + let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 28.0), Sense::click()); + if i == pal.sel { + ui.painter().rect_filled(r, 6.0, t.accent_soft); + ui.scroll_to_rect(r, None); + } else if resp.hovered() { + ui.painter().rect_filled(r, 6.0, t.hover); + } + ui.painter().text(r.left_center() + vec2(12.0, 0.0), Align2::LEFT_CENTER, *glyph, egui::FontId::proportional(15.0), t.accent); + ui.painter().text(r.left_center() + vec2(36.0, 0.0), Align2::LEFT_CENTER, label, egui::FontId::proportional(13.0), t.text); + if !keys.is_empty() { + ui.painter().text(r.right_center() - vec2(10.0, 0.0), Align2::RIGHT_CENTER, keys, theme::semibold(10.5), t.text_faint); + } + if resp.clicked() { + chosen = Some(cmd.clone()); + } + } + if matches.is_empty() { + widgets::hint(ui, "No matching command."); + } + }); + }); + }); + if let Some(c) = chosen { + close = true; + run(app, ctx, c); + } + if !close { + app.palette = Some(pal); + } +} + +// --- prompts -------------------------------------------------------------------------------- + +fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) { + modal_backdrop(ctx); + egui::Area::new(egui::Id::new(id)).order(egui::Order::Foreground).anchor(Align2::CENTER_CENTER, vec2(0.0, -40.0)).show(ctx, |ui| { + egui::Frame::popup(ui.style()).corner_radius(10).inner_margin(16).show(ui, |ui| { + ui.set_width(width); + body(ui); + }); + }); +} + +pub fn prompts(app: &mut App, ctx: &egui::Context) { + let Some(p) = app.prompts.front_mut() else { return }; + let t = Tokens::get(ctx); + let esc = ctx.input(|i| i.key_pressed(Key::Escape)); + let enter = ctx.input(|i| i.key_pressed(Key::Enter)); + let mut done = false; + match p { + Prompt::Secret { session, title, message, value, reply } => { + let who = app.s.pane(session).map(|p| format!("{} ({})", p.info.tab_name, p.info.host)); + modal(ctx, "secret-prompt", 380.0, |ui| { + ui.label(RichText::new(format!("{} {title}", icon::KEY)).font(theme::semibold(15.0))); + if let Some(w) = &who { + ui.label(RichText::new(w).color(t.text_dim)); + } + ui.add_space(4.0); + ui.label(message.as_str()); + let r = ui.add(egui::TextEdit::singleline(value).password(true).desired_width(f32::INFINITY)); + r.request_focus(); + ui.add_space(6.0); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::CHECK, "OK").clicked() || enter { + let _ = reply.send(std::mem::take(value)); + done = true; + } + if ui.button("Cancel").clicked() || esc { + done = true; // dropping the sender cancels + } + }); + }); + } + Prompt::HostKey { host, key_type, fingerprint, reply } => { + modal(ctx, "hostkey-prompt", 460.0, |ui| { + ui.label(RichText::new(format!("{} Verify host key", icon::SHIELD_WARNING)).font(theme::semibold(15.0))); + ui.label(format!("The authenticity of {host} can't be established.")); + ui.add_space(4.0); + widgets::kv(ui, "hk", &[("Key type", key_type.clone()), ("Fingerprint", fingerprint.clone())]); + widgets::hint(ui, "Accept only if this matches the fingerprint the server's administrator gave you."); + ui.add_space(6.0); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::CHECK, "Accept & Save").clicked() { + let _ = reply.send(true); + done = true; + } + if ui.button("Reject").clicked() || esc { + let _ = reply.send(false); + done = true; + } + }); + }); + } + } + if done { + app.prompts.pop_front(); + } +} + +// --- dialogs -------------------------------------------------------------------------------- + +pub fn dialogs(app: &mut App, ctx: &egui::Context) { + let Some(mut d) = app.dialog.take() else { return }; + let t = Tokens::get(ctx); + let esc = ctx.input(|i| i.key_pressed(Key::Escape)); + let mut keep = !esc; + match &mut d { + Dialog::Reuse { profile, existing } => { + let name = app.s.profile(profile).map(|p| p.display_name.clone()).unwrap_or_default(); + modal(ctx, "reuse", 400.0, |ui| { + ui.label(RichText::new("Connection Already Open").font(theme::semibold(15.0))); + ui.label(format!("{name} already has an open session.")); + ui.add_space(8.0); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::BROWSER, "Use Existing").clicked() { + let id = existing.clone(); + app.activate(&id); + keep = false; + } + if ui.button("New Connection").clicked() { + let id = profile.clone(); + app.connect_profile(&id, true); + keep = false; + } + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + } + Dialog::DirectVnc { profile } => { + modal(ctx, "direct-vnc", 420.0, |ui| { + ui.label(RichText::new(format!("{} Unencrypted VNC", icon::WARNING)).font(theme::semibold(15.0)).color(t.warning)); + ui.label("This VNC connection isn't tunnelled over SSH. VNC traffic (including keystrokes and screen contents) is sent unencrypted. Enable the SSH tunnel in the profile to protect it."); + ui.add_space(8.0); + ui.horizontal(|ui| { + if ui.button("Connect Anyway").clicked() { + let id = profile.clone(); + app.connect_profile(&id, true); + keep = false; + } + if ui.button("Edit Profile").clicked() { + app.s.request(Request::EditProfile(profile.clone())); + keep = false; + } + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + } + Dialog::ConfirmClose { session } => { + let name = app.s.pane(session).map(|p| p.info.tab_name.clone()).unwrap_or_default(); + modal(ctx, "confirm-close", 360.0, |ui| { + ui.label(RichText::new("Close session?").font(theme::semibold(15.0))); + ui.label(format!("{name} is still connected.")); + ui.add_space(8.0); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::X, "Close").clicked() || ctx.input(|i| i.key_pressed(Key::Enter)) { + let id = session.clone(); + app.close_session(&id); + keep = false; + } + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + } + Dialog::ConfirmDisconnectMany { sessions } => { + modal(ctx, "confirm-many", 360.0, |ui| { + ui.label(RichText::new("Disconnect sessions?").font(theme::semibold(15.0))); + ui.label(format!("{} open session(s) will be closed.", sessions.len())); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::PLUGS, "Disconnect").clicked() { + for id in sessions.clone() { + app.close_session(&id); + } + keep = false; + } + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + } + Dialog::Rename { session, name } => { + modal(ctx, "rename", 340.0, |ui| { + ui.label(RichText::new("Rename tab").font(theme::semibold(15.0))); + let r = ui.add(egui::TextEdit::singleline(name).desired_width(f32::INFINITY)); + r.request_focus(); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::CHECK, "Rename").clicked() || ctx.input(|i| i.key_pressed(Key::Enter)) { + let n = name.trim().to_string(); + if !n.is_empty() { + let _ = app.s.svc.sessions.rename(session, &n); + if let Some(p) = app.s.pane_mut(session) { + p.info.tab_name = n; + } + } + keep = false; + } + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + } + Dialog::Broadcast { picks } => { + modal(ctx, "broadcast", 360.0, |ui| { + ui.label(RichText::new("Broadcast Targets").font(theme::semibold(15.0))); + widgets::hint(ui, "Select which sessions should receive broadcast input:"); + for (_, label, on) in picks.iter_mut() { + ui.checkbox(on, label.as_str()); + } + ui.add_space(6.0); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::BROADCAST, "Apply & Enable").clicked() { + let sel: std::collections::HashSet = picks.iter().filter(|p| p.2).map(|p| p.0.clone()).collect(); + if sel.is_empty() { + app.s.error("Select at least one target session"); + } else { + let all = sel.len() == picks.len(); + app.s.broadcast_targets = if all { None } else { Some(sel.clone()) }; + if !app.s.broadcast { + app.toggle_broadcast(); + } else { + app.s.info(if all { "Broadcasting to all sessions".to_string() } else { format!("Broadcasting to {} sessions", sel.len()) }); + } + keep = false; + } + } + if ui.button("Select All").clicked() { + for p in picks.iter_mut() { + p.2 = true; + } + } + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + } + Dialog::Guided { session, title, body, known_hosts } => { + let label = app.s.pane(session).map(|p| p.info.tab_name.clone()).unwrap_or_else(|| "Session".into()); + let profile_id = app.s.pane(session).map(|p| p.info.profile_id.clone()).unwrap_or_default(); + modal(ctx, "guided", 440.0, |ui| { + ui.label(RichText::new(title.as_str()).font(theme::semibold(15.0)).color(t.danger)); + ui.label(RichText::new(label).strong()); + ui.add(egui::Label::new(body.as_str()).wrap()); + ui.add_space(8.0); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::ARROW_CLOCKWISE, "Retry").clicked() { + let id = session.clone(); + app.reconnect(&id); + keep = false; + } + if !profile_id.is_empty() && ui.button("Edit Profile").clicked() { + app.s.request(crate::state::Request::EditProfile(profile_id.clone())); + keep = false; + } + if *known_hosts && ui.button("Known Hosts").clicked() { + app.settings.open(SettingsTab::KnownHosts); + keep = false; + } + if ui.button("Close").clicked() { + keep = false; + } + }); + }); + } + Dialog::Delete { profiles } => { + let names: Vec = profiles.iter().filter_map(|id| app.s.profile(id)).map(|p| p.display_name.clone()).collect(); + modal(ctx, "delete", 380.0, |ui| { + ui.label(RichText::new("Delete connection?").font(theme::semibold(15.0))); + ui.label(if names.len() == 1 { format!("\"{}\" will be deleted, with its saved credentials.", names[0]) } else { format!("{} connections will be deleted.", names.len()) }); + ui.add_space(8.0); + ui.horizontal(|ui| { + if ui.add(egui::Button::new(RichText::new(format!("{} Delete", icon::TRASH)).color(Color32::WHITE)).fill(t.danger)).clicked() { + for id in profiles.clone() { + if let Err(e) = app.s.svc.delete_profile(&id) { + app.s.error(e.to_string()); + } + } + app.s.reload_profiles(); + app.sidebar.selected.clear(); + keep = false; + } + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + } + Dialog::Input { title, label, value, then } => { + let mut submit = false; + modal(ctx, "input", 360.0, |ui| { + ui.label(RichText::new(title.as_str()).font(theme::semibold(15.0))); + ui.label(label.as_str()); + let r = ui.add(egui::TextEdit::singleline(value).desired_width(f32::INFINITY)); + r.request_focus(); + ui.horizontal(|ui| { + submit = widgets::primary(ui, icon::CHECK, "OK").clicked() || ctx.input(|i| i.key_pressed(Key::Enter)); + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + if submit { + keep = false; + let v = value.trim().to_string(); + match then { + InputThen::Tags { profiles, replace } => { + let tags: Vec = v.split(',').map(|x| x.trim().to_string()).filter(|x| !x.is_empty()).collect(); + for id in profiles.iter() { + if let Some(mut p) = app.s.profile(id).cloned() { + if *replace { + p.tags = tags.clone(); + } else { + for tg in &tags { + if !p.tags.contains(tg) { + p.tags.push(tg.clone()); + } + } + } + let _ = app.s.svc.update_profile(p); + } + } + } + InputThen::MoveGroup { profiles } => { + for id in profiles.iter() { + if let Some(mut p) = app.s.profile(id).cloned() { + p.folder = v.clone(); + let _ = app.s.svc.update_profile(p); + } + } + } + } + app.s.reload_profiles(); + } + } + } + if keep && app.dialog.is_none() { + app.dialog = Some(d); + } +} + +// --- toasts --------------------------------------------------------------------------------- + +pub fn toasts(app: &mut App, ctx: &egui::Context) { + let t = Tokens::get(ctx); + let now = app.s.now; + app.s.toasts.retain(|x| x.until > now); + if app.s.toasts.is_empty() { + return; + } + ctx.request_repaint_after(std::time::Duration::from_millis(250)); + let mut dismiss = None; + egui::Area::new("toasts".into()).order(egui::Order::Tooltip).anchor(Align2::RIGHT_TOP, vec2(-16.0, 48.0)).show(ctx, |ui| { + for (i, toast) in app.s.toasts.iter().enumerate() { + egui::Frame::popup(ui.style()).corner_radius(8).inner_margin(egui::Margin::symmetric(12, 8)).show(ui, |ui| { + ui.set_max_width(420.0); + ui.horizontal_top(|ui| { + let (g, c) = match toast.kind { + ToastKind::Error => (icon::WARNING_CIRCLE, t.danger), + ToastKind::Warning => (icon::WARNING, t.warning), + ToastKind::Success => (icon::CHECK_CIRCLE, t.success), + ToastKind::Info => (icon::INFO, t.accent), + }; + ui.label(RichText::new(g).color(c).size(15.0)); + ui.add(egui::Label::new(&toast.text).wrap()); + if ui.add(egui::Button::new(RichText::new(icon::X).size(11.0)).frame(false)).clicked() { + dismiss = Some(i); + } + }); + }); + ui.add_space(6.0); + } + }); + if let Some(i) = dismiss { + app.s.toasts.remove(i); + } +} diff --git a/rust/app/src/forwards.rs b/rust/app/src/forwards.rs new file mode 100644 index 0000000..ef494ea --- /dev/null +++ b/rust/app/src/forwards.rs @@ -0,0 +1,17 @@ +//! Port forwards dialog for a live SSH session: list, stop, add. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; + +#[derive(Default)] +pub struct Forwards { + session: Option, +} + +impl Forwards { + pub fn open(&mut self, session: &str) { + self.session = Some(session.into()); + } + + pub fn ui(&mut self, _ctx: &egui::Context, _s: &mut Shared) {} +} diff --git a/rust/app/src/github_view.rs b/rust/app/src/github_view.rs new file mode 100644 index 0000000..0b9a472 --- /dev/null +++ b/rust/app/src/github_view.rs @@ -0,0 +1,25 @@ +//! GitHub repository browser. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; +use nexus_core::models::GitHubRepo; + +pub struct GithubView { + pub session: String, +} + +impl GithubView { + pub fn new(session: &str, _title: &str) -> GithubView { + GithubView { session: session.into() } + } + + pub fn on_status(&mut self, _status: String) {} + + pub fn on_repos(&mut self, _login: String, _repos: Vec) {} + + pub fn on_error(&mut self, _error: String) {} + + pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { + ui.label("GitHub"); + } +} diff --git a/rust/app/src/irc_view.rs b/rust/app/src/irc_view.rs new file mode 100644 index 0000000..76dc455 --- /dev/null +++ b/rust/app/src/irc_view.rs @@ -0,0 +1,23 @@ +//! IRC client view: room tabs, message log, composer and user/room lists. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; +use nexus_core::clients::irc; +use nexus_core::models::Profile; + +pub struct IrcView { + pub session: String, +} + +impl IrcView { + pub fn new(session: &str, _profile: &Profile) -> IrcView { + IrcView { session: session.into() } + } + + pub fn on_event(&mut self, _event: irc::Event) {} + + /// True when an event for a room other than the visible one arrived (for the tab marker). + pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { + ui.label("IRC"); + } +} diff --git a/rust/app/src/main.rs b/rust/app/src/main.rs index 660c693..76186c3 100644 --- a/rust/app/src/main.rs +++ b/rust/app/src/main.rs @@ -1,7 +1,101 @@ -//! Nexus Terminal: SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local terminals. +//! Nexus Terminal: SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local terminals in one +//! native window. `nexus-terminal [--theme pro|midnight|light] [--screenshot out.png] [--size WxH]`. #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] +mod app; +mod chrome; +mod forwards; +mod github_view; +mod irc_view; +mod profile_editor; +mod quick_connect; +mod raw_view; +mod recordings; +mod settings; +mod sftp; +mod sidebar; +mod snippets; +mod splits; +mod ssh_keys; +mod state; +mod tabs; mod terminal; mod terminal_themes; +mod theme; +mod transfer; +mod triggers; +mod views; +mod vnc_view; +mod widgets; -fn main() {} +use nexus_core::config::AppConfig; +use nexus_core::service::Service; +use std::sync::Arc; + +fn main() -> eframe::Result { + let mut opts = app::Options { screenshot: None, theme: None }; + let mut size = [1360.0, 860.0]; + let mut data_dir = None; + let mut args = std::env::args().skip(1); + while let Some(a) = args.next() { + match a.as_str() { + "--theme" => opts.theme = args.next(), + "--screenshot" => opts.screenshot = args.next().map(Into::into), + // Use another data directory (testing, screenshots, portable installs). + "--data-dir" => data_dir = args.next(), + "--size" => { + if let Some((w, h)) = args.next().as_deref().and_then(|s| s.split_once('x')) { + size = [w.parse().unwrap_or(size[0]), h.parse().unwrap_or(size[1])]; + } + } + "-V" | "--version" => { + println!("Nexus Terminal {}", nexus_core::service::VERSION); + return Ok(()); + } + _ => { + println!("usage: nexus-terminal [--theme pro|midnight|light] [--data-dir DIR] [--screenshot out.png] [--size WxH]"); + return Ok(()); + } + } + } + let config = match data_dir { + Some(d) => { + let c = AppConfig::in_dir(d.into()); + std::fs::create_dir_all(&c.vault_dir).ok(); + Ok(c) + } + None => AppConfig::load(), + }; + let svc = match config.map_err(|e| e.to_string()).and_then(|c| Service::open(c).map_err(|e| e.to_string())) { + Ok(s) => Arc::new(s), + Err(e) => { + eprintln!("Nexus Terminal couldn't open its data: {e}"); + rfd::MessageDialog::new().set_title("Nexus Terminal").set_description(format!("Couldn't open the application data:\n{e}")).show(); + std::process::exit(1); + } + }; + let headless = opts.screenshot.is_some(); + let native = eframe::NativeOptions { + persist_window: !headless, + viewport: egui::ViewportBuilder::default() + .with_title("Nexus Terminal") + .with_app_id("nexus-terminal") + .with_icon(window_icon()) + .with_inner_size(size) + .with_min_inner_size([820.0, 520.0]) + .with_decorations(false) + .with_transparent(false) + .with_drag_and_drop(true), + ..Default::default() + }; + let svc2 = svc.clone(); + let r = eframe::run_native("nexus-terminal", native, Box::new(move |cc| Ok(Box::new(app::App::new(cc, svc2, opts))))); + svc.sessions.close_all(); + svc.secrets.vault.lock(); + r +} + +fn window_icon() -> egui::IconData { + let img = image::load_from_memory(chrome::LOGO_PNG).map(|i| i.to_rgba8()).unwrap_or_default(); + egui::IconData { width: img.width(), height: img.height(), rgba: img.into_raw() } +} diff --git a/rust/app/src/profile_editor.rs b/rust/app/src/profile_editor.rs new file mode 100644 index 0000000..f9b7aa4 --- /dev/null +++ b/rust/app/src/profile_editor.rs @@ -0,0 +1,31 @@ +//! Profile editor (right dock): create and edit every protocol's settings. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; +use nexus_core::models::Protocol; + +#[derive(Default)] +pub struct ProfileEditor { + open: bool, +} + +impl ProfileEditor { + pub fn open_new(&mut self, _s: &Shared, _protocol: Option, _folder: &str) { + self.open = true; + } + + pub fn open_edit(&mut self, _s: &Shared, _id: &str) { + self.open = true; + } + + pub fn is_open(&self) -> bool { + self.open + } + + /// Drawn inside the right dock while open. + pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { + if ui.button("Close").clicked() { + self.open = false; + } + } +} diff --git a/rust/app/src/quick_connect.rs b/rust/app/src/quick_connect.rs new file mode 100644 index 0000000..64d440c --- /dev/null +++ b/rust/app/src/quick_connect.rs @@ -0,0 +1,26 @@ +//! Quick Connect dialog: an ad-hoc session without saving a profile. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; + +#[derive(Default)] +pub struct QuickConnect { + open: bool, +} + +impl QuickConnect { + pub fn open(&mut self) { + self.open = true; + } + + pub fn is_open(&self) -> bool { + self.open + } + + /// Called every frame; draws its window(s) while open. + pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { + let mut open = self.open; + egui::Window::new("QuickConnect").open(&mut open).show(ctx, |ui| ui.label("todo")); + self.open = open; + } +} diff --git a/rust/app/src/raw_view.rs b/rust/app/src/raw_view.rs new file mode 100644 index 0000000..33e8210 --- /dev/null +++ b/rust/app/src/raw_view.rs @@ -0,0 +1,22 @@ +//! RAW TCP viewer: text / hex / split views of the capture and a send bar. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; +use nexus_core::models::Profile; + +pub struct RawView { + pub session: String, +} + +impl RawView { + pub fn new(session: &str, _profile: &Profile) -> RawView { + RawView { session: session.into() } + } + + /// Data from `Event::RawData` (direction "send"/"recv", unix ms timestamp). + pub fn on_data(&mut self, _data: &[u8], _direction: &str, _timestamp: i64) {} + + pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { + ui.label("RAW viewer"); + } +} diff --git a/rust/app/src/recordings.rs b/rust/app/src/recordings.rs new file mode 100644 index 0000000..d57d222 --- /dev/null +++ b/rust/app/src/recordings.rs @@ -0,0 +1,26 @@ +//! Recordings browser and playback window. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; + +#[derive(Default)] +pub struct Recordings { + open: bool, +} + +impl Recordings { + pub fn open(&mut self) { + self.open = true; + } + + pub fn is_open(&self) -> bool { + self.open + } + + /// Called every frame; draws its window(s) while open. + pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { + let mut open = self.open; + egui::Window::new("Recordings").open(&mut open).show(ctx, |ui| ui.label("todo")); + self.open = open; + } +} diff --git a/rust/app/src/settings.rs b/rust/app/src/settings.rs new file mode 100644 index 0000000..aa110f9 --- /dev/null +++ b/rust/app/src/settings.rs @@ -0,0 +1,26 @@ +//! Settings window: General, Terminal, Security, Known Hosts, Help, About. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; + +#[derive(Default)] +pub struct Settings { + open: bool, +} + +impl Settings { + pub fn open(&mut self, _tab: crate::state::SettingsTab) { + self.open = true; + } + + pub fn is_open(&self) -> bool { + self.open + } + + /// Called every frame; draws its window(s) while open. + pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { + let mut open = self.open; + egui::Window::new("Settings").open(&mut open).show(ctx, |ui| ui.label("todo")); + self.open = open; + } +} diff --git a/rust/app/src/sftp.rs b/rust/app/src/sftp.rs new file mode 100644 index 0000000..46e2b2e --- /dev/null +++ b/rust/app/src/sftp.rs @@ -0,0 +1,41 @@ +//! SFTP browser (right dock), the file-drop dialog and the remote directory picker. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; +use nexus_core::clients::sftp::Progress; +use std::path::PathBuf; + +#[derive(Default)] +pub struct Sftp { + open_for: Option, +} + +impl Sftp { + pub fn toggle(&mut self, session: &str) { + self.open_for = if self.open_for.as_deref() == Some(session) { None } else { Some(session.into()) }; + } + + /// The session whose browser is showing in the dock, if any. + pub fn open_for(&self) -> Option<&str> { + self.open_for.as_deref() + } + + pub fn close(&mut self, session: &str) { + if self.open_for.as_deref() == Some(session) { + self.open_for = None; + } + } + + pub fn on_progress(&mut self, _session: &str, _p: Progress) {} + + /// Files dropped on a terminal: offer "paste contents" or (SSH) "upload to a directory". + pub fn drop_files(&mut self, _s: &mut Shared, _session: &str, _paths: Vec) {} + + /// The dock panel. + pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { + ui.label("SFTP"); + } + + /// Modal dialogs (file drop, directory picker), drawn every frame. + pub fn dialogs(&mut self, _ctx: &egui::Context, _s: &mut Shared) {} +} diff --git a/rust/app/src/sidebar.rs b/rust/app/src/sidebar.rs new file mode 100644 index 0000000..c6923cf --- /dev/null +++ b/rust/app/src/sidebar.rs @@ -0,0 +1,305 @@ +//! The connection sidebar: Local Machine, Favourites, Recent, folders, Ungrouped; fuzzy search; +//! click to connect, Ctrl/Cmd-click to multi-select, double-click to edit; bulk actions. + +use crate::app::{App, Dialog, InputThen}; +use crate::chrome::fuzzy_score; +use crate::state::Request; +use crate::theme::{self, Tokens}; +use crate::views::{protocol_badge, protocol_color}; +use crate::widgets; +use egui::{Align2, Rect, RichText, Sense, Ui, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::models::Profile; +use std::collections::HashSet; + +#[derive(Default)] +pub struct Sidebar { + pub search: String, + pub collapsed: HashSet, + pub selected: HashSet, +} + +const RECENT: usize = 5; + +pub fn ui(app: &mut App, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + ui.add_space(8.0); + ui.horizontal(|ui| { + ui.add_space(8.0); + ui.label(RichText::new(icon::MAGNIFYING_GLASS).color(t.text_faint)); + ui.add(egui::TextEdit::singleline(&mut app.sidebar.search).hint_text("Search connections").desired_width(ui.available_width() - 10.0)); + }); + ui.add_space(4.0); + + let footer_h = 36.0 + if app.sidebar.selected.is_empty() { 0.0 } else { 62.0 }; + let list_h = (ui.available_height() - footer_h).max(60.0); + egui::ScrollArea::vertical().max_height(list_h).auto_shrink([false, false]).show(ui, |ui| list(app, ui)); + + if !app.sidebar.selected.is_empty() { + bulk_bar(app, ui); + } + // Footer actions. + ui.separator(); + ui.horizontal(|ui| { + ui.add_space(6.0); + if widgets::icon_button(ui, icon::PLUS, "New connection (Ctrl/Cmd+N)").clicked() { + app.s.request(Request::NewProfile { protocol: None, folder: String::new() }); + } + if widgets::icon_button(ui, icon::COMMAND, "Command palette (Ctrl/Cmd+K)").clicked() { + app.palette = Some(Default::default()); + } + if widgets::icon_button(ui, icon::DOWNLOAD_SIMPLE, "Import profiles").clicked() { + app.s.request(Request::ImportEncrypted); + } + if widgets::icon_button(ui, icon::UPLOAD_SIMPLE, "Export profiles").clicked() { + app.s.request(Request::ExportEncrypted(vec![])); + } + if widgets::icon_button(ui, icon::FILM_STRIP, "Recordings").clicked() { + app.recordings.open(); + } + if widgets::icon_button(ui, icon::GEAR, "Settings (Ctrl/Cmd+,)").clicked() { + app.settings.open(crate::state::SettingsTab::General); + } + }); +} + +fn list(app: &mut App, ui: &mut Ui) { + let q = app.sidebar.search.trim().to_string(); + // Search: one ranked list. + if !q.is_empty() { + let mut hits: Vec<(i32, Profile)> = app + .s + .profiles + .iter() + .filter_map(|p| { + let fields = format!("{} {} {} {}", p.display_name, p.host, p.tags.join(" "), p.protocol.id()); + fuzzy_score(&fields, &q).map(|s| (s, p.clone())) + }) + .collect(); + hits.sort_by(|a, b| b.0.cmp(&a.0)); + if hits.is_empty() { + widgets::empty(ui, icon::MAGNIFYING_GLASS, "No matches", "Try a host, name, tag or protocol."); + } + for (_, p) in hits { + row(app, ui, &p, "search"); + } + return; + } + + local_row(app, ui); + let profiles = app.s.profiles.clone(); + let favs: Vec<&Profile> = profiles.iter().filter(|p| p.favourite).collect(); + if !favs.is_empty() { + section(app, ui, "★ Favourites", "favourites", &favs); + } + let mut recent: Vec<&Profile> = profiles.iter().filter(|p| p.last_connected.is_some()).collect(); + recent.sort_by(|a, b| b.last_connected.cmp(&a.last_connected)); + recent.truncate(RECENT); + if !recent.is_empty() { + section(app, ui, "Recent", "recent", &recent); + } + let mut folders: std::collections::BTreeMap> = Default::default(); + let mut ungrouped = vec![]; + for p in &profiles { + let f = p.folder.trim(); + if f.is_empty() { + ungrouped.push(p); + } else { + folders.entry(f.to_string()).or_default().push(p); + } + } + let mut names: Vec<&String> = folders.keys().collect(); + names.sort_by_key(|a| a.to_lowercase()); + let has_groups = !names.is_empty(); + for f in names { + section(app, ui, f, &format!("group:{f}"), &folders[f]); + } + if !ungrouped.is_empty() { + section(app, ui, if has_groups { "Ungrouped" } else { "All Connections" }, "ungrouped", &ungrouped); + } + if profiles.is_empty() { + widgets::empty(ui, icon::PLUGS, "No connections yet", "Create one with the + button below."); + ui.vertical_centered(|ui| { + if widgets::primary(ui, icon::PLUS, "New connection").clicked() { + app.s.request(Request::NewProfile { protocol: None, folder: String::new() }); + } + }); + } +} + +fn header(app: &mut App, ui: &mut Ui, title: &str, key: &str, count: usize) -> bool { + let t = Tokens::get(ui.ctx()); + let folded = app.sidebar.collapsed.contains(key); + let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 22.0), Sense::click()); + let c = if resp.hovered() { t.text_dim } else { t.text_faint }; + let caret = if folded { icon::CARET_RIGHT } else { icon::CARET_DOWN }; + ui.painter().text(r.left_center() + vec2(10.0, 0.0), Align2::LEFT_CENTER, caret, egui::FontId::proportional(10.0), c); + ui.painter().text(r.left_center() + vec2(24.0, 0.0), Align2::LEFT_CENTER, title.to_uppercase(), theme::semibold(10.5), c); + ui.painter().text(r.right_center() - vec2(10.0, 0.0), Align2::RIGHT_CENTER, count.to_string(), egui::FontId::proportional(10.5), t.text_faint); + if resp.clicked() { + if folded { + app.sidebar.collapsed.remove(key); + } else { + app.sidebar.collapsed.insert(key.into()); + } + } + // Right-click a folder to add a connection to it. + if let Some(folder) = key.strip_prefix("group:") { + resp.context_menu(|ui| { + if ui.button(format!("{} New connection in {folder}", icon::PLUS)).clicked() { + app.s.request(Request::NewProfile { protocol: None, folder: folder.into() }); + ui.close(); + } + }); + } + !folded +} + +fn section(app: &mut App, ui: &mut Ui, title: &str, key: &str, list: &[&Profile]) { + ui.add_space(4.0); + if header(app, ui, title, key, list.len()) { + for p in list { + row(app, ui, p, key); + } + } +} + +fn local_row(app: &mut App, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 30.0), Sense::click()); + let r = r.shrink2(vec2(6.0, 1.0)); + if resp.hovered() { + ui.painter().rect_filled(r, 6.0, t.hover); + } + ui.painter().text(r.left_center() + vec2(10.0, 0.0), Align2::LEFT_CENTER, icon::TERMINAL_WINDOW, egui::FontId::proportional(15.0), crate::views::protocol_color(nexus_core::models::Protocol::Local)); + ui.painter().text(r.left_center() + vec2(32.0, 0.0), Align2::LEFT_CENTER, "Local Machine", egui::FontId::proportional(13.0), t.text); + if resp.on_hover_text("Open a terminal on this computer").clicked() { + app.connect_local(); + } +} + +fn row(app: &mut App, ui: &mut Ui, p: &Profile, section: &str) { + let t = Tokens::get(ui.ctx()); + let selected = app.sidebar.selected.contains(&p.id); + let (r, resp) = ui.allocate_exact_size(vec2(ui.available_width(), 34.0), Sense::click()); + let r = r.shrink2(vec2(6.0, 1.0)); + if selected { + ui.painter().rect_filled(r, 6.0, t.row_selected); + } else if resp.hovered() { + ui.painter().rect_filled(r, 6.0, t.hover); + } + let live = app.s.panes.values().any(|pn| pn.info.profile_id == p.id); + let dot = Rect::from_center_size(r.left_center() + vec2(12.0, 0.0), vec2(8.0, 8.0)); + let col = protocol_color(p.protocol); + if live { + ui.painter().circle_filled(dot.center(), 4.0, col); + } else { + ui.painter().circle_stroke(dot.center(), 3.5, egui::Stroke::new(1.5, col)); + } + let name = if p.display_name.is_empty() { &p.host } else { &p.display_name }; + let text_r = Rect::from_min_max(r.min + vec2(24.0, 0.0), r.max - vec2(46.0, 0.0)); + let painter = ui.painter().with_clip_rect(text_r); + painter.text(text_r.left_top() + vec2(0.0, 3.0), Align2::LEFT_TOP, name, egui::FontId::proportional(13.0), t.text); + let sub = if p.username.is_empty() || p.protocol == nexus_core::models::Protocol::Serial { p.host.clone() } else { format!("{}@{}", p.username, p.host) }; + painter.text(text_r.left_bottom() - vec2(0.0, 3.0), Align2::LEFT_BOTTOM, sub, egui::FontId::proportional(11.0), t.text_faint); + ui.painter().text(r.right_center() - vec2(8.0, 0.0), Align2::RIGHT_CENTER, protocol_badge(p.protocol), theme::semibold(9.0), col); + if p.favourite { + ui.painter().text(r.right_center() - vec2(36.0, 0.0), Align2::RIGHT_CENTER, "★", egui::FontId::proportional(11.0), t.warning); + } + let tip = format!("{}\n{} {}:{}{}", name, protocol_badge(p.protocol), p.host, p.port, if p.description.is_empty() { String::new() } else { format!("\n{}", p.description) }); + let resp = resp.on_hover_text(tip); + let multi = ui.input(|i| i.modifiers.command); + if resp.double_clicked() { + app.s.request(Request::EditProfile(p.id.clone())); + } else if resp.clicked() { + if multi { + if !app.sidebar.selected.remove(&p.id) { + app.sidebar.selected.insert(p.id.clone()); + } + } else { + app.sidebar.selected.clear(); + app.connect_profile(&p.id, false); + } + } + resp.context_menu(|ui| { + let _ = section; + if ui.button(format!("{} Connect", icon::PLUGS_CONNECTED)).clicked() { + app.connect_profile(&p.id, false); + ui.close(); + } + if ui.button(format!("{} Edit", icon::PENCIL_SIMPLE)).clicked() { + app.s.request(Request::EditProfile(p.id.clone())); + ui.close(); + } + if ui.button(format!("{} Duplicate", icon::COPY)).clicked() { + match app.s.svc.duplicate_profile(&p.id) { + Ok(d) => app.s.success(format!("Created {}", d.display_name)), + Err(e) => app.s.error(e.to_string()), + } + app.s.reload_profiles(); + ui.close(); + } + let fav = if p.favourite { "Remove from Favourites" } else { "Add to Favourites" }; + if ui.button(format!("{} {fav}", icon::STAR)).clicked() { + let _ = app.s.svc.set_favourite(&p.id, !p.favourite); + app.s.reload_profiles(); + ui.close(); + } + if ui.button(format!("{} Export…", icon::UPLOAD_SIMPLE)).clicked() { + app.s.request(Request::ExportEncrypted(vec![p.id.clone()])); + ui.close(); + } + ui.separator(); + if ui.button(RichText::new(format!("{} Delete", icon::TRASH)).color(t.danger)).clicked() { + app.dialog = Some(Dialog::Delete { profiles: vec![p.id.clone()] }); + ui.close(); + } + }); +} + +fn bulk_bar(app: &mut App, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + let ids: Vec = app.sidebar.selected.iter().cloned().collect(); + egui::Frame::NONE.fill(t.card).corner_radius(6).inner_margin(6).show(ui, |ui| { + ui.label(RichText::new(format!("{} selected", ids.len())).size(11.5).color(t.text_dim)); + ui.horizontal_wrapped(|ui| { + if ui.small_button(format!("{} Connect all", icon::PLUGS_CONNECTED)).clicked() { + for id in &ids { + app.connect_profile(id, true); + } + app.sidebar.selected.clear(); + } + if ui.small_button(format!("{} Disconnect", icon::PLUGS)).clicked() { + let sessions: Vec = app.s.panes.values().filter(|p| ids.contains(&p.info.profile_id)).map(|p| p.info.id.clone()).collect(); + if sessions.is_empty() { + app.s.info("None of the selected connections are open"); + } else if app.s.prefs.confirm_on_disconnect { + app.dialog = Some(Dialog::ConfirmDisconnectMany { sessions }); + } else { + for s in sessions { + app.close_session(&s); + } + } + } + ui.menu_button(format!("{} Tags", icon::TAG), |ui| { + if ui.button("Add tags…").clicked() { + app.dialog = Some(Dialog::Input { title: "Add tags".into(), label: "Comma-separated tags to add".into(), value: String::new(), then: InputThen::Tags { profiles: ids.clone(), replace: false } }); + ui.close(); + } + if ui.button("Replace tags…").clicked() { + app.dialog = Some(Dialog::Input { title: "Replace tags".into(), label: "Comma-separated tags".into(), value: String::new(), then: InputThen::Tags { profiles: ids.clone(), replace: true } }); + ui.close(); + } + }); + if ui.small_button(format!("{} Move group", icon::FOLDER)).clicked() { + app.dialog = Some(Dialog::Input { title: "Move to group".into(), label: "Group name (empty for Ungrouped)".into(), value: String::new(), then: InputThen::MoveGroup { profiles: ids.clone() } }); + } + if ui.small_button(format!("{} Delete", icon::TRASH)).clicked() { + app.dialog = Some(Dialog::Delete { profiles: ids.clone() }); + } + if ui.small_button("Clear").clicked() { + app.sidebar.selected.clear(); + } + }); + }); +} diff --git a/rust/app/src/snippets.rs b/rust/app/src/snippets.rs new file mode 100644 index 0000000..545ef6d --- /dev/null +++ b/rust/app/src/snippets.rs @@ -0,0 +1,26 @@ +//! Snippet library window and the {{variable}} prompt. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; + +#[derive(Default)] +pub struct Snippets { + open: bool, +} + +impl Snippets { + pub fn open(&mut self) { + self.open = true; + } + + pub fn is_open(&self) -> bool { + self.open + } + + /// Called every frame; draws its window(s) while open. + pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { + let mut open = self.open; + egui::Window::new("Snippets").open(&mut open).show(ctx, |ui| ui.label("todo")); + self.open = open; + } +} diff --git a/rust/app/src/splits.rs b/rust/app/src/splits.rs new file mode 100644 index 0000000..124d921 --- /dev/null +++ b/rust/app/src/splits.rs @@ -0,0 +1,319 @@ +//! Split panes: a binary tree of sessions inside one tab. A new split starts as a placeholder +//! pane offering Local Machine and the saved connections; ✕ on a pane promotes its sibling. + +use crate::app::App; +use crate::state::Shared; +use crate::theme::Tokens; +use crate::views::protocol_color; +use crate::widgets; +use egui::{Align2, Rect, RichText, Sense, Ui, pos2, vec2}; +use egui_phosphor::regular as icon; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Dir { + /// Side by side (Split Right). + Right, + /// Stacked (Split Down). + Down, +} + +/// A tab's layout. Leaf `None` is the placeholder pane showing the connection picker. +#[derive(Debug)] +pub enum Split { + Leaf(Option), + Node { dir: Dir, ratio: f32, a: Box, b: Box }, +} + +const MIN_PANE: f32 = 100.0; + +impl Split { + pub fn sessions(&self) -> Vec { + match self { + Split::Leaf(Some(s)) => vec![s.clone()], + Split::Leaf(None) => vec![], + Split::Node { a, b, .. } => { + let mut v = a.sessions(); + v.extend(b.sessions()); + v + } + } + } + + pub fn rename(&mut self, from: &str, to: &str) { + match self { + Split::Leaf(Some(s)) if s == from => *s = to.into(), + Split::Node { a, b, .. } => { + a.rename(from, to); + b.rename(from, to); + } + _ => {} + } + } + + fn at(&mut self, path: &[bool]) -> &mut Split { + match (self, path.split_first()) { + (Split::Node { a, b, .. }, Some((side, rest))) => if *side { b.at(rest) } else { a.at(rest) }, + (s, _) => s, + } + } + + /// Splits the leaf holding `session` (or the first placeholder when `session` is None). + fn split_leaf(&mut self, session: Option<&str>, dir: Dir) -> bool { + match self { + Split::Leaf(s) if s.as_deref() == session => { + let old = std::mem::replace(self, Split::Leaf(None)); + *self = Split::Node { dir, ratio: 0.5, a: Box::new(old), b: Box::new(Split::Leaf(None)) }; + true + } + Split::Node { a, b, .. } => a.split_leaf(session, dir) || b.split_leaf(session, dir), + _ => false, + } + } + + /// Removes the leaf at `path`, replacing its parent with the sibling. + fn remove_at(&mut self, path: &[bool]) { + let Some((last, parent)) = path.split_last() else { + *self = Split::Leaf(None); + return; + }; + let node = self.at(parent); + if let Split::Node { a, b, .. } = std::mem::replace(node, Split::Leaf(None)) { + *node = if *last { *a } else { *b }; + } + } + + fn find(&self, session: &str, path: &mut Vec) -> bool { + match self { + Split::Leaf(Some(s)) => s == session, + Split::Leaf(None) => false, + Split::Node { a, b, .. } => { + path.push(false); + if a.find(session, path) { + return true; + } + path.pop(); + path.push(true); + if b.find(session, path) { + return true; + } + path.pop(); + false + } + } + } + + fn leaves(&self, rect: Rect, path: &mut Vec, out: &mut Vec<(Vec, Option, Rect)>, dividers: &mut Vec<(Vec, Dir, Rect, Rect)>) { + match self { + Split::Leaf(s) => out.push((path.clone(), s.clone(), rect)), + Split::Node { dir, ratio, a, b } => { + let (ra, rb, div) = divide(rect, *dir, *ratio); + dividers.push((path.clone(), *dir, div, rect)); + path.push(false); + a.leaves(ra, path, out, dividers); + path.pop(); + path.push(true); + b.leaves(rb, path, out, dividers); + path.pop(); + } + } + } +} + +fn divide(r: Rect, dir: Dir, ratio: f32) -> (Rect, Rect, Rect) { + const GAP: f32 = 4.0; + match dir { + Dir::Right => { + let x = r.left() + (r.width() - GAP) * ratio; + (Rect::from_min_max(r.min, pos2(x, r.bottom())), Rect::from_min_max(pos2(x + GAP, r.top()), r.max), Rect::from_min_max(pos2(x, r.top()), pos2(x + GAP, r.bottom()))) + } + Dir::Down => { + let y = r.top() + (r.height() - GAP) * ratio; + (Rect::from_min_max(r.min, pos2(r.right(), y)), Rect::from_min_max(pos2(r.left(), y + GAP), r.max), Rect::from_min_max(pos2(r.left(), y), pos2(r.right(), y + GAP))) + } + } +} + +/// Splits the focused pane of the active tab. +pub fn split_focused(app: &mut App, dir: Dir) { + let Some(tab_id) = app.s.active.clone() else { return }; + let focused = app.s.focused_session().unwrap_or(tab_id.clone()); + let Some(tab) = app.s.tabs.iter_mut().find(|t| t.id == tab_id) else { return }; + match &mut tab.split { + None => tab.split = Some(Split::Node { dir, ratio: 0.5, a: Box::new(Split::Leaf(Some(tab_id.clone()))), b: Box::new(Split::Leaf(None)) }), + Some(sp) => { + if !sp.split_leaf(Some(&focused), dir) { + sp.split_leaf(Some(&tab_id), dir); + } + } + } +} + +/// Takes a split child out of its tab (after its session closed). The tab's own session can be +/// removed too: the next pane takes over the tab. +pub fn remove_session(s: &mut Shared, session: &str) { + for i in 0..s.tabs.len() { + let Some(sp) = &mut s.tabs[i].split else { continue }; + let mut path = vec![]; + if !sp.find(session, &mut path) { + continue; + } + sp.remove_at(&path); + let rest = sp.sessions(); + if let Split::Leaf(Some(only)) = sp { + let only = only.clone(); + s.tabs[i].split = None; + s.tabs[i].id = only; + } else if s.tabs[i].id == session { + match rest.first() { + Some(next) => s.tabs[i].id = next.clone(), + None => { + s.tabs.remove(i); + } + } + } + if s.active.as_deref() == Some(session) { + s.active = s.tabs.get(i.min(s.tabs.len().saturating_sub(1))).map(|t| t.id.clone()); + } + return; + } +} + +pub fn render(app: &mut App, ui: &mut Ui, tab_id: &str) { + let t = Tokens::get(ui.ctx()); + let area = ui.available_rect_before_wrap(); + let (mut leaves, mut dividers) = (vec![], vec![]); + if let Some(sp) = app.s.tab(tab_id).and_then(|tb| tb.split.as_ref()) { + sp.leaves(area, &mut vec![], &mut leaves, &mut dividers); + } + // Dividers: drag to resize, never squeezing a pane under MIN_PANE. + for (path, dir, div, total) in dividers { + let resp = ui.interact(div.expand(2.0), ui.id().with(("divider", &path)), Sense::drag()); + let c = if resp.hovered() || resp.dragged() { t.accent } else { t.separator }; + ui.painter().rect_filled(div, 0.0, c); + if resp.hovered() { + ui.ctx().set_cursor_icon(if dir == Dir::Right { egui::CursorIcon::ResizeHorizontal } else { egui::CursorIcon::ResizeVertical }); + } + if resp.dragged() + && let Some(p) = resp.interact_pointer_pos() + { + let (pos, start, len) = match dir { + Dir::Right => (p.x, total.left(), total.width()), + Dir::Down => (p.y, total.top(), total.height()), + }; + let min = (MIN_PANE / len).min(0.45); + let ratio = ((pos - start) / len).clamp(min, 1.0 - min); + if let Some(Split::Node { ratio: r, .. }) = app.s.tabs.iter_mut().find(|tb| tb.id == tab_id).and_then(|tb| tb.split.as_mut()).map(|sp| sp.at(&path)) { + *r = ratio; + } + } + } + let mut close: Option<(Vec, Option)> = None; + for (path, session, rect) in leaves { + let head = Rect::from_min_size(rect.min, vec2(rect.width(), 22.0)); + let body = Rect::from_min_max(pos2(rect.left(), head.bottom()), rect.max); + let focused = session.is_some() && app.s.focused == session; + ui.painter().rect_filled(head, 0.0, if focused { t.card } else { t.tab_strip }); + let title = session.as_deref().and_then(|s| app.s.pane(s)).map(|p| (p.info.tab_name.clone(), protocol_color(p.info.protocol))).unwrap_or(("New pane".into(), t.text_faint)); + ui.painter().circle_filled(head.left_center() + vec2(10.0, 0.0), 3.5, title.1); + ui.painter().with_clip_rect(head.shrink2(vec2(24.0, 0.0))).text(head.left_center() + vec2(20.0, 0.0), Align2::LEFT_CENTER, title.0, egui::FontId::proportional(11.5), if focused { t.text } else { t.text_dim }); + let xr = Rect::from_center_size(head.right_center() - vec2(12.0, 0.0), vec2(16.0, 16.0)); + if widgets::icon_button(&mut ui.new_child(egui::UiBuilder::new().max_rect(xr)), icon::X, "Close pane").clicked() { + close = Some((path.clone(), session.clone())); + } + match session { + Some(id) => { + if ui.interact(head, ui.id().with(("pane-head", &id)), Sense::click()).clicked() { + app.s.focused = Some(id.clone()); + } + crate::tabs::pane(app, ui, &id, body); + } + None => picker(app, ui, tab_id, &path, body), + } + } + if let Some((path, session)) = close { + match session { + Some(id) => app.close_session(&id), + None => { + if let Some(tb) = app.s.tabs.iter_mut().find(|tb| tb.id == tab_id) + && let Some(sp) = &mut tb.split + { + sp.remove_at(&path); + if let Split::Leaf(Some(_)) = sp { + tb.split = None; + } + } + } + } + } +} + +/// The placeholder pane: pick what to open here. +fn picker(app: &mut App, ui: &mut Ui, tab_id: &str, path: &[bool], rect: Rect) { + let t = Tokens::get(ui.ctx()); + ui.painter().rect_filled(rect, 0.0, t.canvas); + let mut child = ui.new_child(egui::UiBuilder::new().max_rect(rect.shrink(16.0))); + let mut chosen: Option> = None; // Some(None) = local + child.vertical_centered(|ui| { + ui.label(RichText::new("Open in this pane").font(crate::theme::semibold(14.0))); + ui.add_space(6.0); + egui::ScrollArea::vertical().max_height(rect.height() - 60.0).show(ui, |ui| { + if ui.button(format!("{} Local Machine", icon::TERMINAL_WINDOW)).clicked() { + chosen = Some(None); + } + for p in &app.s.profiles { + if !p.protocol.is_terminal() { + continue; + } + let name = if p.display_name.is_empty() { &p.host } else { &p.display_name }; + if ui.button(RichText::new(format!("● {name}")).color(t.text)).clicked() { + chosen = Some(Some(p.id.clone())); + } + } + }); + }); + let Some(choice) = chosen else { return }; + let sid = match choice { + None => app.s.svc.connect_local(), + Some(pid) => match app.s.svc.connect_profile(&pid) { + Ok(s) => s, + Err(e) => { + app.s.error(format!("Connection failed: {e}")); + return; + } + }, + }; + // The pane may have been closed meanwhile; then drop the new session. + let placed = app.s.tabs.iter_mut().find(|tb| tb.id == tab_id).and_then(|tb| tb.split.as_mut()).map(|sp| { + let leaf = sp.at(path); + if matches!(leaf, Split::Leaf(None)) { + *leaf = Split::Leaf(Some(sid.clone())); + true + } else { + false + } + }); + if placed == Some(true) { + app.add_session(&sid, false); + app.s.focused = Some(sid); + } else { + let _ = app.s.svc.sessions.disconnect(&sid); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn split_and_collapse() { + let mut sp = Split::Node { dir: Dir::Right, ratio: 0.5, a: Box::new(Split::Leaf(Some("a".into()))), b: Box::new(Split::Leaf(None)) }; + assert!(sp.split_leaf(Some("a"), Dir::Down)); + assert_eq!(sp.sessions(), vec!["a".to_string()]); + let mut path = vec![]; + assert!(sp.find("a", &mut path)); + assert_eq!(path, vec![false, false]); + sp.remove_at(&path); + assert!(sp.sessions().is_empty()); + sp.rename("a", "b"); + } +} diff --git a/rust/app/src/ssh_keys.rs b/rust/app/src/ssh_keys.rs new file mode 100644 index 0000000..c5c62e8 --- /dev/null +++ b/rust/app/src/ssh_keys.rs @@ -0,0 +1,26 @@ +//! SSH Key Manager window. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; + +#[derive(Default)] +pub struct SshKeys { + open: bool, +} + +impl SshKeys { + pub fn open(&mut self) { + self.open = true; + } + + pub fn is_open(&self) -> bool { + self.open + } + + /// Called every frame; draws its window(s) while open. + pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { + let mut open = self.open; + egui::Window::new("SshKeys").open(&mut open).show(ctx, |ui| ui.label("todo")); + self.open = open; + } +} diff --git a/rust/app/src/state.rs b/rust/app/src/state.rs new file mode 100644 index 0000000..21af468 --- /dev/null +++ b/rust/app/src/state.rs @@ -0,0 +1,216 @@ +//! State shared by the main window and every feature module: the service, cached profiles and +//! preferences, open tabs, toasts, and a request queue features use to ask the app to act. + +use crate::views::View; +use nexus_core::models::{AppPreferences, Profile, Protocol, SessionInfo, SessionState}; +use nexus_core::service::Service; +use std::collections::HashSet; +use std::sync::Arc; + +/// Things a feature asks the main window to do (handled once per frame, in order). +pub enum Request { + /// Open the profile editor for a new profile (optionally pre-set protocol / folder). + NewProfile { protocol: Option, folder: String }, + EditProfile(String), + Connect(String), + ConnectQuick(Profile), + ConnectLocal, + /// Reload `Shared::profiles` from the database. + ReloadProfiles, + /// Re-read and apply preferences (theme, terminal theme, fonts…). + ReloadPreferences, + QuickConnect, + Settings(SettingsTab), + SshKeys, + ExportEncrypted(Vec), + ImportEncrypted, + ExportJson, + ImportJson, + Recordings, + Snippets, + Triggers, + Sftp(String), + PortForwards(String), + /// Type text into the active session (snippets). + SendToActive(String), + /// Bytes to a specific session. + Write(String, Vec), + Disconnect(String), + CommandPalette, + About, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum SettingsTab { + #[default] + General, + Terminal, + Security, + KnownHosts, + Help, + About, +} + +pub struct Toast { + pub text: String, + pub kind: ToastKind, + pub until: f64, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum ToastKind { + Info, + Success, + Warning, + Error, +} + +/// A session on screen (a tab's own session or a split pane) and how it's drawn. +pub struct Pane { + pub info: SessionInfo, + pub view: View, + pub recording: bool, + /// Local shell exited: Enter restarts it. + pub local_exited: bool, + /// Wall-clock time the session last connected. + pub connected_since: Option>, + /// Bumped to make trigger highlights flash. + pub highlight_until: f64, +} + +/// A tab. Its session is `id`; `split` lays out extra panes (which have no tab of their own). +pub struct Tab { + pub id: String, + /// Output arrived while the tab wasn't active. + pub activity: bool, + pub split: Option, +} + +pub struct Shared { + pub svc: Arc, + pub profiles: Vec, + pub prefs: AppPreferences, + pub tabs: Vec, + /// Every on-screen session by id (tabs and split panes). + pub panes: std::collections::HashMap, + /// Session id of the active tab. + pub active: Option, + /// Session id of the focused pane inside the active tab (the tab itself or a split child). + pub focused: Option, + pub toasts: Vec, + pub requests: Vec, + pub broadcast: bool, + /// None = every connected session. + pub broadcast_targets: Option>, + pub now: f64, + pub ctx: egui::Context, +} + +impl Shared { + pub fn new(svc: Arc, ctx: egui::Context) -> Shared { + let prefs = svc.preferences(); + let profiles = svc.profiles(); + Shared { + svc, + profiles, + prefs, + tabs: vec![], + panes: Default::default(), + active: None, + focused: None, + toasts: vec![], + requests: vec![], + broadcast: false, + broadcast_targets: None, + now: 0.0, + ctx, + } + } + + pub fn request(&mut self, r: Request) { + self.requests.push(r); + self.ctx.request_repaint(); + } + + fn push(&mut self, text: impl Into, kind: ToastKind) { + let secs = if kind == ToastKind::Error { 6.0 } else { 4.0 }; + self.toasts.push(Toast { text: text.into(), kind, until: self.now + secs }); + self.ctx.request_repaint(); + } + + pub fn info(&mut self, text: impl Into) { + self.push(text, ToastKind::Info); + } + + pub fn success(&mut self, text: impl Into) { + self.push(text, ToastKind::Success); + } + + pub fn warn(&mut self, text: impl Into) { + self.push(text, ToastKind::Warning); + } + + pub fn error(&mut self, text: impl Into) { + self.push(text, ToastKind::Error); + } + + pub fn profile(&self, id: &str) -> Option<&Profile> { + self.profiles.iter().find(|p| p.id == id) + } + + pub fn reload_profiles(&mut self) { + self.profiles = self.svc.profiles(); + } + + pub fn tab(&self, id: &str) -> Option<&Tab> { + self.tabs.iter().find(|t| t.id == id) + } + + pub fn pane(&self, id: &str) -> Option<&Pane> { + self.panes.get(id) + } + + pub fn pane_mut(&mut self, id: &str) -> Option<&mut Pane> { + self.panes.get_mut(id) + } + + /// The tab that shows `session` (its own, or the one it's split into). + pub fn tab_of(&self, session: &str) -> Option<&Tab> { + self.tabs.iter().find(|t| t.id == session || t.split.as_ref().is_some_and(|sp| sp.sessions().iter().any(|x| x == session))) + } + + /// The session keyboard actions apply to: the focused pane, else the active tab. + pub fn focused_session(&self) -> Option { + self.focused.clone().or_else(|| self.active.clone()) + } + + /// Every session id on screen (tabs and split panes). + pub fn all_sessions(&self) -> Vec { + self.panes.keys().cloned().collect() + } + + pub fn session_state(&self, id: &str) -> Option { + self.svc.sessions.info(id).map(|i| i.state) + } + + /// Runs `f` on a worker thread and toasts its error, if any (for slow service calls). + pub fn spawn(&self, f: impl FnOnce() -> nexus_core::Result + Send + 'static, done: impl FnOnce(T) + Send + 'static) { + let ctx = self.ctx.clone(); + std::thread::spawn(move || { + match f() { + Ok(v) => done(v), + Err(e) => crate::app::post_error(e.to_string()), + } + ctx.request_repaint(); + }); + } +} + +/// Formats a command-key shortcut for menus: "⌘K" on macOS, "Ctrl+K" elsewhere. +pub fn keys(s: &str) -> String { + if cfg!(target_os = "macos") { + s.replace("Mod+Shift+", "⇧⌘").replace("Mod+", "⌘") + } else { + s.replace("Mod+", "Ctrl+") + } +} diff --git a/rust/app/src/tabs.rs b/rust/app/src/tabs.rs new file mode 100644 index 0000000..c714461 --- /dev/null +++ b/rust/app/src/tabs.rs @@ -0,0 +1,448 @@ +//! The tab strip and the workspace under it: the active tab's session (or its split panes), +//! terminal find bar and connection overlays. + +use crate::app::{App, Dialog}; +use crate::chrome::{state_color, state_label}; +use crate::state::{Request, Shared}; +use crate::terminal::TermResponse; +use crate::theme::{self, Tokens}; +use crate::views::{View, protocol_badge, protocol_color}; +use crate::widgets; +use egui::{Align2, CornerRadius, Key, Rect, RichText, Sense, Stroke, Ui, pos2, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::models::{Protocol, SessionState}; + +#[derive(Default)] +pub struct TabsUi { + pub find_focus: bool, + drag: Option, +} + +/// Tab ids in display order: pinned first, then the rest, each in insertion order. +pub fn ordered(s: &Shared) -> Vec { + let pinned = |id: &str| s.pane(id).is_some_and(|p| p.info.pinned); + let mut v: Vec = s.tabs.iter().filter(|t| pinned(&t.id)).map(|t| t.id.clone()).collect(); + v.extend(s.tabs.iter().filter(|t| !pinned(&t.id)).map(|t| t.id.clone())); + v +} + +pub fn workspace(app: &mut App, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + egui::Panel::top("tabstrip").exact_size(32.0).frame(egui::Frame::NONE.fill(t.dock)).show(ui, |ui| strip(app, ui)); + egui::CentralPanel::default().frame(egui::Frame::NONE.fill(t.canvas)).show(ui, |ui| { + let Some(active) = app.s.active.clone().filter(|a| app.s.tab(a).is_some()) else { + empty_state(app, ui); + return; + }; + let has_split = app.s.tab(&active).is_some_and(|tb| tb.split.is_some()); + if has_split { + crate::splits::render(app, ui, &active); + } else { + let rect = ui.available_rect_before_wrap(); + pane(app, ui, &active, rect); + } + }); +} + +fn empty_state(app: &mut App, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + let r = ui.max_rect(); + ui.scope_builder(egui::UiBuilder::new().max_rect(Rect::from_center_size(r.center(), vec2(420.0, 420.0))), |ui| { + ui.vertical_centered(|ui| { + ui.add(egui::Image::new(&crate::chrome::logo(ui.ctx())).fit_to_exact_size(vec2(84.0, 84.0))); + ui.label(RichText::new("Nexus Terminal").font(theme::semibold(28.0))); + ui.label(RichText::new("SSH · Telnet · RAW · Serial · VNC · IRC · GitHub").color(t.text_dim)); + ui.add_space(18.0); + ui.horizontal(|ui| { + ui.add_space((ui.available_width() - 330.0).max(0.0) / 2.0); + if widgets::primary(ui, icon::PLUS, "New Connection").clicked() { + app.s.request(Request::NewProfile { protocol: None, folder: String::new() }); + } + if widgets::button(ui, icon::LIGHTNING, "Quick Connect").clicked() { + app.quick.open(); + } + if widgets::button(ui, icon::TERMINAL_WINDOW, "Local").clicked() { + app.connect_local(); + } + }); + ui.add_space(16.0); + let keys = [("Mod+K", "command palette"), ("Mod+N", "new connection"), ("Mod+Shift+Q", "quick connect"), ("Mod+,", "settings")]; + egui::Grid::new("welcome-keys").num_columns(2).spacing(vec2(10.0, 6.0)).show(ui, |ui| { + for (k, v) in keys { + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| widgets::chip(ui, &crate::state::keys(k), t.text_dim)); + ui.label(RichText::new(v).color(t.text_faint)); + ui.end_row(); + } + }); + }); + }); +} + +fn strip(app: &mut App, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + let order = ordered(&app.s); + let mut close = None; + let mut activate = None; + let mut menu_action: Option<(String, &'static str)> = None; + let full = ui.max_rect(); + let right_w = 64.0; + let strip_rect = Rect::from_min_max(full.min, pos2(full.right() - right_w, full.bottom())); + let mut child = ui.new_child(egui::UiBuilder::new().max_rect(strip_rect)); + egui::ScrollArea::horizontal().id_salt("tabs-scroll").scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden).show(&mut child, |ui| { + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 1.0; + ui.add_space(4.0); + for id in &order { + let Some(p) = app.s.pane(id) else { continue }; + let p_info = p.info.clone(); + let (p_recording, p_since) = (p.recording, p.connected_since); + let p = PaneInfo { info: p_info, recording: p_recording, connected_since: p_since }; + let tab_activity = app.s.tab(id).is_some_and(|tb| tb.activity); + let active = app.s.active.as_deref() == Some(id.as_str()); + let state = app.s.session_state(id).unwrap_or(p.info.state); + let label = p.info.tab_name.clone(); + let font = egui::FontId::proportional(12.5); + let galley = ui.painter().layout_no_wrap(label.clone(), font, if active { t.text } else { t.text_dim }); + let badge = ui.painter().layout_no_wrap(protocol_badge(p.info.protocol).into(), theme::semibold(9.0), protocol_color(p.info.protocol)); + let sftp = p.info.protocol == Protocol::Ssh; + let extra = if p.info.pinned { 16.0 } else { 0.0 } + if sftp { 20.0 } else { 0.0 }; + let w = (galley.size().x + badge.size().x + 66.0 + extra).min(280.0); + let (r, resp) = ui.allocate_exact_size(vec2(w, 30.0), Sense::click_and_drag()); + let body = Rect::from_min_max(r.min + vec2(0.0, 3.0), r.max); + if active { + ui.painter().rect_filled(body, CornerRadius { nw: 6, ne: 6, sw: 0, se: 0 }, t.canvas); + ui.painter().hline(body.x_range().shrink(6.0), body.top() + 0.5, Stroke::new(2.0, protocol_color(p.info.protocol))); + if active && resp.rect.min.x < strip_rect.min.x || resp.rect.max.x > strip_rect.max.x { + ui.scroll_to_rect(r, None); + } + } else if resp.hovered() { + ui.painter().rect_filled(body, CornerRadius { nw: 6, ne: 6, sw: 0, se: 0 }, t.hover); + } + let mut x = body.left() + 9.0; + ui.painter().circle_filled(pos2(x + 3.0, body.center().y), 3.5, state_color(&t, state)); + x += 12.0; + let br = Rect::from_min_size(pos2(x, body.center().y - badge.size().y / 2.0 - 2.0), badge.size() + vec2(8.0, 4.0)); + ui.painter().rect_filled(br, 4.0, protocol_color(p.info.protocol).gamma_multiply(0.16)); + ui.painter().galley(br.min + vec2(4.0, 2.0), badge, t.text); + x = br.right() + 7.0; + if p.info.pinned { + ui.painter().text(pos2(x, body.center().y), Align2::LEFT_CENTER, icon::PUSH_PIN, egui::FontId::proportional(11.0), t.text_faint); + x += 16.0; + } + let right_controls = 24.0 + if sftp { 20.0 } else { 0.0 }; + let gr = Rect::from_min_max(pos2(x, body.top()), pos2(body.right() - right_controls, body.bottom())); + ui.painter().with_clip_rect(gr).galley(pos2(x, body.center().y - galley.size().y / 2.0), galley, t.text); + if tab_activity && !active { + ui.painter().circle_filled(pos2(body.right() - right_controls - 2.0, body.top() + 7.0), 3.0, t.accent); + } + if p.recording { + ui.painter().circle_filled(pos2(body.left() + 4.0, body.top() + 6.0), 3.0, t.danger); + } + if sftp { + let fr = Rect::from_center_size(pos2(body.right() - 33.0, body.center().y), vec2(16.0, 16.0)); + let fresp = ui.interact(fr, ui.id().with(("sftp", id)), Sense::click()); + let c = if app.sftp.open_for() == Some(id.as_str()) { t.accent } else if fresp.hovered() { t.text } else { t.text_faint }; + ui.painter().text(fr.center(), Align2::CENTER_CENTER, icon::FOLDER_SIMPLE, egui::FontId::proportional(12.0), c); + if fresp.on_hover_text("SFTP browser").clicked() { + app.s.request(Request::Sftp(id.clone())); + } + } + let xr = Rect::from_center_size(pos2(body.right() - 13.0, body.center().y), vec2(16.0, 16.0)); + let xresp = ui.interact(xr, ui.id().with(("close", id)), Sense::click()); + if active || resp.hovered() || xresp.hovered() { + if xresp.hovered() { + ui.painter().rect_filled(xr, 3.0, t.hover); + } + ui.painter().text(xr.center(), Align2::CENTER_CENTER, icon::X, egui::FontId::proportional(11.0), t.text_dim); + } + // Drag to reorder. + if resp.drag_started() { + app.tabs_ui.drag = Some(id.clone()); + } + if let Some(d) = app.tabs_ui.drag.clone() + && d != *id + && resp.contains_pointer() + && ui.input(|i| i.pointer.primary_down()) + { + let from = app.s.tabs.iter().position(|x| x.id == d); + let to = app.s.tabs.iter().position(|x| x.id == *id); + if let (Some(f), Some(to)) = (from, to) { + let tb = app.s.tabs.remove(f); + app.s.tabs.insert(to, tb); + } + } + if xresp.clicked() { + close = Some(id.clone()); + } else if resp.clicked() { + activate = Some(id.clone()); + } + let since = p.connected_since.map(|t| format!("\nConnected since {}", t.format("%H:%M:%S"))).unwrap_or_default(); + let tip = format!("{} — {}\n{}{since}", p.info.tab_name, p.info.host, state_label(state)); + let resp = resp.on_hover_text(tip); + resp.context_menu(|ui| { + for (label, a, glyph) in [ + ("Rename…", "rename", icon::PENCIL_SIMPLE), + ("Duplicate", "duplicate", icon::COPY), + (if p.info.pinned { "Unpin" } else { "Pin" }, "pin", icon::PUSH_PIN), + ("Move Left", "left", icon::ARROW_LEFT), + ("Move Right", "right", icon::ARROW_RIGHT), + ("Reconnect", "reconnect", icon::ARROW_CLOCKWISE), + ("Close", "close", icon::X), + ] { + if matches!(a, "left" | "reconnect" | "close") { + ui.separator(); + } + if ui.button(format!("{glyph} {label}")).clicked() { + menu_action = Some((id.clone(), a)); + ui.close(); + } + } + }); + } + if !ui.input(|i| i.pointer.primary_down()) { + app.tabs_ui.drag = None; + } + }); + }); + // Right cluster: new tab and broadcast. + let rr = Rect::from_min_max(pos2(full.right() - right_w, full.top()), full.max); + let mut right = ui.new_child(egui::UiBuilder::new().max_rect(rr).layout(egui::Layout::left_to_right(egui::Align::Center))); + right.add_space(6.0); + if widgets::icon_button(&mut right, icon::PLUS, "New connection").clicked() { + app.s.request(Request::NewProfile { protocol: None, folder: String::new() }); + } + let b = widgets::icon_button(&mut right, icon::BROADCAST, if app.s.broadcast { "Broadcast ON — click to stop, right-click for targets" } else { "Broadcast input (right-click to pick targets)" }); + if app.s.broadcast { + right.painter().rect_stroke(b.rect, 4.0, Stroke::new(1.5, t.warning), egui::StrokeKind::Inside); + } + if b.clicked() { + app.toggle_broadcast(); + } + if b.secondary_clicked() { + app.pick_broadcast_targets(); + } + + if let Some(id) = activate { + app.activate(&id); + } + if let Some(id) = close { + app.request_close(&id); + } + if let Some((id, a)) = menu_action { + tab_action(app, &id, a); + } +} + +struct PaneInfo { + info: nexus_core::models::SessionInfo, + recording: bool, + connected_since: Option>, +} + +fn tab_action(app: &mut App, id: &str, a: &str) { + match a { + "rename" => { + let name = app.s.pane(id).map(|p| p.info.tab_name.clone()).unwrap_or_default(); + app.dialog = Some(Dialog::Rename { session: id.into(), name }); + } + "duplicate" => { + let Some(p) = app.s.pane(id) else { return }; + if p.info.protocol == Protocol::Local { + app.connect_local(); + } else if let Some(prof) = app.s.svc.sessions.profile(id) { + if app.s.profile(&prof.id).is_some() { + let pid = prof.id.clone(); + app.connect_profile(&pid, true); + } else { + app.s.request(Request::ConnectQuick(prof)); + } + } + } + "pin" => { + if let Some(p) = app.s.pane_mut(id) { + p.info.pinned = !p.info.pinned; + let pinned = p.info.pinned; + let _ = app.s.svc.sessions.pin(id, pinned); + } + } + "left" | "right" => { + if let Some(i) = app.s.tabs.iter().position(|t| t.id == id) { + let j = if a == "left" { i.checked_sub(1) } else { Some(i + 1).filter(|j| *j < app.s.tabs.len()) }; + if let Some(j) = j { + app.s.tabs.swap(i, j); + } + } + } + "reconnect" => app.reconnect(id), + "close" => app.request_close(id), + _ => {} + } +} + +/// Draws one session into `rect`. +pub fn pane(app: &mut App, ui: &mut Ui, id: &str, rect: Rect) { + let Some(mut pane) = app.s.panes.remove(id) else { return }; + let t = Tokens::get(ui.ctx()); + let theme_id = app.s.prefs.terminal_theme.clone(); + let focused_tab = app.s.focused.as_deref() == Some(id) || (app.s.focused.is_none() && app.s.active.as_deref() == Some(id)); + let mut child = ui.new_child(egui::UiBuilder::new().max_rect(rect).id_salt(("pane", id))); + child.set_clip_rect(rect.intersect(ui.clip_rect())); + let ui = &mut child; + let mut response = None; + match &mut pane.view { + View::Terminal(term) => { + if term.find.is_some() { + find_bar(app, ui, term); + } + let theme = crate::terminal_themes::get(&theme_id); + let r = term.ui(ui, theme, focused_tab); + if pane.highlight_until > app.s.now { + ui.painter().rect_stroke(rect.shrink(1.0), 0.0, Stroke::new(2.0, t.warning), egui::StrokeKind::Inside); + ui.ctx().request_repaint_after(std::time::Duration::from_millis(100)); + } + response = Some(r); + } + View::Raw(v) => v.ui(ui, &mut app.s), + View::Irc(v) => v.ui(ui, &mut app.s), + View::Github(v) => v.ui(ui, &mut app.s), + View::Vnc(v) => v.ui(ui, &mut app.s), + } + let state = app.s.session_state(id).unwrap_or(pane.info.state); + let local_exited = pane.local_exited; + let error = pane.info.error.clone(); + let proto = pane.info.protocol; + app.s.panes.insert(id.into(), pane); + if let Some(r) = response { + terminal_response(app, id, r, local_exited); + } + overlay(app, ui, id, rect, state, &error, local_exited, proto); +} + +fn terminal_response(app: &mut App, id: &str, r: TermResponse, local_exited: bool) { + if r.focused && app.s.focused.as_deref() != Some(id) { + app.s.focused = Some(id.into()); + } + if let Some((c, rows)) = r.resized { + let _ = app.s.svc.sessions.resize(id, c, rows); + } + if !r.reply.is_empty() { + let _ = app.s.svc.sessions.write(id, &r.reply); + } + if local_exited { + if r.input.iter().any(|b| *b == b'\r' || *b == b'\n') { + let size = match app.s.pane(id).map(|p| &p.view) { + Some(View::Terminal(t)) => t.size(), + _ => (80, 24), + }; + if let Err(e) = app.s.svc.sessions.restart_local(id, size.0, size.1) { + app.s.error(e.to_string()); + } else if let Some(p) = app.s.pane_mut(id) { + p.local_exited = false; + } + } + } else if !r.input.is_empty() { + app.send_input(id, &r.input); + } + if r.bell { + app.s.ctx.request_repaint(); + } + if let Some(a) = r.menu { + app.terminal_menu(id, a); + } +} + +#[allow(clippy::too_many_arguments)] +fn overlay(app: &mut App, ui: &mut Ui, id: &str, rect: Rect, state: SessionState, error: &str, local_exited: bool, proto: Protocol) { + let t = Tokens::get(ui.ctx()); + let show = match state { + SessionState::Disconnected => !local_exited && proto != Protocol::Local, + SessionState::Error => true, + SessionState::Reconnecting => true, + SessionState::Connecting => proto != Protocol::Local && !matches!(app.s.pane(id).map(|p| &p.view), Some(View::Terminal(_))), + SessionState::Connected => false, + }; + if !show { + return; + } + let card = Rect::from_center_size(rect.center(), vec2(360.0_f32.min(rect.width() - 20.0), 150.0)); + ui.painter().rect_filled(rect, 0.0, t.canvas.gamma_multiply(0.55)); + let mut c = ui.new_child(egui::UiBuilder::new().max_rect(card)); + egui::Frame::popup(c.style()).corner_radius(10).inner_margin(14).show(&mut c, |ui| { + ui.set_width(card.width() - 28.0); + ui.vertical_centered(|ui| { + let (g, title, col) = match state { + SessionState::Reconnecting => (icon::ARROW_CLOCKWISE, "Reconnecting…", t.warning), + SessionState::Connecting => (icon::PLUGS, "Connecting…", t.warning), + SessionState::Error => (icon::WARNING_CIRCLE, "Connection error", t.danger), + _ => (icon::PLUGS, "Disconnected", t.text_dim), + }; + ui.label(RichText::new(format!("{g} {title}")).font(theme::semibold(15.0)).color(col)); + if !error.is_empty() && state != SessionState::Reconnecting { + ui.add(egui::Label::new(RichText::new(error).color(t.text_dim).size(12.0)).wrap()); + } + ui.add_space(6.0); + ui.horizontal(|ui| { + ui.add_space((ui.available_width() - 200.0).max(0.0) / 2.0); + if state == SessionState::Reconnecting { + if ui.button("Cancel").clicked() { + app.s.svc.sessions.cancel_reconnect(id); + } + } else if state != SessionState::Connecting { + if widgets::primary(ui, icon::ARROW_CLOCKWISE, "Reconnect").clicked() { + app.reconnect(id); + } + if ui.button("Close").clicked() { + app.close_session(id); + } + } + }); + }); + }); +} + +fn find_bar(app: &mut App, ui: &mut Ui, term: &mut crate::terminal::TermView) { + let t = Tokens::get(ui.ctx()); + let (cur, total) = term.find_counts(); + let mut close = false; + let mut step = None; + egui::Frame::NONE.fill(t.card).inner_margin(egui::Margin::symmetric(8, 4)).show(ui, |ui| { + ui.horizontal(|ui| { + let f = term.find.as_mut().unwrap(); + ui.label(RichText::new(icon::MAGNIFYING_GLASS).color(t.text_faint)); + let r = ui.add(egui::TextEdit::singleline(&mut f.query).hint_text("Find").desired_width(220.0)); + if app.tabs_ui.find_focus { + r.request_focus(); + app.tabs_ui.find_focus = false; + } + if r.lost_focus() && ui.input(|i| i.key_pressed(Key::Enter)) { + step = Some(!ui.input(|i| i.modifiers.shift)); + r.request_focus(); + } + if r.has_focus() && ui.input(|i| i.key_pressed(Key::Escape)) { + close = true; + } + ui.toggle_value(&mut f.case_sensitive, "Aa").on_hover_text("Match case"); + ui.toggle_value(&mut f.regex, ".*").on_hover_text("Regular expression"); + match &f.error { + Some(e) => ui.label(RichText::new(e.lines().last().unwrap_or("bad regex")).color(t.danger).size(11.0)), + None => ui.label(RichText::new(if f.query.is_empty() { String::new() } else { format!("{cur} of {total}") }).size(11.5).color(t.text_dim)), + }; + if widgets::icon_button(ui, icon::ARROW_UP, "Previous (Shift+Enter)").clicked() { + step = Some(false); + } + if widgets::icon_button(ui, icon::ARROW_DOWN, "Next (Enter)").clicked() { + step = Some(true); + } + if widgets::icon_button(ui, icon::X, "Close (Esc)").clicked() { + close = true; + } + }); + }); + if let Some(fwd) = step { + term.find_step(fwd); + } + if close { + term.find = None; + term.request_focus(); + } +} diff --git a/rust/app/src/terminal.rs b/rust/app/src/terminal.rs index 502c4f7..fd92e3b 100644 --- a/rust/app/src/terminal.rs +++ b/rust/app/src/terminal.rs @@ -11,7 +11,7 @@ use alacritty_terminal::term::cell::Flags; use alacritty_terminal::term::test::TermSize; use alacritty_terminal::term::{Config, Term, TermMode}; use alacritty_terminal::vte::ansi::{self, Color, CursorShape, NamedColor, Processor, StdSyncHandler}; -use egui::{Color32, Event, FontId, Key, Modifiers, PointerButton, Pos2, Rect, Sense, Stroke, Ui, Vec2, pos2, vec2}; +use egui::{Color32, Event, FontId, Key, Modifiers, PointerButton, Pos2, Rect, Sense, Stroke, Ui, pos2, vec2}; use std::sync::{Arc, Mutex}; #[derive(Clone, Default)] @@ -290,7 +290,7 @@ impl TermView { self.keyboard(ui, &mut out); let cur = self.term.grid().cursor.point; let cursor_rect = Rect::from_min_size(origin + vec2(cur.column.0 as f32 * cw, cur.line.0 as f32 * ch), vec2(cw, ch)); - ui.output_mut(|o| o.ime = Some(egui::output::IMEOutput { rect, cursor_rect })); + ui.output_mut(|o| o.ime = Some(egui::output::IMEOutput { purpose: egui::IMEPurpose::Terminal, rect, cursor_rect, should_interrupt_composition: false })); } self.mouse(ui, &resp, origin, cw, ch, &mut out); @@ -429,7 +429,7 @@ impl TermView { }; for e in &events { match e { - Event::MouseWheel { delta, unit, modifiers } if hover.is_some() => { + Event::MouseWheel { delta, unit, modifiers, .. } if hover.is_some() => { if modifiers.command { continue; // left for zoom } @@ -669,8 +669,12 @@ impl TermView { painter.text(r.left_top(), egui::Align2::LEFT_TOP, cell.c, font.clone(), rgb(theme.cursor_accent)); } } - CursorStyle::Underline => painter.rect_filled(Rect::from_min_max(pos2(r.left(), r.bottom() - 2.0), r.max), 0.0, c), - CursorStyle::Bar => painter.rect_filled(Rect::from_min_size(r.min, vec2(2.0, ch)), 0.0, c), + CursorStyle::Underline => { + painter.rect_filled(Rect::from_min_max(pos2(r.left(), r.bottom() - 2.0), r.max), 0.0, c); + } + CursorStyle::Bar => { + painter.rect_filled(Rect::from_min_size(r.min, vec2(2.0, ch)), 0.0, c); + } } } } diff --git a/rust/app/src/transfer.rs b/rust/app/src/transfer.rs new file mode 100644 index 0000000..f61a671 --- /dev/null +++ b/rust/app/src/transfer.rs @@ -0,0 +1,16 @@ +//! Profile export/import: encrypted `.nexus` (also reads `.link`) and plain JSON. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; + +#[derive(Default)] +pub struct Transfer {} + +impl Transfer { + /// Encrypted export of these profiles (all when empty): pick profiles, password, file. + pub fn export_encrypted(&mut self, _ids: Vec) {} + pub fn import_encrypted(&mut self) {} + pub fn export_json(&mut self) {} + pub fn import_json(&mut self) {} + pub fn ui(&mut self, _ctx: &egui::Context, _s: &mut Shared) {} +} diff --git a/rust/app/src/triggers.rs b/rust/app/src/triggers.rs new file mode 100644 index 0000000..31faa7e --- /dev/null +++ b/rust/app/src/triggers.rs @@ -0,0 +1,25 @@ +//! Trigger rules: match terminal output lines and notify / highlight / beep. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; + +#[derive(Default)] +pub struct Triggers { + open: bool, +} + +impl Triggers { + pub fn open(&mut self) { + self.open = true; + } + + /// Feeds session output; fires matching rules (toasts, highlight request, bell). + /// Returns true if the tab should be highlighted. + pub fn check(&mut self, _s: &mut Shared, _session: &str, _data: &[u8]) -> bool { + false + } + + pub fn cleanup(&mut self, _session: &str) {} + + pub fn ui(&mut self, _ctx: &egui::Context, _s: &mut Shared) {} +} diff --git a/rust/app/src/views.rs b/rust/app/src/views.rs new file mode 100644 index 0000000..57c46b4 --- /dev/null +++ b/rust/app/src/views.rs @@ -0,0 +1,55 @@ +//! How a session is drawn. Terminal-style protocols share `TermView`; the rest have their own. + +use crate::github_view::GithubView; +use crate::irc_view::IrcView; +use crate::raw_view::RawView; +use crate::terminal::TermView; +use crate::vnc_view::VncView; + +pub enum View { + Terminal(Box), + Raw(Box), + Irc(Box), + Github(Box), + Vnc(Box), +} + +impl View { + pub fn terminal(&mut self) -> Option<&mut TermView> { + match self { + View::Terminal(t) => Some(t), + _ => None, + } + } +} + +/// Protocol badge colour (the web build's --protocol-* tokens). +pub fn protocol_color(p: nexus_core::models::Protocol) -> egui::Color32 { + use nexus_core::models::Protocol::*; + let rgb = egui::Color32::from_rgb; + match p { + Ssh => rgb(0x4e, 0xcd, 0xc4), + Telnet => rgb(0xff, 0xa7, 0x26), + Raw => rgb(0xab, 0x47, 0xbc), + Github => rgb(0x7c, 0x8a, 0xff), + Vnc => rgb(0xe8, 0x5d, 0x4a), + Irc => rgb(0xd8, 0xb2, 0x4c), + Local => rgb(0x8b, 0xc3, 0x4a), + Serial => rgb(0x4f, 0xa3, 0xe0), + } +} + +/// Short badge text ("SSH", "TELNET"…). +pub fn protocol_badge(p: nexus_core::models::Protocol) -> &'static str { + use nexus_core::models::Protocol::*; + match p { + Ssh => "SSH", + Telnet => "TELNET", + Raw => "RAW", + Github => "GitHub", + Vnc => "VNC", + Irc => "IRC", + Local => "LOCAL", + Serial => "SERIAL", + } +} diff --git a/rust/app/src/vnc_view.rs b/rust/app/src/vnc_view.rs new file mode 100644 index 0000000..24c7a1d --- /dev/null +++ b/rust/app/src/vnc_view.rs @@ -0,0 +1,22 @@ +//! VNC viewer: the remote framebuffer as a texture, with mouse and keyboard input. +//! STUB — interface fixed; implementation pending. + +use crate::state::Shared; +use nexus_core::models::Profile; + +pub struct VncView { + pub session: String, +} + +impl VncView { + pub fn new(session: &str, _profile: &Profile) -> VncView { + VncView { session: session.into() } + } + + /// Called on `Event::VncReady` (the client is connected; fetch it via `s.svc.sessions.vnc(id)`). + pub fn on_ready(&mut self) {} + + pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { + ui.label("VNC"); + } +} diff --git a/rust/app/src/widgets.rs b/rust/app/src/widgets.rs index 1a7bf1e..0748565 100644 --- a/rust/app/src/widgets.rs +++ b/rust/app/src/widgets.rs @@ -3,7 +3,6 @@ use crate::theme::{self, Tokens}; use egui::{Align, Color32, Layout, Response, RichText, Sense, Ui, vec2}; -use egui_phosphor::regular as icon; /// Small uppercase section header with a hairline. pub fn section(ui: &mut Ui, title: &str) { diff --git a/rust/packaging/macos/NexusTerminal.icns b/rust/packaging/macos/NexusTerminal.icns new file mode 100644 index 0000000000000000000000000000000000000000..7667632ee8ec9f36dbc1854fe8074b4c1ea2e0db GIT binary patch literal 314885 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%0AA16Ikll@n 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

SP1go(|2~8jq^skw`|4!@;Bxd)@i!krx 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

HR?O2?0ywZ$C{hoRub#b4nrmxLhr_gaT zr@!ESrL{sRR}NJNFM-c%xJFOI96rZx**xl^?H#H2N&8exq&Ba2pB~9q=sg^4dw3_A ziK*%?OLTB&#D+)=$HWVP(S!F_ZYaMDqqA2Sx^i4*+wkZkLQ3x|0k=`ql2671=C}lR zIyR|iOar3HggoOdrG<%ZgA5TThxhO*l>*FJq5bfQdG3cVfZOAlaMHuePsm+u8y;U3 zdq%B)PK|%uYMCxX1?OA_pXMbop%cV8p}j6b*OZ&jF7V^69)}r zyLs!SZQg)(3vC>_fv41|7gp4xo^cjzGsrvo^*s|iDZ9lk;bucJn1@er9Kw1;Z22Vp z9SsS})sN|&+gh#6{MQ~qoKoQ!njP_m1N5t2eAAHWe1aNd6q;EJPw57YeEX2&mZF_e z^OqRbXa1FBuCkGnTr`Q??$3-gU!WLEtZ()Jr(SFIgI1NZ%hEETXbS7P^=QOK0sBle z6(V$ogzC`&8+j1tp4k=ydPHUM`AtRxi9O||JKucTM_MHKP8~0QFcR{x?@IStXi-|d z)dowCGa~hDso9<0(1K4@#OW+#L1Ps=h8yO`?>CzAp|)jcUdD%A6C+aOSqcx+Mp#H% zCbCeptQpzj!VI~JP2Op|q`+ay9fMPWUp+RhU2rO$dq$_iVq06|~h7hIk{6 zk&WAYdY+9Ra2bF8u9w8UDWRFgGd1nf$9Jn^n3;yiDRC~NF1uAMkmr9VDya&qrzxqt z+*;9-T7v7PM@R1(-gAW9KQeAr6j*Q!P9X_+M18S~>8pU^3u?A&EFS8H9&8j;M^lH& zB6LHeM&a}VYUjofoB1btJ6g9gJG15FFP02bk6bO5lPuCe(}u;~q~JJ~Oe%g>d%?>y zP$$Q?R+vv$+oLSzvF(*o$#rL|z}?eb-MF|l7efXN>*MRm}#vSg&@n z`Qx-K>HPJeOD`7|WUlfd7#s*1l9F8ySe7SdwW`hDxo1l{;P;Z^F-;>sFm_5pDuOI5fU zDU(b|P?_CL3fa?+QnRXS$mt|h?GjqYMDn5SbrMQapXK)6Z-et^JMCUeV4jB1*Ovv$ z^o&z-2mx{Hb&eNZ`HV6RVqIlvHl!@Qy~zC9zaAfkFnw+Jwa9Z04i6uhcz(K|z-Nw6 zc2~sBD~$o`lp?aZlI&*Ap|nTh3L?G|NhDnAfqQ~OSZ$y+*5v}R8`vI{`UIh72=aYw zBs_8d!T@na_N!=)<@>}j6~?!87;~Pp&ZVoqrp0~eGFr0P_xkSMhravUrIxN9Q(s*O z;K4^v*b(8+7~JA1v4Au8ZCMx2O`qmm%ztX zTR*EtHVe47&L{WT(Jk<-T`H&i=<^(}xrDDE^BvW_2fXFA6UNr}so9S4j>KOvD3E#P zfLRDn7A?ymGJJ-H)1Q+JEZGm;^#oi~dUBAWW1rl~s&KnQ4xWYQ2w&}v$zCPNu6Ext z5lxz1n|ePVj}1T4+9T)BEv=t6+p~gC@8CYhGR{HhU+-SBwYpI0tzBw1`ugy-0Zhol z^a728&aSaj3a_l*{=sp%wv&~(YGIaaQb}1-S>Z@xW0?=tZq=h&>+#8+QdCpv@?epl zVNr*Z1K)y(Xv^)5iAY;tuJ4);7~Sz7$nL<}O**|Pw8W@iEXx%DN-QAb)KYskqSEab z;LLJ4An$M_r6zf@CqR4iO=cPN;2GJ%t!}~31Zq^OR7#Mtrn-&H8_`SJw=Zw|B1OkV z$Nh2LitUQ)h{?ryops(bcp2Ee*%&!^Ssn8(adPHT-Ts@sYxtplCMxE8(W;c_m8?4V(Y%(s|lWo~#cD?INmdLCpB=e9?==RfoaOv{rZ@$kxztzyE zRYgYG#WhjJbrrP}xl7*VO;;-AISD7Re(LpdZZ`OphvY^3*xagW*rKRfVCkG~-!|6g zn(gF4jto=xZ5~e+349@xj6rLCLi>-qW}N^ literal 0 HcmV?d00001 diff --git a/rust/packaging/nexus.svg b/rust/packaging/nexus.svg new file mode 100644 index 0000000..00c16bf --- /dev/null +++ b/rust/packaging/nexus.svg @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + nexus — ssh + + + + + + + user@nexus:~$ + + + >_ + + + + + + SSH v2 AES-256 + + + + + + diff --git a/rust/packaging/windows/nexus.ico b/rust/packaging/windows/nexus.ico new file mode 100644 index 0000000000000000000000000000000000000000..1142719a4e9695dbd77e47feedfd512358d23abe GIT binary patch literal 372526 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 Date: Sun, 11 Oct 2026 16:51:53 +0100 Subject: [PATCH 19/45] rust: system CJK font fallback, sidebar footer layout, --run-local test hook --- rust/app/src/app.rs | 16 ++++++++++++ rust/app/src/main.rs | 3 ++- rust/app/src/sidebar.rs | 56 ++++++++++++++++++++--------------------- rust/app/src/theme.rs | 26 +++++++++++++++++++ 4 files changed, 71 insertions(+), 30 deletions(-) diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs index 02ba590..b284da7 100644 --- a/rust/app/src/app.rs +++ b/rust/app/src/app.rs @@ -18,6 +18,8 @@ use std::sync::{Arc, Mutex, mpsc}; pub struct Options { pub screenshot: Option, pub theme: Option, + /// Open a local terminal at startup and type this command once connected (testing aid). + pub run_local: Option, } /// Errors from worker threads, drained into toasts each frame. @@ -77,6 +79,7 @@ pub struct App { pub update: Option, screenshot: Option<(PathBuf, u32)>, shot_settle: u32, + run_local: Option<(String, Option)>, } const fn sc(m: Modifiers, k: Key) -> KeyboardShortcut { @@ -121,7 +124,13 @@ impl App { update: None, screenshot: opts.screenshot.map(|p| (p, 0)), shot_settle: 0, + run_local: opts.run_local.map(|c| (c, None)), }; + if let Some((_, sid)) = &mut app.run_local { + let id = app.s.svc.connect_local(); + *sid = Some(id.clone()); + app.add_session(&id, true); + } if let Some(from) = &svc.config.migrated_from { app.s.success(format!("Imported your Alchementary Link data from {}", from.display())); } @@ -426,6 +435,13 @@ impl App { let local = p.info.protocol == Protocol::Local; match state { SessionState::Connected => { + if let Some((cmd, Some(sid))) = &self.run_local + && sid == session + { + let _ = self.s.svc.sessions.write(session, format!("{cmd}\n").as_bytes()); + self.run_local = None; + } + let Some(p) = self.s.pane_mut(session) else { return }; p.local_exited = false; p.connected_since = Some(chrono::Local::now()); // The PTY was created before the view knew its size: send it now. diff --git a/rust/app/src/main.rs b/rust/app/src/main.rs index 76186c3..07c5e6f 100644 --- a/rust/app/src/main.rs +++ b/rust/app/src/main.rs @@ -33,13 +33,14 @@ use nexus_core::service::Service; use std::sync::Arc; fn main() -> eframe::Result { - let mut opts = app::Options { screenshot: None, theme: None }; + let mut opts = app::Options { screenshot: None, theme: None, run_local: None }; let mut size = [1360.0, 860.0]; let mut data_dir = None; let mut args = std::env::args().skip(1); while let Some(a) = args.next() { match a.as_str() { "--theme" => opts.theme = args.next(), + "--run-local" => opts.run_local = args.next(), "--screenshot" => opts.screenshot = args.next().map(Into::into), // Use another data directory (testing, screenshots, portable installs). "--data-dir" => data_dir = args.next(), diff --git a/rust/app/src/sidebar.rs b/rust/app/src/sidebar.rs index c6923cf..35ad290 100644 --- a/rust/app/src/sidebar.rs +++ b/rust/app/src/sidebar.rs @@ -31,36 +31,34 @@ pub fn ui(app: &mut App, ui: &mut Ui) { }); ui.add_space(4.0); - let footer_h = 36.0 + if app.sidebar.selected.is_empty() { 0.0 } else { 62.0 }; - let list_h = (ui.available_height() - footer_h).max(60.0); - egui::ScrollArea::vertical().max_height(list_h).auto_shrink([false, false]).show(ui, |ui| list(app, ui)); - - if !app.sidebar.selected.is_empty() { - bulk_bar(app, ui); - } - // Footer actions. - ui.separator(); - ui.horizontal(|ui| { - ui.add_space(6.0); - if widgets::icon_button(ui, icon::PLUS, "New connection (Ctrl/Cmd+N)").clicked() { - app.s.request(Request::NewProfile { protocol: None, folder: String::new() }); - } - if widgets::icon_button(ui, icon::COMMAND, "Command palette (Ctrl/Cmd+K)").clicked() { - app.palette = Some(Default::default()); - } - if widgets::icon_button(ui, icon::DOWNLOAD_SIMPLE, "Import profiles").clicked() { - app.s.request(Request::ImportEncrypted); - } - if widgets::icon_button(ui, icon::UPLOAD_SIMPLE, "Export profiles").clicked() { - app.s.request(Request::ExportEncrypted(vec![])); - } - if widgets::icon_button(ui, icon::FILM_STRIP, "Recordings").clicked() { - app.recordings.open(); - } - if widgets::icon_button(ui, icon::GEAR, "Settings (Ctrl/Cmd+,)").clicked() { - app.settings.open(crate::state::SettingsTab::General); - } + // Footer actions, pinned to the bottom of the sidebar. + egui::Panel::bottom("sidebar-footer").exact_size(36.0).frame(egui::Frame::NONE.fill(t.dock)).show_inside(ui, |ui| { + ui.horizontal_centered(|ui| { + ui.add_space(6.0); + if widgets::icon_button(ui, icon::PLUS, "New connection (Ctrl/Cmd+N)").clicked() { + app.s.request(Request::NewProfile { protocol: None, folder: String::new() }); + } + if widgets::icon_button(ui, icon::COMMAND, "Command palette (Ctrl/Cmd+K)").clicked() { + app.palette = Some(Default::default()); + } + if widgets::icon_button(ui, icon::DOWNLOAD_SIMPLE, "Import profiles").clicked() { + app.s.request(Request::ImportEncrypted); + } + if widgets::icon_button(ui, icon::UPLOAD_SIMPLE, "Export profiles").clicked() { + app.s.request(Request::ExportEncrypted(vec![])); + } + if widgets::icon_button(ui, icon::FILM_SLATE, "Recordings").clicked() { + app.recordings.open(); + } + if widgets::icon_button(ui, icon::GEAR, "Settings (Ctrl/Cmd+,)").clicked() { + app.settings.open(crate::state::SettingsTab::General); + } + }); }); + if !app.sidebar.selected.is_empty() { + egui::Panel::bottom("sidebar-bulk").frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)).show_inside(ui, |ui| bulk_bar(app, ui)); + } + egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| list(app, ui)); } fn list(app: &mut App, ui: &mut Ui) { diff --git a/rust/app/src/theme.rs b/rust/app/src/theme.rs index 28a5b46..48a9e5e 100644 --- a/rust/app/src/theme.rs +++ b/rust/app/src/theme.rs @@ -196,12 +196,38 @@ pub fn install_fonts(ctx: &egui::Context) { // The bundled Inter has its private-use-area cmap stripped, so phosphor's icons win there. fonts.families.entry(FontFamily::Proportional).or_default().insert(0, "Inter".into()); fonts.families.entry(FontFamily::Monospace).or_default().insert(0, "JetBrainsMono".into()); + // CJK and other scripts the bundled fonts lack: borrow a system font if one is installed. + if let Some(bytes) = system_fallback_font() { + fonts.font_data.insert("system-fallback".into(), Arc::new(FontData::from_owned(bytes))); + for fam in [FontFamily::Proportional, FontFamily::Monospace] { + fonts.families.entry(fam).or_default().push("system-fallback".into()); + } + } let mut semibold = vec!["Inter-SemiBold".to_owned()]; semibold.extend(fonts.families[&FontFamily::Proportional].iter().cloned()); fonts.families.insert(FontFamily::Name("semibold".into()), semibold); ctx.set_fonts(fonts); } +/// The first wide-coverage (CJK) system font found on this machine. +fn system_fallback_font() -> Option> { + let candidates: &[&str] = if cfg!(target_os = "macos") { + &["/System/Library/Fonts/PingFang.ttc", "/System/Library/Fonts/Hiragino Sans GB.ttc", "/Library/Fonts/Arial Unicode.ttf", "/System/Library/Fonts/Supplemental/Arial Unicode.ttf"] + } else if cfg!(windows) { + &["C:\\Windows\\Fonts\\msyh.ttc", "C:\\Windows\\Fonts\\YuGothM.ttc", "C:\\Windows\\Fonts\\msgothic.ttc", "C:\\Windows\\Fonts\\malgun.ttf"] + } else { + &[ + "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc", + "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", + "/usr/share/fonts/wenquanyi/wqy-microhei/wqy-microhei.ttc", + "/usr/share/fonts/truetype/wqy/wqy-microhei.ttc", + ] + }; + candidates.iter().find_map(|p| std::fs::read(p).ok()) +} + pub fn semibold(size: f32) -> FontId { FontId::new(size, FontFamily::Name("semibold".into())) } From 328507c57b02444efb5ad1455c400efd7b87bd1c Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 16:53:19 +0100 Subject: [PATCH 20/45] rust: packaging (Linux desktop entry, macOS bundle), cargo release script with update manifests, CI --- .github/workflows/ci.yml | 28 +++++ .github/workflows/release-builds.yml | 126 +++++--------------- rust/core/src/service.rs | 6 +- rust/packaging/linux/install.sh | 14 +++ rust/packaging/linux/nexus-terminal.desktop | 11 ++ rust/packaging/macos/Info.plist | 36 ++++++ rust/packaging/macos/build-app.sh | 62 ++++++++++ rust/release.sh | 109 +++++++++++++++++ 8 files changed, 294 insertions(+), 98 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100755 rust/packaging/linux/install.sh create mode 100644 rust/packaging/linux/nexus-terminal.desktop create mode 100644 rust/packaging/macos/Info.plist create mode 100755 rust/packaging/macos/build-app.sh create mode 100755 rust/release.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bc00f43 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + +jobs: + test: + name: Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-22.04, macos-14, windows-latest] + defaults: + run: + shell: bash + working-directory: rust + steps: + - uses: actions/checkout@v4 + - name: Linux dependencies (GUI libs, and sshd for the SSH integration tests) + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y libxkbcommon-dev libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev libwayland-dev libgtk-3-dev openssh-server + - run: cargo test --locked --workspace + - run: cargo clippy --locked --workspace -- -D warnings diff --git a/.github/workflows/release-builds.yml b/.github/workflows/release-builds.yml index 57a1c7d..64fe0c6 100644 --- a/.github/workflows/release-builds.yml +++ b/.github/workflows/release-builds.yml @@ -17,118 +17,50 @@ jobs: fail-fast: false matrix: include: - - platform: darwin/amd64 - runner: macos-13 - os: darwin - arch: amd64 - archive_type: zip - - platform: darwin/arm64 - runner: macos-14 - os: darwin - arch: arm64 - archive_type: zip - - platform: linux/amd64 - runner: ubuntu-latest - os: linux - arch: amd64 - archive_type: tar - - platform: linux/arm64 - runner: ubuntu-24.04-arm64 - os: linux - arch: arm64 - archive_type: tar - - platform: windows/amd64 - runner: windows-latest - os: windows - arch: amd64 - archive_type: zip - - platform: windows/arm64 - runner: ubuntu-latest - os: windows - arch: arm64 - archive_type: zip - cross: true - + - { platform: darwin/amd64, runner: macos-13 } + - { platform: darwin/arm64, runner: macos-14 } + - { platform: linux/amd64, runner: ubuntu-22.04 } + - { platform: linux/arm64, runner: ubuntu-22.04-arm } + - { platform: windows/amd64, runner: windows-latest } + - { platform: windows/arm64, runner: windows-11-arm } + defaults: + run: + shell: bash + working-directory: rust steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 + - uses: actions/checkout@v4 with: - go-version-file: go.mod + fetch-depth: 0 - - name: Set up Node - uses: actions/setup-node@v4 - with: - node-version: "20" + - name: Rust toolchain (pinned by rust-toolchain.toml) + run: rustup show active-toolchain || rustup toolchain install - - name: Install Wails - run: go install github.com/wailsapp/wails/v2/cmd/wails@$(go list -m -f '{{.Version}}' github.com/wailsapp/wails/v2) - - - name: Install frontend dependencies - run: npm ci - working-directory: frontend - - - name: Install Linux build dependencies - if: matrix.os == 'linux' + - name: Linux build dependencies + if: runner.os == 'Linux' run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev + sudo apt-get install -y libxkbcommon-dev libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev libwayland-dev libgtk-3-dev zip - - name: Install Windows packaging tools - if: matrix.os == 'windows' && matrix.cross - run: | - sudo apt-get update - sudo apt-get install -y zip + - name: Test + run: cargo test --locked --workspace - - name: Build (non-Windows runners) - if: matrix.runner != 'windows-latest' + - name: Build and package env: - CGO_ENABLED: "0" - run: | - wails build -platform "${{ matrix.platform }}" -ldflags "-s -w" + NEXUS_RELEASE_TARGETS: ${{ matrix.platform }} + NEXUS_RELEASE_ROOT: ${{ github.workspace }}/build/releases + run: ./release.sh - - name: Build (Windows runner) - if: matrix.runner == 'windows-latest' - shell: pwsh - env: - CGO_ENABLED: "0" - run: | - wails build -platform "${{ matrix.platform }}" -ldflags "-s -w" - - - name: Package (zip) - if: matrix.archive_type == 'zip' && matrix.runner != 'windows-latest' - run: | - archive="alchementary-link-${{ matrix.platform }}.zip" - cd build/bin - zip -r "../../${archive}" . - - - name: Package (tar) - if: matrix.archive_type == 'tar' - run: | - archive="alchementary-link-${{ matrix.platform }}.tar.gz" - tar -czf "${archive}" -C build/bin . - - - name: Package (Windows zip) - if: matrix.archive_type == 'zip' && matrix.runner == 'windows-latest' - shell: pwsh - run: | - $archive = "alchementary-link-${{ matrix.platform }}.zip" - Compress-Archive -Path "build/bin/*" -DestinationPath $archive - - - name: Upload artifact + - name: Upload artefacts uses: actions/upload-artifact@v4 with: - name: alchementary-link-${{ matrix.platform }} + name: nexus-terminal-${{ matrix.runner }} path: | - alchementary-link-${{ matrix.platform }}.zip - alchementary-link-${{ matrix.platform }}.tar.gz + build/releases/*/NexusTerminal-*.zip + build/releases/manifest-*.json + build/releases/version-*.txt - name: Attach to GitHub Release if: startsWith(github.ref, 'refs/tags/') uses: softprops/action-gh-release@v2 with: - files: | - alchementary-link-${{ matrix.platform }}.zip - alchementary-link-${{ matrix.platform }}.tar.gz + files: build/releases/*/NexusTerminal-*.zip diff --git a/rust/core/src/service.rs b/rust/core/src/service.rs index 2d385e7..89df85f 100644 --- a/rust/core/src/service.rs +++ b/rust/core/src/service.rs @@ -13,7 +13,11 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::{Duration, Instant}; -pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +/// The release version (`NEXUS_VERSION` from release.sh, e.g. "v1.2.0-abc1234"), else the crate version. +pub const VERSION: &str = match option_env!("NEXUS_VERSION") { + Some(v) => v, + None => env!("CARGO_PKG_VERSION"), +}; const MAX_IMPORT_JSON_BYTES: usize = 5 * 1024 * 1024; pub struct Service { diff --git a/rust/packaging/linux/install.sh b/rust/packaging/linux/install.sh new file mode 100755 index 0000000..086869b --- /dev/null +++ b/rust/packaging/linux/install.sh @@ -0,0 +1,14 @@ +#!/bin/sh +# Install Nexus Terminal for the current user so it appears in the app launcher. +# Usage: rust/packaging/linux/install.sh [path/to/nexus-terminal] (default: target/release/nexus-terminal) +set -eu +HERE=$(cd "$(dirname "$0")" && pwd) +BIN=${1:-"$HERE/../../target/release/nexus-terminal"} +PREFIX=${XDG_DATA_HOME:-$HOME/.local/share} +install -Dm755 "$BIN" "$HOME/.local/bin/nexus-terminal" +install -Dm644 "$HERE/nexus-terminal.desktop" "$PREFIX/applications/nexus-terminal.desktop" +install -Dm644 "$HERE/../../app/assets/icon/icon-256.png" "$PREFIX/icons/hicolor/256x256/apps/nexus-terminal.png" +install -Dm644 "$HERE/../../app/assets/icon/icon-512.png" "$PREFIX/icons/hicolor/512x512/apps/nexus-terminal.png" +update-desktop-database "$PREFIX/applications" 2>/dev/null || true +gtk-update-icon-cache -f -t "$PREFIX/icons/hicolor" 2>/dev/null || true +echo "installed: ~/.local/bin/nexus-terminal (make sure ~/.local/bin is on PATH)" diff --git a/rust/packaging/linux/nexus-terminal.desktop b/rust/packaging/linux/nexus-terminal.desktop new file mode 100644 index 0000000..f902934 --- /dev/null +++ b/rust/packaging/linux/nexus-terminal.desktop @@ -0,0 +1,11 @@ +[Desktop Entry] +Type=Application +Name=Nexus Terminal +GenericName=Terminal and Remote Connections +Comment=SSH, Telnet, RAW TCP, Serial, VNC, IRC and GitHub sessions in one window +Exec=nexus-terminal +Icon=nexus-terminal +Terminal=false +Categories=System;TerminalEmulator;Network;RemoteAccess; +Keywords=ssh;telnet;serial;sftp;vnc;irc;terminal; +StartupWMClass=nexus-terminal diff --git a/rust/packaging/macos/Info.plist b/rust/packaging/macos/Info.plist new file mode 100644 index 0000000..1443493 --- /dev/null +++ b/rust/packaging/macos/Info.plist @@ -0,0 +1,36 @@ + + + + + CFBundleName + Nexus Terminal + CFBundleDisplayName + Nexus Terminal + CFBundleIdentifier + com.andydixon.nexusterminal + CFBundleExecutable + NexusTerminal + CFBundleIconFile + NexusTerminal + CFBundlePackageType + APPL + CFBundleShortVersionString + __VERSION__ + CFBundleVersion + __VERSION__ + CFBundleInfoDictionaryVersion + 6.0 + CFBundleDevelopmentRegion + en + LSApplicationCategoryType + public.app-category.developer-tools + LSMinimumSystemVersion + 11.0 + NSHighResolutionCapable + + NSSupportsAutomaticGraphicsSwitching + + NSHumanReadableCopyright + © Andy Dixon + + diff --git a/rust/packaging/macos/build-app.sh b/rust/packaging/macos/build-app.sh new file mode 100755 index 0000000..93ed8df --- /dev/null +++ b/rust/packaging/macos/build-app.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# Build NexusTerminal.app, a macOS application bundle. Run on a Mac with Xcode command-line tools: +# +# rust/packaging/macos/build-app.sh # universal (Apple Silicon + Intel) if both +# # targets are installed, else this Mac's arch +# rust/packaging/macos/build-app.sh --dmg # also wrap it in a .dmg +# +# Output: rust/target/macos/NexusTerminal.app (and NexusTerminal-.dmg). +# Set NEXUS_BIN=/path/to/binary to bundle an existing build instead of compiling. +set -eu +HERE=$(cd "$(dirname "$0")" && pwd) +ROOT=$(cd "$HERE/../.." && pwd) +cd "$ROOT" +VERSION=${NEXUS_VERSION:-$(sed -n 's/^version = "\(.*\)"/\1/p' Cargo.toml | head -1)} +OUT="$ROOT/target/macos" +APP="$OUT/NexusTerminal.app" + +if [ -n "${NEXUS_BIN:-}" ]; then + BIN="$NEXUS_BIN" +else + targets="" + for t in aarch64-apple-darwin x86_64-apple-darwin; do + if rustup target list --installed 2>/dev/null | grep -qx "$t"; then targets="$targets $t"; fi + done + [ -n "$targets" ] || targets=" $(rustc -vV | sed -n 's/^host: //p')" + bins="" + for t in $targets; do + echo "building $t" + cargo build --release -p nexus --target "$t" + bins="$bins target/$t/release/nexus-terminal" + done + mkdir -p "$OUT" + set -- $bins + if [ $# -gt 1 ]; then + lipo -create "$@" -output "$OUT/nexus-terminal-universal" + BIN="$OUT/nexus-terminal-universal" + else + BIN="$1" + fi +fi + +rm -rf "$APP" +mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources" +cp "$BIN" "$APP/Contents/MacOS/NexusTerminal" +chmod +x "$APP/Contents/MacOS/NexusTerminal" +cp "$HERE/NexusTerminal.icns" "$APP/Contents/Resources/NexusTerminal.icns" +sed "s/__VERSION__/$VERSION/g" "$HERE/Info.plist" > "$APP/Contents/Info.plist" +printf 'APPL????' > "$APP/Contents/PkgInfo" + +if command -v plutil >/dev/null; then plutil -lint "$APP/Contents/Info.plist"; fi +# Ad-hoc sign so Apple Silicon runs it. For distribution, sign with a Developer ID and notarize: +# codesign --force --options runtime --sign "Developer ID Application: …" NexusTerminal.app +# xcrun notarytool submit … --wait && xcrun stapler staple NexusTerminal.app +if command -v codesign >/dev/null; then codesign --force --deep --sign - "$APP"; fi + +if [ "${1:-}" = "--dmg" ] && command -v hdiutil >/dev/null; then + DMG="$OUT/NexusTerminal-$VERSION.dmg" + rm -f "$DMG" + hdiutil create -volname "Nexus Terminal" -srcfolder "$APP" -ov -format UDZO "$DMG" + echo "built $DMG" +fi +echo "built $APP" diff --git a/rust/release.sh b/rust/release.sh new file mode 100755 index 0000000..03dd4f9 --- /dev/null +++ b/rust/release.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash +# release.sh — build and package Nexus Terminal release artefacts with cargo. +# +# Produces, under build/releases// (repo root): +# NexusTerminal--[.exe|.app] and NexusTerminal--.zip +# and next to them manifest--.json + version--.txt for the update server +# (updates/index.php), in the same format the Wails build produced. +# +# Targets default to this machine's native one; set NEXUS_RELEASE_TARGETS to a comma list such as +# "linux/amd64,windows/amd64" (windows/* from Linux needs the mingw-w64 toolchain and rustup target). +# CI (.github/workflows/release-builds.yml) builds every platform natively. +set -euo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$HERE/.." && pwd)" +cd "$HERE" + +DEFAULT_UPDATE_URL="https://alchementary.co.uk/download/link/manifest-{platform}-{arch}.json" +DEFAULT_RELEASE_BASE_URL="https://alchementary.co.uk/download/link" + +log() { echo -e "\033[0;32m[nexus]\033[0m $1"; } +warn() { echo -e "\033[1;33m[nexus]\033[0m $1"; } + +host_target() { + local os arch + case "$(uname -s)" in Darwin) os=darwin ;; Linux) os=linux ;; MINGW*|MSYS*|CYGWIN*) os=windows ;; *) os=unknown ;; esac + case "$(uname -m)" in x86_64|amd64) arch=amd64 ;; arm64|aarch64) arch=arm64 ;; *) arch=unknown ;; esac + echo "$os/$arch" +} + +rust_triple() { + case "$1" in + linux/amd64) echo x86_64-unknown-linux-gnu ;; + linux/arm64) echo aarch64-unknown-linux-gnu ;; + darwin/amd64) echo x86_64-apple-darwin ;; + darwin/arm64) echo aarch64-apple-darwin ;; + windows/amd64) if [ "$(uname -s)" = Linux ]; then echo x86_64-pc-windows-gnu; else echo x86_64-pc-windows-msvc; fi ;; + windows/arm64) echo aarch64-pc-windows-msvc ;; + *) return 1 ;; + esac +} + +VERSION="${NEXUS_VERSION:-$(git describe --tags --abbrev=0 2>/dev/null || echo dev)-$(git rev-parse --short HEAD 2>/dev/null || echo unknown)}" +export NEXUS_VERSION="$VERSION" +export NEXUS_UPDATE_URL="${NEXUS_UPDATE_URL:-$DEFAULT_UPDATE_URL}" +BASE_URL="${NEXUS_RELEASE_BASE_URL:-$DEFAULT_RELEASE_BASE_URL}" +RELEASE_ROOT="${NEXUS_RELEASE_ROOT:-$ROOT/build/releases}" +OUT="$RELEASE_ROOT/$VERSION" +mkdir -p "$OUT" +: > "$OUT/.release-files" + +IFS=',' read -r -a TARGETS <<< "${NEXUS_RELEASE_TARGETS:-$(host_target)}" +errors=0 +for target in "${TARGETS[@]}"; do + os="${target%%/*}"; arch="${target##*/}" + triple="$(rust_triple "$target")" || { warn "unknown target $target"; errors=$((errors+1)); continue; } + base="NexusTerminal-${os}-${arch}" + log "Building $target ($triple), version $VERSION" + rustup target add "$triple" >/dev/null 2>&1 || true + if ! cargo build --release --locked -p nexus --target "$triple"; then + warn "build failed for $target"; errors=$((errors+1)); continue + fi + bin="target/$triple/release/nexus-terminal" + case "$os" in + darwin) + NEXUS_BIN="$bin" packaging/macos/build-app.sh + rm -rf "$OUT/$base.app" + cp -R target/macos/NexusTerminal.app "$OUT/$base.app" + (cd "$OUT" && rm -f "$base.zip" && zip -qry "$base.zip" "$base.app") + ;; + windows) + cp "$bin.exe" "$OUT/$base.exe" + (cd "$OUT" && rm -f "$base.zip" && zip -qj "$base.zip" "$base.exe") + ;; + *) + cp "$bin" "$OUT/$base" + strip "$OUT/$base" 2>/dev/null || true + stage="$(mktemp -d)" + cp "$OUT/$base" "$stage/nexus-terminal" + cp packaging/linux/nexus-terminal.desktop packaging/linux/install.sh "$stage/" + mkdir -p "$stage/app/assets/icon" && cp app/assets/icon/icon-256.png app/assets/icon/icon-512.png "$stage/app/assets/icon/" + sed -i 's|\$HERE/../../app|$HERE/app|; s|\$HERE/../../target/release/nexus-terminal|$HERE/nexus-terminal|' "$stage/install.sh" + (cd "$stage" && rm -f "$OUT/$base.zip" && zip -qr "$OUT/$base.zip" .) + rm -rf "$stage" + ;; + esac + echo "$os|$arch|$base.zip" >> "$OUT/.release-files" +done + +published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" +while IFS='|' read -r os arch filename; do + [ -z "$os" ] && continue + sha="$(shasum -a 256 "$OUT/$filename" 2>/dev/null || sha256sum "$OUT/$filename")"; sha="${sha%% *}" + size="$(wc -c < "$OUT/$filename" | tr -d ' ')" + echo "$VERSION" > "$RELEASE_ROOT/version-${os}-${arch}.txt" + cat > "$RELEASE_ROOT/manifest-${os}-${arch}.json" < Date: Sun, 11 Oct 2026 17:04:24 +0100 Subject: [PATCH 21/45] rust: keychain calls off the tokio runtime; plan reflects as-built crate choices --- docs/rust-port-plan.md | 10 ++++++---- rust/core/src/secrets.rs | 24 ++++++++++++++++-------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/docs/rust-port-plan.md b/docs/rust-port-plan.md index 9965ce0..321bc17 100644 --- a/docs/rust-port-plan.md +++ b/docs/rust-port-plan.md @@ -25,13 +25,13 @@ style of Leviathan Desktop (`../leviathan/rust`). | Terminal emulation | `alacritty_terminal` | VT parser, grid, scrollback, selection and regex search (the same engine Zed uses). We draw it with egui's painter (§6) | | Local shell | `portable-pty` | `forkpty` on Unix, ConPTY on Windows | | Serial | `serialport` | Baud/bits/parity/stop, RTS/CTS and XON/XOFF, DTR/RTS, break, port enumeration | -| IRC | `irc` (tokio, rustls) | Registration, PING, CTCP, NickServ via PRIVMSG; reconnect and channel-state persistence are ours | -| VNC | `vnc-rs` | RFB handshake, VNC auth, Raw/CopyRect/Tight/ZRLE → egui texture | +| IRC | none (our own client over std TCP, `rustls` for TLS) | Registration, PING, CTCP, NickServ via PRIVMSG; reconnect and channel-state persistence are ours | +| VNC | none (our own RFB client; `des`, `flate2`, `image` for Tight JPEG) | RFB 3.3–3.8, VNC auth, Raw/CopyRect/Hextile/ZRLE/Tight → egui texture | | HTTP (GitHub, updater) | `ureq` 3 | Blocking calls on `spawn_blocking` | | SQLite | `rusqlite` (`bundled`) | SQLite compiled in, so no system library is needed | | Vault, export | `argon2`, `aes-gcm`, `rand` | Native Argon2id at 128 MiB takes about 0.3 s | -| Keychain | `keyring` 3 | Must read entries written by go-keyring (§3) | -| TOTP | `totp-rs` | | +| Keychain | `keyring` 3 (pure-Rust D-Bus via zbus on Linux) | Must read entries written by go-keyring (§3) | +| TOTP | none (`hmac` + `sha1`, ~30 lines) | RFC 6238, normalised like pquerna/otp | | JSON | `serde`, `serde_json` | Protocol configs stay JSON columns | | Telnet, RAW, recordings | none (tokio `TcpStream`, serde) | Small ports of the Go code | @@ -235,6 +235,8 @@ Terminal views are repainted only when their session receives data, and for inpu ## 6a. VNC +**As built:** `vnc-rs` was not used; the fallback below (our own RFB client) is what shipped. + - `vnc-rs` on a tokio task, with an optional SSH tunnel through §2's `direct-tcpip`. - Decoded rectangles are applied to an RGBA buffer, and dirty frames update a `TextureHandle`, scaled to fit. - Input: pointer → `PointerEvent`; keys → X11 keysyms (port the keysym map from `vnc.js`). diff --git a/rust/core/src/secrets.rs b/rust/core/src/secrets.rs index 9dc55f3..fea0c80 100644 --- a/rust/core/src/secrets.rs +++ b/rust/core/src/secrets.rs @@ -177,6 +177,12 @@ pub const KEYCHAIN_SERVICE: &str = "NexusTerminal"; /// The service name Alchementary Link wrote under; read as a fallback (§7 of the port plan). pub const LEGACY_KEYCHAIN_SERVICE: &str = "AlchementaryLink"; +/// Runs a keychain call on a plain thread: the D-Bus backend may start its own async runtime, +/// which panics if the caller is already inside tokio (as SSH authentication is). +fn off_runtime(f: impl FnOnce() -> T + Send + 'static) -> T { + std::thread::spawn(f).join().expect("keychain thread panicked") +} + fn entry(service: &str, key: &str) -> keyring::Result { // go-keyring's Windows target is "service:user"; keyring's default differs. if cfg!(windows) { @@ -199,30 +205,32 @@ impl Keychain { } pub fn available(&self) -> bool { - let probe = "__nexus_keychain_probe__"; - entry(&self.service, probe).and_then(|e| e.set_password("probe").and_then(|_| e.delete_credential())).is_ok() + let service = self.service.clone(); + off_runtime(move || entry(&service, "__nexus_keychain_probe__").and_then(|e| e.set_password("probe").and_then(|_| e.delete_credential())).is_ok()) } /// Reads a secret; a miss under the current service falls back to the legacy one and copies /// the value forward. Legacy items are never deleted, so the old app keeps working. pub fn get(&self, key: &str) -> Option { - if let Ok(v) = entry(&self.service, key).and_then(|e| e.get_password()) { + let (service, key2) = (self.service.clone(), key.to_string()); + if let Ok(v) = off_runtime(move || entry(&service, &key2).and_then(|e| e.get_password())) { return Some(v); } - let legacy = self.legacy.as_deref()?; - let v = entry(legacy, key).and_then(|e| e.get_password()).ok()?; + let (legacy, key2) = (self.legacy.clone()?, key.to_string()); + let v = off_runtime(move || entry(&legacy, &key2).and_then(|e| e.get_password())).ok()?; let _ = self.set(key, &v); Some(v) } pub fn set(&self, key: &str, value: &str) -> Result<()> { - entry(&self.service, key) - .and_then(|e| e.set_password(value)) + let (service, key2, value) = (self.service.clone(), key.to_string(), value.to_string()); + off_runtime(move || entry(&service, &key2).and_then(|e| e.set_password(&value))) .map_err(|e| format!("keychain: failed to store secret {key:?}: {e}").into()) } pub fn delete(&self, key: &str) { - let _ = entry(&self.service, key).and_then(|e| e.delete_credential()); + let (service, key) = (self.service.clone(), key.to_string()); + let _ = off_runtime(move || entry(&service, &key).and_then(|e| e.delete_credential())); } } From b1e18a8ffb51291cfcb7428fa6010057e6b6d031 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:14:11 +0100 Subject: [PATCH 22/45] rust: optimise the app crate in dev builds (terminal parser); throughput test --- rust/Cargo.toml | 4 ++++ rust/app/src/terminal.rs | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 620c57a..983f2a5 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -9,5 +9,9 @@ edition = "2024" [profile.dev.package."*"] opt-level = 2 +# The terminal parser is generic and gets instantiated in the app crate; unoptimised it is ~50x slower. +[profile.dev.package.nexus] +opt-level = 2 + [profile.release] lto = "thin" diff --git a/rust/app/src/terminal.rs b/rust/app/src/terminal.rs index fd92e3b..d705318 100644 --- a/rust/app/src/terminal.rs +++ b/rust/app/src/terminal.rs @@ -894,3 +894,25 @@ mod tests { assert_eq!(mouse_report(0, 2, 3, true, TermMode::empty()), vec![0x1b, b'[', b'M', 32, 35, 36]); } } + +#[cfg(test)] +mod perf { + /// `cargo test --release -p nexus perf -- --ignored --nocapture` + #[test] + #[ignore] + fn feed_throughput() { + let mut t = super::TermView::new(10_000, 14.0, super::CursorStyle::Block); + let line = b"drwxr-xr-x 22 root root 4096 Oct 1 00:01 \x1b[01;34msome-directory-name\x1b[0m and more text\r\n"; + let chunk: Vec = line.iter().copied().cycle().take(64 * 1024).collect(); + let total = 50 * 1024 * 1024; + let start = std::time::Instant::now(); + let mut n = 0; + while n < total { + t.feed(&chunk); + n += chunk.len(); + } + let secs = start.elapsed().as_secs_f64(); + eprintln!("fed {} MiB in {secs:.2}s = {:.0} MiB/s", total >> 20, (total >> 20) as f64 / secs); + assert!(secs < 30.0, "too slow even allowing for a loaded machine"); + } +} From f80374db776c66b7e0a261842079587fed2cf232 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:14:40 +0100 Subject: [PATCH 23/45] docs: build and release for the Rust app --- docs/build-and-release.md | 108 ++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 57 deletions(-) diff --git a/docs/build-and-release.md b/docs/build-and-release.md index f9fba19..cd18922 100644 --- a/docs/build-and-release.md +++ b/docs/build-and-release.md @@ -1,80 +1,74 @@ # Build and Release -This project uses [Wails](https://wails.io/) for desktop packaging. +Nexus Terminal is a Rust workspace in `rust/`: -## Ubuntu 24.04 prerequisites - -Install the Linux build toolchain and Wails runtime headers with: - -```bash -sudo apt-get update -sudo apt-get install -y \ - build-essential \ - libgtk-3-dev \ - libwebkit2gtk-4.1-dev \ - libayatana-appindicator3-dev \ - pkg-config \ - zip +``` +rust/ +├── core/ nexus-core: storage, secrets, protocol clients, session manager, Service. No GUI. +└── app/ nexus: the egui desktop app (binary `nexus-terminal`) ``` -Notes: +The toolchain is pinned in `rust/rust-toolchain.toml`; `rustup` installs it on first build. -- Ubuntu 24.04 does not ship `libwebkit2gtk-4.0-dev`. -- Wails `v2.12.0` supports Ubuntu 24.04 by building with the `webkit2_41` tag. -- The local build scripts detect that package layout automatically and add the tag when needed. +## Prerequisites -You will also need these tools available in `PATH`: +- **All platforms:** [rustup](https://rustup.rs). +- **Linux:** the X11/Wayland/GL headers eframe links against, and GTK for native file dialogs: -- `go` -- `node` -- `npm` -- `wails` + ```bash + sudo apt-get install -y build-essential pkg-config libxkbcommon-dev libgl1-mesa-dev \ + libx11-dev libxcursor-dev libxrandr-dev libxi-dev libwayland-dev libgtk-3-dev zip + ``` -Install the Wails CLI with: +- **macOS:** Xcode command-line tools (`xcode-select --install`). +- **Windows:** the MSVC build tools (Visual Studio Build Tools, "Desktop development with C++"). + +There are no runtime C libraries to install: SQLite is compiled in and TLS uses rustls. + +## Build and run ```bash -go install github.com/wailsapp/wails/v2/cmd/wails@latest -export PATH="$PATH:$(go env GOPATH)/bin" +cd rust +cargo run --release # the app +cargo test --workspace # unit and integration tests +cargo clippy --workspace -- -D warnings ``` -## Build commands +Useful flags (also handy for testing): `nexus-terminal [--theme pro|midnight|light] +[--data-dir DIR] [--screenshot out.png] [--size WxH] [--run-local CMD]`. `--data-dir` points at +another data directory; `--screenshot` renders the window, saves a PNG and exits (works under +`xvfb-run`); `--run-local` opens a local terminal and types `CMD` into it. -Build the app binary for the current host: +The SSH integration tests start a throwaway `sshd` as the current user on a random port; they +skip themselves when OpenSSH isn't installed. + +## Release packages ```bash -./build.sh +cd rust +./release.sh # this machine's platform +NEXUS_RELEASE_TARGETS=linux/amd64,windows/amd64 ./release.sh ``` -Build packaged releases for every supported 64-bit desktop target: +Artefacts land in `build/releases//` at the repo root: -```bash -./release.sh -``` +- `NexusTerminal--.zip` — on macOS a universal-capable `.app` bundle + (`rust/packaging/macos/build-app.sh`), on Windows the `.exe` (icon and version info embedded, + GUI subsystem), on Linux the binary plus `install.sh` and a `.desktop` entry + (`rust/packaging/linux/install.sh` installs into `~/.local`). +- `manifest--.json` and `version--.txt` for the update server + (`updates/index.php`), in the same format as before. -By default, `./release.sh` builds all six OS/architecture combinations: +The version is `-` unless `NEXUS_VERSION` is set. The update manifest +URL is compiled in from `NEXUS_UPDATE_URL` (default in `release.sh`); `{platform}` and `{arch}` use +the old Go names (`darwin`/`linux`/`windows`, `amd64`/`arm64`). -- macOS (`darwin`): `amd64` and `arm64` -- Linux: `amd64` and `arm64` -- Windows: `amd64` and `arm64` +Cross-building `windows/amd64` from Linux works with `x86_64-pc-windows-gnu` and mingw-w64. +macOS bundles must be built on a Mac. CI (`.github/workflows/release-builds.yml`) builds every +platform natively on GitHub runners and attaches the zips to tagged releases; +`.github/workflows/ci.yml` runs tests and clippy on Linux, macOS and Windows. -Go calls the x86-64 architecture `amd64` and the AArch64 architecture `arm64`. +## Signing -On macOS, Linux builds run in architecture-matched Podman or Docker containers -because Wails does not cross-compile Linux applications directly from macOS. -The reusable builder images keep their `node_modules` separate from the host -checkout. - -## Optional targeted builds - -The release scripts disable CGO and use a pure-Go SQLite driver, so Linux and Windows cross-targets do not need C cross-compilers for the application backend. To build a smaller set, override the default targets: - -```bash -LINK_RELEASE_TARGETS=linux/amd64,windows/amd64 ./release.sh -``` - -## Platform caveats - -- To produce all six targets in one run, use macOS with Podman or Docker running. -- macOS targets cannot be cross-built from Linux or Windows with Wails. -- Cross-target support still depends on Wails and frontend packaging support for the requested platform. -- Tested on Ubuntu 24.04 `amd64`: `linux/amd64` and `windows/amd64` build successfully. +macOS bundles are ad-hoc signed. For distribution, sign with a Developer ID and notarize (see the +comments in `build-app.sh`). Windows binaries are unsigned. From 7f4f9376fb3a0c5369f0090ddd9725c45a478721 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:23:47 +0100 Subject: [PATCH 24/45] rust: profile editor and quick connect --- rust/app/src/chrome.rs | 2 +- rust/app/src/profile_editor.rs | 1148 +++++++++++++++++++++++++++++++- rust/app/src/quick_connect.rs | 169 ++++- 3 files changed, 1297 insertions(+), 22 deletions(-) diff --git a/rust/app/src/chrome.rs b/rust/app/src/chrome.rs index 22d8e2e..0317ea7 100644 --- a/rust/app/src/chrome.rs +++ b/rust/app/src/chrome.rs @@ -611,7 +611,7 @@ pub fn palette(app: &mut App, ctx: &egui::Context) { // --- prompts -------------------------------------------------------------------------------- -fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) { +pub fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) { modal_backdrop(ctx); egui::Area::new(egui::Id::new(id)).order(egui::Order::Foreground).anchor(Align2::CENTER_CENTER, vec2(0.0, -40.0)).show(ctx, |ui| { egui::Frame::popup(ui.style()).corner_radius(10).inner_margin(16).show(ui, |ui| { diff --git a/rust/app/src/profile_editor.rs b/rust/app/src/profile_editor.rs index f9b7aa4..7e4d40d 100644 --- a/rust/app/src/profile_editor.rs +++ b/rust/app/src/profile_editor.rs @@ -1,31 +1,1155 @@ -//! Profile editor (right dock): create and edit every protocol's settings. -//! STUB — interface fixed; implementation pending. +//! Profile editor (right dock): create and edit every protocol's settings. A port of the +//! `profiles.js` drawer plus the Serial protocol. Switching protocol keeps the common fields +//! (including the group), and list editors keep unsaved edits. -use crate::state::Shared; -use nexus_core::models::Protocol; +use crate::state::{Request, Shared}; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::{Align, Button, ComboBox, DragValue, Layout, Response, RichText, Stroke, TextEdit, Ui, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::clients::serial; +use nexus_core::keyfiles; +use nexus_core::models::*; +use nexus_core::validation::{ValidationError, validate_profile}; +use std::collections::{BTreeMap, HashMap}; +use std::path::Path; +use std::sync::{Arc, Mutex}; + +type KeyChecks = HashMap>; -#[derive(Default)] pub struct ProfileEditor { open: bool, + is_new: bool, + p: Profile, + // Text buffers parsed on save. + tags: String, + env: String, + rooms: String, + /// Secrets typed into the editor, by session-manager kind ("password", "github-token", …); + /// stored when the profile is saved. + secrets: BTreeMap<&'static str, String>, + totp: String, + totp_show: bool, + totp_saved: bool, + /// Text shown on the TOTP Test button until the given time. + totp_flash: Option<(String, f64)>, + keys: Vec, + folders: Vec, + ports: Vec, + key_checks: KeyChecks, + /// Inline validation shows after the first Save/Test attempt. + show_errors: bool, + confirm_delete: bool, + testing: bool, + test: Arc>>>, +} + +impl Default for ProfileEditor { + fn default() -> Self { + ProfileEditor { + open: false, + is_new: true, + p: Profile::new(Protocol::Ssh), + tags: String::new(), + env: String::new(), + rooms: String::new(), + secrets: BTreeMap::new(), + totp: String::new(), + totp_show: false, + totp_saved: false, + totp_flash: None, + keys: vec![], + folders: vec![], + ports: vec![], + key_checks: HashMap::new(), + show_errors: false, + confirm_delete: false, + testing: false, + test: Default::default(), + } + } } impl ProfileEditor { - pub fn open_new(&mut self, _s: &Shared, _protocol: Option, _folder: &str) { - self.open = true; + pub fn open_new(&mut self, s: &Shared, protocol: Option, folder: &str) { + let proto = protocol.or_else(|| Protocol::from_id(&s.prefs.default_protocol)).filter(|p| *p != Protocol::Local).unwrap_or_default(); + let mut p = Profile::new(proto); + p.folder = folder.to_string(); + self.load(s, p, true); } - pub fn open_edit(&mut self, _s: &Shared, _id: &str) { - self.open = true; + pub fn open_edit(&mut self, s: &Shared, id: &str) { + if let Some(p) = s.profile(id) { + self.load(s, p.clone(), false); + } } pub fn is_open(&self) -> bool { self.open } + fn load(&mut self, s: &Shared, mut p: Profile, is_new: bool) { + prep(&mut p); + self.tags = p.tags.join(", "); + self.env = p.ssh.as_ref().map(|c| c.env_vars.iter().map(|(k, v)| format!("{k}={v}")).collect::>().join("\n")).unwrap_or_default(); + self.rooms = p.irc.as_ref().map(|c| c.auto_join_rooms.join("\n")).unwrap_or_default(); + self.totp_saved = !is_new && s.svc.has_totp(&p.id); + self.p = p; + self.is_new = is_new; + self.secrets.clear(); + self.totp.clear(); + self.totp_show = false; + self.totp_flash = None; + self.keys = keyfiles::list_ssh_keys().unwrap_or_default().iter().map(|k| k.display().to_string()).collect(); + let mut folders: Vec = s.profiles.iter().map(|p| p.folder.trim().to_string()).filter(|f| !f.is_empty()).collect(); + folders.sort_by_key(|f| f.to_lowercase()); + folders.dedup(); + self.folders = folders; + self.ports = serial::list_ports(); + self.key_checks.clear(); + self.show_errors = false; + self.confirm_delete = false; + self.open = true; + } + + /// Keeps the common fields (folder included) and resets the port to the new protocol's + /// default. Protocol configs edited earlier in this session are kept until save. + fn switch(&mut self, proto: Protocol) { + if self.p.protocol == proto { + return; + } + let d = Profile::new(proto); + let p = &mut self.p; + p.protocol = proto; + p.port = d.port; + if proto == Protocol::Github && p.host.trim().is_empty() { + p.host = d.host; + } + p.ssh = p.ssh.take().or(d.ssh); + p.telnet = p.telnet.take().or(d.telnet); + p.raw = p.raw.take().or(d.raw); + p.github = p.github.take().or(d.github); + p.vnc = p.vnc.take().or(d.vnc); + p.irc = p.irc.take().or(d.irc); + p.serial = p.serial.take().or(d.serial); + prep(p); + } + + /// The profile as it would be saved: buffers parsed, other protocols' configs dropped. + fn collect(&self) -> Profile { + let mut p = self.p.clone(); + let proto = p.protocol; + if proto != Protocol::Ssh { + p.ssh = None; + } + if proto != Protocol::Telnet { + p.telnet = None; + } + if proto != Protocol::Raw { + p.raw = None; + } + if proto != Protocol::Github { + p.github = None; + } + if proto != Protocol::Vnc { + p.vnc = None; + } + if proto != Protocol::Irc { + p.irc = None; + } + if proto != Protocol::Serial { + p.serial = None; + } + p.folder = p.folder.trim().to_string(); + p.host = p.host.trim().to_string(); + p.tags = self.tags.split(',').map(str::trim).filter(|t| !t.is_empty()).map(String::from).collect(); + if let Some(c) = &mut p.ssh { + c.env_vars = parse_env(&self.env); + if c.proxy_type == "none" { + c.proxy_type.clear(); + } + } + if let Some(c) = &mut p.irc { + c.auto_join_rooms = self.rooms.split(['\n', ',']).map(str::trim).filter(|r| !r.is_empty()).map(String::from).collect(); + } + p + } + + fn validate(&self) -> Vec { + let mut p = self.collect(); + p.normalize(); + validate_profile(&p).err().map(|e| e.errors).unwrap_or_default() + } + /// Drawn inside the right dock while open. - pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { - if ui.button("Close").clicked() { - self.open = false; + pub fn ui(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + if let Some(r) = self.test.lock().unwrap().take() { + self.testing = false; + match r { + Ok(ms) => s.success(format!("Reachable — {ms} ms")), + Err(e) => s.error(format!("Test failed: {e}")), + } + } + ui.horizontal(|ui| { + ui.label(RichText::new(if self.is_new { "New Connection" } else { "Edit Connection" }).font(theme::semibold(15.0))); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if widgets::icon_button(ui, icon::X, "Close").clicked() { + self.open = false; + } + }); + }); + ui.horizontal_wrapped(|ui| { + for proto in Protocol::SAVED { + if ui.selectable_label(self.p.protocol == proto, proto.label()).clicked() { + self.switch(proto); + } + } + }); + ui.add_space(2.0); + let errs = if self.show_errors { self.validate() } else { vec![] }; + egui::Panel::bottom("profile-editor-footer").resizable(false).frame(egui::Frame::NONE.fill(t.dock).inner_margin(egui::Margin::symmetric(0, 6))).show(ui, |ui| self.footer(ui, s, &errs)); + egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| { + ui.add_space(4.0); + self.general(ui, &errs); + match self.p.protocol { + Protocol::Ssh => self.ssh(ui, s, &errs), + Protocol::Telnet => self.telnet(ui), + Protocol::Raw => self.raw(ui, &errs), + Protocol::Github => self.github(ui, &errs), + Protocol::Vnc => self.vnc(ui, &errs), + Protocol::Irc => self.irc(ui, &errs), + Protocol::Serial => self.serial(ui, &errs), + Protocol::Local => {} + } + if matches!(self.p.protocol, Protocol::Ssh | Protocol::Telnet | Protocol::Irc | Protocol::Serial) { + self.terminal(ui, &errs); + } + let (p, tags) = (&mut self.p, &mut self.tags); + section(ui, "Notes & Tags", |ui| { + text(ui, "Tags", tags, "production, london, vpn"); + widgets::hint(ui, "Comma-separated tags for filtering"); + label(ui, "Notes"); + ui.add(TextEdit::multiline(&mut p.notes).hint_text("Free-form notes…").desired_rows(4).desired_width(f32::INFINITY)); + }); + }); + } + + fn footer(&mut self, ui: &mut Ui, s: &mut Shared, errs: &[ValidationError]) { + let t = Tokens::get(ui.ctx()); + if !errs.is_empty() { + let all = errs.iter().map(|e| e.message.clone()).collect::>().join("\n"); + let n = errs.len(); + ui.label(RichText::new(format!("{} {n} problem{} — see the highlighted fields", icon::WARNING_CIRCLE, if n == 1 { "" } else { "s" })).color(t.danger).size(12.0)).on_hover_text(all); + } + if self.confirm_delete { + ui.horizontal(|ui| { + ui.label(RichText::new(format!("Delete \"{}\"? This can't be undone.", self.p.display_name)).color(t.danger)); + }); + ui.horizontal(|ui| { + if ui.add(Button::new(RichText::new(format!("{} Delete", icon::TRASH)).color(t.accent_text)).fill(t.danger)).clicked() { + match s.svc.delete_profile(&self.p.id) { + Ok(()) => { + s.success("Profile deleted"); + s.request(Request::ReloadProfiles); + self.open = false; + } + Err(e) => s.error(format!("Failed to delete profile: {e}")), + } + self.confirm_delete = false; + } + if ui.button("Keep").clicked() { + self.confirm_delete = false; + } + }); + return; + } + ui.horizontal(|ui| { + if !self.is_new && ui.add(Button::new(RichText::new(icon::TRASH).color(t.danger))).on_hover_text("Delete profile").clicked() { + self.confirm_delete = true; + } + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + let connect = if self.is_new { "Create & Connect" } else { "Save & Connect" }; + if widgets::primary(ui, icon::PLUGS_CONNECTED, connect).clicked() { + self.save(s, true); + } + if widgets::button(ui, icon::FLOPPY_DISK, if self.is_new { "Create" } else { "Save" }).clicked() { + self.save(s, false); + } + let can_test = !self.testing && self.p.protocol != Protocol::Serial; + if ui.add_enabled(can_test, Button::new(format!("{} {}", icon::PULSE, if self.testing { "Testing…" } else { "Test" }))).clicked() { + self.test_connection(s); + } + if ui.button("Cancel").clicked() { + self.open = false; + } + }); + }); + } + + fn save(&mut self, s: &mut Shared, and_connect: bool) { + self.show_errors = true; + let errs = self.validate(); + if let Some(e) = errs.first() { + s.warn(format!("Please fix the highlighted fields ({}).", e.message)); + return; + } + let p = self.collect(); + let r = if self.is_new { s.svc.create_profile(p) } else { s.svc.update_profile(p) }; + match r { + Err(e) => s.error(format!("Save failed: {e}")), + Ok(saved) => { + for (kind, v) in &self.secrets { + if !v.is_empty() + && let Err(e) = s.svc.set_profile_secret(&saved.id, kind, v) + { + s.error(format!("Couldn't store the {kind} secret: {e}")); + } + } + s.success(if self.is_new { "Profile created" } else { "Profile updated" }); + s.request(Request::ReloadProfiles); + self.open = false; + if and_connect { + s.request(Request::Connect(saved.id)); + } + } + } + } + + fn test_connection(&mut self, s: &mut Shared) { + self.show_errors = true; + let p = self.collect(); + let (host, port) = match &p.vnc { + Some(c) if p.protocol == Protocol::Vnc => (if c.vnc_host.trim().is_empty() { p.host.clone() } else { c.vnc_host.trim().to_string() }, c.vnc_port), + _ => (p.host.clone(), p.port), + }; + s.info(format!("Testing connection to {host}:{port}…")); + self.testing = true; + let (slot, svc, ctx) = (self.test.clone(), s.svc.clone(), s.ctx.clone()); + std::thread::spawn(move || { + let r = svc.test_connection(&host, port).map_err(|e| e.to_string()); + *slot.lock().unwrap() = Some(r); + ctx.request_repaint(); + }); + } + + // --- sections --------------------------------------------------------------------------- + + fn general(&mut self, ui: &mut Ui, errs: &[ValidationError]) { + let Self { p, folders, ports, .. } = self; + section(ui, "General", |ui| { + text(ui, "Display Name", &mut p.display_name, "My Server"); + if p.protocol == Protocol::Serial { + label(ui, "Device"); + let mut refresh = false; + suggest(ui, &mut p.host, "/dev/ttyUSB0, /dev/cu.usbserial…, COM3", ports, |ui, _| { + refresh = widgets::icon_button(ui, icon::ARROWS_CLOCKWISE, "Refresh the device list").clicked(); + }); + if refresh { + *ports = serial::list_ports(); + } + if ports.is_empty() { + widgets::hint(ui, "No serial devices found. Type a device path or plug one in and refresh."); + } + err(ui, errs, "host"); + } else { + let hint = if p.protocol == Protocol::Github { "github.com" } else { "192.168.1.1 or example.com" }; + text(ui, "Host", &mut p.host, hint); + err(ui, errs, "host"); + if p.protocol != Protocol::Github { + num(ui, "Port", &mut p.port, 1..=65535); + err(ui, errs, "port"); + text(ui, "Username", &mut p.username, "root"); + err(ui, errs, "username"); + } + } + label(ui, "Group"); + suggest(ui, &mut p.folder, "e.g. Production, Staging, Home Lab", folders, |_, _| {}); + widgets::hint(ui, "Choose an existing group or type a new name to create one"); + text(ui, "Description", &mut p.description, "Optional description"); + err(ui, errs, "displayName"); + }); + } + + fn ssh(&mut self, ui: &mut Ui, s: &mut Shared, errs: &[ValidationError]) { + let Self { p, keys, key_checks, secrets, env, totp, totp_show, totp_saved, totp_flash, is_new, .. } = self; + let id = p.id.clone(); + let c = p.ssh.get_or_insert_with(SshConfig::defaults); + section(ui, "Authentication", |ui| { + mode(ui, "Password Mode", &mut c.secret_mode); + if c.secret_mode == SecretMode::Saved { + secret(ui, "Password", secrets, "password", "Leave blank to keep the saved password"); + } + label(ui, "Auth Methods"); + ui.horizontal_wrapped(|ui| { + for (m, l) in [("publickey", "Public key"), ("password", "Password"), ("keyboard-interactive", "Keyboard-interactive")] { + let mut on = c.auth_methods.iter().any(|x| x == m); + if ui.checkbox(&mut on, l).changed() { + if on { + c.auth_methods.push(m.into()); + } else { + c.auth_methods.retain(|x| x != m); + } + } + } + }); + err(ui, errs, "ssh.authMethods"); + }); + section(ui, "Key & Passphrase", |ui| { + label(ui, "Private Key Path"); + key_path(ui, &mut c.private_key_path, keys, key_checks, "/Users/username/.ssh/id_ed25519"); + widgets::hint(ui, "Select a key from your .ssh folder or type a full path"); + mode(ui, "Passphrase Mode", &mut c.key_secret_mode); + if c.key_secret_mode == SecretMode::Saved { + secret(ui, "Passphrase", secrets, "passphrase", "Leave blank to keep the saved passphrase"); + } + }); + section(ui, "Host Verification", |ui| { + let opts: Vec<_> = HostKeyPolicy::ALL.iter().map(|k| (*k, k.label())).collect(); + combo(ui, "Host Key Policy", &mut c.host_key_policy, &opts); + }); + section(ui, "Jump Hosts", |ui| { + let n = c.jump_hosts.len(); + let mut act = None; + for (i, jh) in c.jump_hosts.iter_mut().enumerate() { + item(ui, |ui| { + rtl(ui, |ui| { + act = row_buttons(ui, i, n).or(act.take()); + ui.add(TextEdit::singleline(&mut jh.username).hint_text("User").desired_width(ui.available_width() * 0.35)); + ui.add(DragValue::new(&mut jh.port).range(1..=65535)); + ui.add(TextEdit::singleline(&mut jh.host).hint_text("Host").desired_width(ui.available_width())); + }); + key_path(ui, &mut jh.private_key_path, keys, key_checks, "Full key path (optional)"); + }); + } + apply(&mut c.jump_hosts, act); + list_errs(ui, errs, "ssh.jumpHosts"); + if ui.button(format!("{} Add Jump Host", icon::PLUS)).clicked() { + c.jump_hosts.push(JumpHost { port: 22, auth_method: "publickey".into(), secret_mode: SecretMode::AskEveryTime, ..Default::default() }); + } + }); + section(ui, "Port Forwarding", |ui| { + let n = c.port_forwards.len(); + let mut act = None; + for (i, pf) in c.port_forwards.iter_mut().enumerate() { + item(ui, |ui| { + ui.horizontal(|ui| { + let opts = [(ForwardDirection::Local, "Local →"), (ForwardDirection::Remote, "← Remote")]; + ComboBox::from_id_salt(("pf-dir", i)).width(84.0).selected_text(if pf.direction == ForwardDirection::Local { opts[0].1 } else { opts[1].1 }).show_ui(ui, |ui| { + for (v, l) in opts { + ui.selectable_value(&mut pf.direction, v, l); + } + }); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + act = row_buttons(ui, i, n).or(act.take()); + ui.checkbox(&mut pf.enabled, "Active").on_hover_text("Start on connect"); + ui.add(TextEdit::singleline(&mut pf.label).hint_text("Label (optional)").desired_width(ui.available_width())); + }); + }); + rtl(ui, |ui| { + // Two host fields share what the ports and arrow leave. + let w = ((ui.available_width() - 150.0) / 2.0).max(40.0); + ui.add(DragValue::new(&mut pf.dest_port).range(1..=65535)); + ui.add(TextEdit::singleline(&mut pf.dest_host).hint_text("Dest host").desired_width(w)); + ui.label("⟶"); + ui.add(DragValue::new(&mut pf.bind_port).range(1..=65535)); + ui.add(TextEdit::singleline(&mut pf.bind_host).hint_text("Bind host").desired_width(ui.available_width())); + }); + }); + } + apply(&mut c.port_forwards, act); + list_errs(ui, errs, "ssh.portForwards"); + if ui.button(format!("{} Add Port Forward", icon::PLUS)).clicked() { + c.port_forwards.push(PortForward { id: new_id(), bind_host: "127.0.0.1".into(), bind_port: 1, dest_port: 1, enabled: true, ..Default::default() }); + } + }); + section(ui, "Startup", |ui| { + text(ui, "Startup Command", &mut c.startup_command, "Optional command to run after connecting"); + label(ui, "Environment Variables"); + ui.add(TextEdit::multiline(env).hint_text("KEY=VALUE (one per line)").desired_rows(3).desired_width(f32::INFINITY).font(egui::TextStyle::Monospace)); + }); + section(ui, "Login Script", |ui| login_script(ui, "ssh-login", &mut c.login_scripts)); + section(ui, "SSH Agent", |ui| { + check(ui, "Use SSH Agent for Authentication", &mut c.agent_auth, "Authenticate using keys from the local SSH agent (ssh-agent)"); + check(ui, "Forward Agent to Remote", &mut c.agent_forwarding, "Allow the remote host to use your local SSH agent keys (equivalent to ssh -A)"); + }); + section(ui, "TOTP / 2FA", |ui| { + widgets::hint(ui, "Configure a TOTP secret to auto-fill 2FA codes during SSH keyboard-interactive authentication. The secret is stored encrypted."); + label(ui, "TOTP Secret"); + let hint = if *totp_saved { "••••••••• (secret saved)" } else { "Base32-encoded secret (e.g. from authenticator app setup)" }; + rtl(ui, |ui| { + if widgets::icon_button(ui, if *totp_show { icon::EYE_SLASH } else { icon::EYE }, "Show/hide secret").clicked() { + *totp_show = !*totp_show; + } + ui.add(TextEdit::singleline(totp).password(!*totp_show).hint_text(hint).desired_width(ui.available_width())); + }); + ui.horizontal(|ui| { + let flash = totp_flash.as_ref().filter(|(_, until)| *until > s.now).map(|(t, _)| t.clone()); + if let Some((_, until)) = totp_flash { + ui.ctx().request_repaint_after(std::time::Duration::from_secs_f64((*until - s.now).max(0.0))); + } + let test = ui.add(Button::new(RichText::new(flash.as_deref().unwrap_or("Test")).font(theme::mono(12.5)))).on_hover_text("Generate a code with the saved secret"); + if test.clicked() { + *totp_flash = Some(match (*is_new, s.svc.totp_code(&id)) { + (false, Ok(code)) => (code, s.now + 5.0), + _ => ("Save first".into(), s.now + 2.0), + }); + } + if ui.button("Save").on_hover_text("Encrypt and save the TOTP secret").clicked() { + let v = totp.trim().to_string(); + if *is_new { + s.warn("Save the profile first, then set the TOTP secret."); + } else if v.is_empty() { + s.svc.delete_totp(&id); + *totp_saved = false; + s.info("TOTP secret removed."); + } else { + match s.svc.set_profile_secret(&id, "totp", &v) { + Ok(()) => { + totp.clear(); + *totp_saved = true; + s.success("TOTP secret saved securely."); + } + Err(e) => s.error(format!("Failed to save TOTP secret: {e}")), + } + } + } + if ui.add_enabled(!*is_new, Button::new("Delete")).on_hover_text("Remove the TOTP secret").clicked() { + s.svc.delete_totp(&id); + *totp_saved = false; + s.info("TOTP secret removed."); + } + }); + }); + section(ui, "Proxy", |ui| { + combo_s(ui, "Proxy Type", &mut c.proxy_type, &[("none", "None (direct connection)"), ("socks5", "SOCKS5"), ("http", "HTTP CONNECT")]); + if c.proxy_type != "none" { + text(ui, "Proxy Host", &mut c.proxy_host, "proxy.example.com"); + num(ui, "Proxy Port", &mut c.proxy_port, 1..=65535); + text(ui, "Proxy Username", &mut c.proxy_username, "Optional"); + label(ui, "Proxy Password"); + ui.add(TextEdit::singleline(&mut c.proxy_password).password(true).hint_text("Optional").desired_width(f32::INFINITY)); + } + }); + section(ui, "Advanced", |ui| { + text(ui, "Terminal Type", &mut c.terminal_type, DEFAULT_TERMINAL_TYPE); + num(ui, "Keepalive Interval (s)", &mut c.keepalive_interval, 0..=86400); + num(ui, "Max Missed Keepalives", &mut c.keepalive_count_max, 0..=1000); + check(ui, "Compression", &mut c.compression, ""); + reconnect(ui, &mut c.auto_reconnect, &mut c.max_reconnect_tries); + }); + } + + fn telnet(&mut self, ui: &mut Ui) { + let c = self.p.telnet.get_or_insert_with(TelnetConfig::defaults); + section(ui, "Login", |ui| { + check(ui, "Local Echo", &mut c.local_echo, ""); + check(ui, "Line Mode", &mut c.line_mode, ""); + }); + section(ui, "Terminal & Encoding", |ui| { + text(ui, "Terminal Type", &mut c.terminal_type, DEFAULT_TERMINAL_TYPE); + combo_s(ui, "Encoding", &mut c.encoding, ENCODINGS); + }); + section(ui, "Echo & Line Endings", |ui| line_ending(ui, &mut c.line_ending)); + section(ui, "Negotiation", |ui| check(ui, "Debug Negotiation", &mut c.negotiation_debug, "")); + section(ui, "Login Script", |ui| login_script(ui, "telnet-login", &mut c.login_scripts)); + section(ui, "Reconnection", |ui| reconnect(ui, &mut c.auto_reconnect, &mut c.max_reconnect_tries)); + } + + fn raw(&mut self, ui: &mut Ui, errs: &[ValidationError]) { + let p = &mut self.p; + let c = p.raw.get_or_insert_with(RawConfig::defaults); + section(ui, "Encoding & Framing", |ui| { + combo_s(ui, "Encoding", &mut c.encoding, &[("utf-8", "UTF-8"), ("ascii", "ASCII"), ("hex", "Hex")]); + line_ending(ui, &mut c.line_ending); + }); + section(ui, "Display", |ui| { + check(ui, "Show Timestamps", &mut c.show_timestamps, ""); + check(ui, "Show Direction", &mut c.show_direction, ""); + }); + section(ui, "Payload Presets", |ui| { + let n = c.payload_presets.len(); + let mut act = None; + for (i, ps) in c.payload_presets.iter_mut().enumerate() { + rtl(ui, |ui| { + act = row_buttons(ui, i, n).or(act.take()); + ComboBox::from_id_salt(("preset-fmt", i)).width(76.0).selected_text(fmt_label(&ps.format)).show_ui(ui, |ui| { + for f in ["text", "hex", "escaped"] { + ui.selectable_value(&mut ps.format, f.to_string(), fmt_label(f)); + } + }); + let w = (ui.available_width() - ui.spacing().item_spacing.x) * 0.6; + ui.add(TextEdit::singleline(&mut ps.payload).hint_text("Payload").font(egui::TextStyle::Monospace).desired_width(w)); + ui.add(TextEdit::singleline(&mut ps.name).hint_text("Name").desired_width(ui.available_width())); + }); + } + apply(&mut c.payload_presets, act); + list_errs(ui, errs, "raw.payloadPresets"); + if ui.button(format!("{} Add Preset", icon::PLUS)).clicked() { + c.payload_presets.push(PayloadPreset { id: new_id(), format: "text".into(), ..Default::default() }); + } + }); + section(ui, "Timeouts & Reconnect", |ui| { + num(ui, "Connect Timeout (s)", &mut p.connect_timeout, 0..=3600); + num(ui, "Read Timeout (s)", &mut p.read_timeout, 0..=3600); + check(ui, "Auto-reconnect", &mut c.auto_reconnect, ""); + }); + } + + fn github(&mut self, ui: &mut Ui, errs: &[ValidationError]) { + let Self { p, secrets, .. } = self; + let c = p.github.get_or_insert_with(GitHubConfig::defaults); + section(ui, "API", |ui| { + text(ui, "API Base URL", &mut c.base_url, "https://api.github.com"); + err(ui, errs, "github.baseURL"); + mode(ui, "Token Storage", &mut c.secret_mode); + if c.secret_mode == SecretMode::Saved { + secret(ui, "Personal Access Token", secrets, "github-token", "Leave blank to keep the saved token"); + } + }); + section(ui, "Refresh", |ui| { + num(ui, "Refresh Interval (seconds)", &mut c.refresh_interval_secs, 60..=3600); + err(ui, errs, "github.refreshIntervalSecs"); + }); + } + + fn vnc(&mut self, ui: &mut Ui, errs: &[ValidationError]) { + let Self { p, keys, key_checks, secrets, .. } = self; + let c = p.vnc.get_or_insert_with(VncConfig::defaults); + section(ui, "VNC Server", |ui| { + text(ui, "VNC Host", &mut c.vnc_host, "192.168.1.100 (defaults to Host)"); + err(ui, errs, "vnc.vncHost"); + num(ui, "VNC Port", &mut c.vnc_port, 1..=65535); + err(ui, errs, "vnc.vncPort"); + mode(ui, "Password Storage", &mut c.secret_mode); + if c.secret_mode == SecretMode::Saved { + secret(ui, "VNC Password", secrets, "vnc-password", "Leave blank to keep the saved password"); + } + }); + section(ui, "Display", |ui| { + check(ui, "View Only", &mut c.view_only, ""); + check(ui, "Scale to Fit", &mut c.scale_to_fit, ""); + check(ui, "Shared Session", &mut c.shared_session, ""); + num(ui, "Quality (0-9)", &mut c.quality, 0..=9); + err(ui, errs, "vnc.quality"); + }); + section(ui, "SSH Tunnel", |ui| { + tunnel(ui, "vnc", Tunnel { on: &mut c.ssh_tunnel, host: &mut c.ssh_host, port: &mut c.ssh_port, user: &mut c.ssh_username, auth: &mut c.ssh_auth_method, key: &mut c.ssh_private_key, agent: &mut c.ssh_agent_auth, mode: &mut c.ssh_secret_mode }, keys, key_checks, secrets, errs); + }); + } + + fn irc(&mut self, ui: &mut Ui, errs: &[ValidationError]) { + let Self { p, keys, key_checks, secrets, rooms, .. } = self; + let port = &mut p.port; + let c = p.irc.get_or_insert_with(IrcConfig::defaults); + section(ui, "IRC", |ui| { + text(ui, "Nick", &mut c.nick, "yourNick"); + err(ui, errs, "irc.nick"); + text(ui, "Real Name", &mut c.real_name, "Shown in WHOIS"); + err(ui, errs, "irc.realName"); + if ui.checkbox(&mut c.use_tls, "Use TLS").changed() { + // Follow the protocol default unless the port was customised. + match (c.use_tls, *port) { + (true, p) if p == i64::from(DEFAULT_IRC_PORT) => *port = DEFAULT_IRC_TLS_PORT.into(), + (false, p) if p == i64::from(DEFAULT_IRC_TLS_PORT) => *port = DEFAULT_IRC_PORT.into(), + _ => {} + } + } + widgets::hint(ui, "Use port 6697 for most TLS IRC networks."); + check(ui, "Server Password", &mut c.server_password_required, "Prompt for an IRC PASS secret before registration."); + if c.server_password_required { + mode(ui, "Server Password Storage", &mut c.server_password_mode); + if c.server_password_mode == SecretMode::Saved { + secret(ui, "Server Password", secrets, "irc-server-password", "Leave blank to keep the saved password"); + } + } + }); + section(ui, "NickServ", |ui| { + check(ui, "Identify on Connect", &mut c.nick_serv_identify, ""); + if c.nick_serv_identify { + text(ui, "Account", &mut c.nick_serv_account, "Optional if it matches your nick"); + err(ui, errs, "irc.nickServAccount"); + mode(ui, "Password Storage", &mut c.nick_serv_mode); + if c.nick_serv_mode == SecretMode::Saved { + secret(ui, "NickServ Password", secrets, "irc-nickserv-password", "Leave blank to keep the saved password"); + } + } + }); + section(ui, "Rooms", |ui| { + label(ui, "Auto-Join Rooms"); + ui.add(TextEdit::multiline(rooms).hint_text("#general\n#ops").desired_rows(4).desired_width(f32::INFINITY)); + widgets::hint(ui, "One room per line or comma-separated."); + let names: Vec<&str> = rooms.split(['\n', ',']).map(str::trim).filter(|r| !r.is_empty()).collect(); + for e in errs.iter().filter(|e| e.field.starts_with("irc.autoJoinRooms")) { + let room = index_of(&e.field).and_then(|i| names.get(i)).copied().unwrap_or(""); + error_line(ui, &format!("{room}: {}", e.message)); + } + }); + section(ui, "SSH Tunnel", |ui| { + tunnel(ui, "irc", Tunnel { on: &mut c.ssh_tunnel, host: &mut c.ssh_host, port: &mut c.ssh_port, user: &mut c.ssh_username, auth: &mut c.ssh_auth_method, key: &mut c.ssh_private_key, agent: &mut c.ssh_agent_auth, mode: &mut c.ssh_secret_mode }, keys, key_checks, secrets, errs); + }); + section(ui, "Reconnection", |ui| reconnect(ui, &mut c.auto_reconnect, &mut c.max_reconnect_tries)); + } + + fn serial(&mut self, ui: &mut Ui, errs: &[ValidationError]) { + let c = self.p.serial.get_or_insert_with(SerialConfig::default); + section(ui, "Line Settings", |ui| { + label(ui, "Baud Rate"); + ui.horizontal(|ui| { + ComboBox::from_id_salt("serial-baud").selected_text(if BAUDS.contains(&c.baud_rate) { c.baud_rate.to_string() } else { "Custom".into() }).show_ui(ui, |ui| { + for b in BAUDS { + ui.selectable_value(&mut c.baud_rate, b, b.to_string()); + } + }); + ui.add(DragValue::new(&mut c.baud_rate).range(1..=20_000_000)).on_hover_text("Any rate the adapter supports"); + }); + err(ui, errs, "serial.baudRate"); + combo(ui, "Data Bits", &mut c.data_bits, &[(5, "5"), (6, "6"), (7, "7"), (8, "8")]); + err(ui, errs, "serial.dataBits"); + combo(ui, "Parity", &mut c.parity, &[(Parity::None, "None"), (Parity::Odd, "Odd"), (Parity::Even, "Even")]); + combo(ui, "Stop Bits", &mut c.stop_bits, &[(1, "1"), (2, "2")]); + err(ui, errs, "serial.stopBits"); + combo(ui, "Flow Control", &mut c.flow_control, &[(FlowControl::None, "None"), (FlowControl::Xonxoff, "XON/XOFF (software)"), (FlowControl::Rtscts, "RTS/CTS (hardware)")]); + }); + section(ui, "Behaviour", |ui| { + line_ending(ui, &mut c.line_ending); + combo_s(ui, "Encoding", &mut c.encoding, ENCODINGS); + check(ui, "Local Echo", &mut c.local_echo, "Show typed characters locally (for devices that don't echo)"); + check(ui, "Assert DTR on connect", &mut c.dtr_on_connect, ""); + check(ui, "Assert RTS on connect", &mut c.rts_on_connect, ""); + reconnect(ui, &mut c.auto_reconnect, &mut c.max_reconnect_tries); + widgets::hint(ui, "Reconnects when the device is plugged back in."); + }); + } + + fn terminal(&mut self, ui: &mut Ui, errs: &[ValidationError]) { + let p = &mut self.p; + section(ui, "Terminal", |ui| { + text(ui, "Font Family", &mut p.terminal_font_family, DEFAULT_FONT_FAMILY); + num(ui, "Font Size", &mut p.terminal_font_size, 8..=36); + err(ui, errs, "terminalFontSize"); + num(ui, "Scrollback Lines", &mut p.terminal_scrollback, 0..=100_000); + err(ui, errs, "terminalScrollback"); + combo_s(ui, "Cursor Style", &mut p.terminal_cursor_style, &[("block", "Block"), ("underline", "Underline"), ("bar", "Bar")]); + }); + } +} + +const ENCODINGS: &[(&str, &str)] = &[("utf-8", "UTF-8"), ("ascii", "ASCII"), ("iso-8859-1", "ISO 8859-1")]; +const BAUDS: [u32; 14] = [300, 1200, 2400, 4800, 9600, 14400, 19200, 38400, 57600, 115200, 230400, 460800, 921600, 1_000_000]; + +/// Fills the zero values the JS form defaulted (`|| 5`, `|| 1080`…), so range-clamped inputs +/// start from the same numbers. +fn prep(p: &mut Profile) { + fn tries(n: &mut i64) { + if *n <= 0 { + *n = 5; + } + } + fn port(n: &mut i64, d: i64) { + if *n <= 0 { + *n = d; + } + } + fn steps(v: &mut [LoginScriptStep]) { + for s in v.iter_mut().filter(|s| s.kind == "wait" && s.timeout <= 0) { + s.timeout = 10; + } + } + if p.terminal_font_size == 0 { + p.terminal_font_size = DEFAULT_FONT_SIZE; + } + if p.terminal_font_family.is_empty() { + p.terminal_font_family = DEFAULT_FONT_FAMILY.into(); + } + if p.terminal_cursor_style.is_empty() { + p.terminal_cursor_style = DEFAULT_CURSOR_STYLE.into(); + } + if let Some(c) = &mut p.ssh { + tries(&mut c.max_reconnect_tries); + port(&mut c.proxy_port, 1080); + if c.proxy_type.is_empty() { + c.proxy_type = "none".into(); + } + for j in &mut c.jump_hosts { + port(&mut j.port, 22); + } + steps(&mut c.login_scripts); + } + if let Some(c) = &mut p.telnet { + tries(&mut c.max_reconnect_tries); + steps(&mut c.login_scripts); + } + if let Some(c) = &mut p.irc { + tries(&mut c.max_reconnect_tries); + port(&mut c.ssh_port, 22); + } + if let Some(c) = &mut p.vnc { + port(&mut c.vnc_port, DEFAULT_VNC_PORT.into()); + port(&mut c.ssh_port, 22); + } + if let Some(c) = &mut p.github + && c.refresh_interval_secs == 0 + { + c.refresh_interval_secs = 300; + } + if let Some(c) = &mut p.serial { + tries(&mut c.max_reconnect_tries); + } +} + +fn parse_env(text: &str) -> BTreeMap { + text.lines() + .filter_map(|l| l.split_once('=')) + .map(|(k, v)| (k.trim().to_string(), v.trim().to_string())) + .filter(|(k, _)| !k.is_empty()) + .collect() +} + +/// "ssh.jumpHosts[2].host" → 2 +fn index_of(field: &str) -> Option { + field.split_once('[')?.1.split_once(']')?.0.parse().ok() +} + +// --- form widgets ----------------------------------------------------------------------------- + +/// A collapsible card with a section header. +fn section(ui: &mut Ui, title: &str, body: impl FnOnce(&mut Ui)) { + let t = Tokens::get(ui.ctx()); + egui::Frame::NONE.fill(t.card).stroke(Stroke::new(1.0, t.card_border)).corner_radius(6).inner_margin(10).show(ui, |ui| { + ui.set_width(ui.available_width()); + egui::CollapsingHeader::new(RichText::new(title.to_uppercase()).font(theme::semibold(10.5)).color(t.text_faint).extra_letter_spacing(0.6)) + .id_salt(("pe-section", title)) + .default_open(true) + .show(ui, |ui| { + ui.spacing_mut().item_spacing.y = 4.0; + body(ui) + }); + }); + ui.add_space(6.0); +} + +/// A list entry (jump host, forward) on a faint background. +fn item(ui: &mut Ui, body: impl FnOnce(&mut Ui)) { + let t = Tokens::get(ui.ctx()); + egui::Frame::NONE.fill(t.text.gamma_multiply(0.04)).corner_radius(4).inner_margin(6).show(ui, |ui| { + ui.set_width(ui.available_width()); + body(ui) + }); +} + +fn label(ui: &mut Ui, text: &str) { + let t = Tokens::get(ui.ctx()); + ui.add_space(2.0); + ui.label(RichText::new(text).color(t.text_dim).size(12.0)); +} + +fn error_line(ui: &mut Ui, msg: &str) { + let t = Tokens::get(ui.ctx()); + ui.label(RichText::new(format!("{} {msg}", icon::WARNING_CIRCLE)).color(t.danger).size(11.5)); +} + +fn err(ui: &mut Ui, errs: &[ValidationError], field: &str) { + for e in errs.iter().filter(|e| e.field == field) { + error_line(ui, &e.message); + } +} + +/// Errors for a list's rows ("ssh.jumpHosts[1].port" → "Row 2: …"). +fn list_errs(ui: &mut Ui, errs: &[ValidationError], prefix: &str) { + for e in errs.iter().filter(|e| e.field.starts_with(prefix)) { + match index_of(&e.field) { + Some(i) => error_line(ui, &format!("Row {}: {}", i + 1, e.message)), + None => error_line(ui, &e.message), } } } + +fn text(ui: &mut Ui, l: &str, v: &mut String, hint: &str) -> Response { + label(ui, l); + ui.add(TextEdit::singleline(v).hint_text(hint).desired_width(f32::INFINITY)) +} + +fn num(ui: &mut Ui, l: &str, v: &mut i64, range: std::ops::RangeInclusive) { + label(ui, l); + ui.add(DragValue::new(v).range(range).speed(0.25)); +} + +fn check(ui: &mut Ui, l: &str, v: &mut bool, help: &str) { + ui.checkbox(v, l); + if !help.is_empty() { + widgets::hint(ui, help); + } +} + +fn combo(ui: &mut Ui, l: &str, v: &mut T, opts: &[(T, &str)]) { + label(ui, l); + let cur = opts.iter().find(|o| o.0 == *v).map_or("", |o| o.1); + ComboBox::from_id_salt(("pe-combo", l)).selected_text(cur).width(ui.available_width().min(320.0)).show_ui(ui, |ui| { + for (o, text) in opts { + ui.selectable_value(v, *o, *text); + } + }); +} + +fn combo_s(ui: &mut Ui, l: &str, v: &mut String, opts: &[(&str, &str)]) { + label(ui, l); + let cur = opts.iter().find(|o| o.0 == v).map_or(v.clone(), |o| o.1.to_string()); + ComboBox::from_id_salt(("pe-combo", l)).selected_text(cur).width(ui.available_width().min(320.0)).show_ui(ui, |ui| { + for (o, text) in opts { + ui.selectable_value(v, o.to_string(), *text); + } + }); +} + +fn mode(ui: &mut Ui, l: &str, v: &mut SecretMode) { + let opts: Vec<_> = SecretMode::ALL.iter().map(|m| (*m, m.label())).collect(); + combo(ui, l, v, &opts); +} + +fn line_ending(ui: &mut Ui, v: &mut LineEnding) { + let opts: Vec<_> = LineEnding::ALL.iter().map(|e| (*e, e.label())).collect(); + combo(ui, "Line Ending", v, &opts); +} + +fn reconnect(ui: &mut Ui, on: &mut bool, tries: &mut i64) { + check(ui, "Auto-Reconnect", on, "Automatically reconnect with exponential backoff when the connection drops"); + if *on { + num(ui, "Max Reconnect Attempts", tries, 1..=20); + } +} + +/// A password field whose value is stored in the vault/keychain under `kind` on save. +fn secret(ui: &mut Ui, l: &str, secrets: &mut BTreeMap<&'static str, String>, kind: &'static str, hint: &str) { + label(ui, l); + ui.add(TextEdit::singleline(secrets.entry(kind).or_default()).password(true).hint_text(hint).desired_width(f32::INFINITY)); +} + +/// A text field with a drop-down of suggestions (an HTML datalist). `extra` draws trailing +/// buttons, right to left, so the field takes exactly the width they leave. +fn suggest(ui: &mut Ui, v: &mut String, hint: &str, opts: &[String], extra: impl FnOnce(&mut Ui, &mut String)) -> Response { + rtl(ui, |ui| { + extra(ui, v); + ui.add_enabled_ui(!opts.is_empty(), |ui| { + ui.menu_button(icon::CARET_DOWN, |ui| { + egui::ScrollArea::vertical().max_height(280.0).show(ui, |ui| { + for o in opts { + if ui.selectable_label(v == o, o).clicked() { + *v = o.clone(); + ui.close(); + } + } + }); + }) + .response + .on_hover_text("Suggestions") + }); + ui.add(TextEdit::singleline(v).hint_text(hint).desired_width(ui.available_width())) + }) +} + +/// A row laid out right to left: add fixed-size widgets first, then one that fills the rest. +fn rtl(ui: &mut Ui, body: impl FnOnce(&mut Ui) -> R) -> R { + ui.horizontal(|ui| ui.with_layout(Layout::right_to_left(Align::Center), body).inner).inner +} + +/// Key path with `~/.ssh` suggestions, Browse, and the JS checks once the field loses focus. +fn key_path(ui: &mut Ui, v: &mut String, keys: &[String], checks: &mut KeyChecks, hint: &str) { + let r = suggest(ui, v, hint, keys, |ui, v| { + if ui.button("Browse").clicked() + && let Some(p) = rfd::FileDialog::new().set_title("Select SSH Private Key").pick_file() + { + *v = p.display().to_string(); + } + }); + let path = v.trim(); + if path.is_empty() || r.has_focus() { + return; + } + let t = Tokens::get(ui.ctx()); + match checks.entry(path.to_string()).or_insert_with(|| check_key(path)) { + Ok(m) => ui.label(RichText::new(format!("{} {m}", icon::CHECK_CIRCLE)).color(t.success).size(11.5)), + Err(m) => ui.label(RichText::new(format!("{} {m}", icon::WARNING_CIRCLE)).color(t.danger).size(11.5)), + }; +} + +fn check_key(path: &str) -> Result<&'static str, &'static str> { + if path.starts_with('~') { + Err("Please use a full path (no ~).") + } else if !Path::new(path).exists() { + Err("Key file not found.") + } else if !keyfiles::is_likely_private_key(Path::new(path)) { + Err("File is not a recognised private key.") + } else { + Ok("Key verified.") + } +} + +enum Act { + Up(usize), + Down(usize), + Remove(usize), +} + +fn row_buttons(ui: &mut Ui, i: usize, n: usize) -> Option { + let mut a = None; + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 2.0; + let mut order = [0, 1, 2]; + // Always ▲ ▼ ✕ on screen, also inside right-to-left rows. + if ui.layout().prefer_right_to_left() { + order.reverse(); + } + for k in order { + let (glyph, tip, enabled, act) = match k { + 0 => (icon::ARROW_UP, "Move up", i > 0, Act::Up(i)), + 1 => (icon::ARROW_DOWN, "Move down", i + 1 < n, Act::Down(i)), + _ => (icon::X, "Remove", true, Act::Remove(i)), + }; + if ui.add_enabled(enabled, Button::new(glyph).small()).on_hover_text(tip).clicked() { + a = Some(act); + } + } + }); + a +} + +fn apply(v: &mut Vec, a: Option) { + match a { + Some(Act::Up(i)) if i > 0 && i < v.len() => v.swap(i - 1, i), + Some(Act::Down(i)) if i + 1 < v.len() => v.swap(i, i + 1), + Some(Act::Remove(i)) if i < v.len() => { + v.remove(i); + } + _ => {} + } +} + +fn fmt_label(f: &str) -> &'static str { + match f { + "hex" => "Hex", + "escaped" => "Escaped", + _ => "Text", + } +} + +/// Expect-style steps: wait for text (timeout 1–120 s) or send text. +fn login_script(ui: &mut Ui, id: &str, steps: &mut Vec) { + widgets::hint(ui, "Automate login sequences: wait for text from the server, then send a response. Steps execute in order after connection."); + if steps.is_empty() { + widgets::hint(ui, "No login script steps configured."); + } + let n = steps.len(); + let mut act = None; + // Rows are fixed-width columns around a value field that fills the rest, so they line up. + for (i, st) in steps.iter_mut().enumerate() { + ui.horizontal(|ui| { + let wait = st.kind == "wait"; + ComboBox::from_id_salt((id, i)).width(84.0).selected_text(if wait { "Wait for" } else { "Send" }).show_ui(ui, |ui| { + ui.selectable_value(&mut st.kind, "wait".into(), "Wait for"); + ui.selectable_value(&mut st.kind, "send".into(), "Send"); + }); + if st.kind == "wait" && st.timeout <= 0 { + st.timeout = 10; + } + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + act = row_buttons(ui, i, n).or(act.take()); + if wait { + ui.add_sized(vec2(56.0, 22.0), DragValue::new(&mut st.timeout).range(1..=120).suffix(" s")).on_hover_text("Timeout (seconds)"); + } else { + ui.add_space(56.0 + ui.spacing().item_spacing.x); + } + ui.add(TextEdit::singleline(&mut st.value).hint_text(if wait { "Text to match…" } else { "Text to send…" }).desired_width(ui.available_width())); + }); + }); + } + apply(steps, act); + ui.horizontal(|ui| { + if ui.button(format!("{} Wait", icon::PLUS)).clicked() { + steps.push(LoginScriptStep { kind: "wait".into(), value: String::new(), timeout: 10 }); + } + if ui.button(format!("{} Send", icon::PLUS)).clicked() { + steps.push(LoginScriptStep { kind: "send".into(), value: String::new(), timeout: 0 }); + } + }); +} + +/// The SSH-tunnel fields VNC and IRC share. +struct Tunnel<'a> { + on: &'a mut bool, + host: &'a mut String, + port: &'a mut i64, + user: &'a mut String, + auth: &'a mut String, + key: &'a mut String, + agent: &'a mut bool, + mode: &'a mut SecretMode, +} + +fn tunnel(ui: &mut Ui, kind: &str, c: Tunnel, keys: &[String], checks: &mut KeyChecks, secrets: &mut BTreeMap<&'static str, String>, errs: &[ValidationError]) { + check(ui, "Enable SSH Tunnel", c.on, ""); + if !*c.on { + return; + } + text(ui, "SSH Host", c.host, "jump.example.com"); + err(ui, errs, &format!("{kind}.sshHost")); + num(ui, "SSH Port", c.port, 1..=65535); + err(ui, errs, &format!("{kind}.sshPort")); + text(ui, "SSH Username", c.user, "admin"); + err(ui, errs, &format!("{kind}.sshUsername")); + combo_s(ui, "SSH Auth Method", c.auth, &[("publickey", "Public Key"), ("password", "Password")]); + err(ui, errs, &format!("{kind}.sshAuthMethod")); + if c.auth != "password" { + label(ui, "SSH Private Key Path"); + key_path(ui, c.key, keys, checks, "/Users/username/.ssh/id_ed25519"); + check(ui, "Use SSH Agent", c.agent, ""); + } + mode(ui, "SSH Secret Storage", c.mode); + if *c.mode == SecretMode::Saved { + // Kinds match the session manager's tunnel secrets ("vnc-ssh-password", "irc-ssh-passphrase"). + let (l, k): (&str, &'static str) = match (kind, c.auth.as_str()) { + ("vnc", "password") => ("SSH Password", "vnc-ssh-password"), + ("vnc", _) => ("SSH Key Passphrase", "vnc-ssh-passphrase"), + (_, "password") => ("SSH Password", "irc-ssh-password"), + _ => ("SSH Key Passphrase", "irc-ssh-passphrase"), + }; + secret(ui, l, secrets, k, "Leave blank to keep the saved secret"); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn list_moves_and_parsing() { + let mut v = vec![1, 2, 3]; + apply(&mut v, Some(Act::Up(2))); + assert_eq!(v, [1, 3, 2]); + apply(&mut v, Some(Act::Down(0))); + assert_eq!(v, [3, 1, 2]); + apply(&mut v, Some(Act::Remove(1))); + assert_eq!(v, [3, 2]); + apply(&mut v, Some(Act::Down(1))); // last row: no-op + assert_eq!(v, [3, 2]); + assert_eq!(index_of("ssh.jumpHosts[12].host"), Some(12)); + let env = parse_env("A=1\n =x\nB = two=2\nnoeq"); + assert_eq!(env.len(), 2); + assert_eq!(env["B"], "two=2"); + } + + #[test] + fn switching_protocol_keeps_common_fields() { + let mut e = ProfileEditor::default(); + e.p.folder = "Prod".into(); + e.p.host = "box".into(); + e.p.display_name = "Box".into(); + e.switch(Protocol::Serial); + assert_eq!((e.p.folder.as_str(), e.p.host.as_str(), e.p.port), ("Prod", "box", 0)); + assert!(e.p.serial.is_some()); + e.switch(Protocol::Irc); + assert_eq!(e.p.port, 6667); + let saved = e.collect(); + assert!(saved.ssh.is_none() && saved.serial.is_none() && saved.irc.is_some()); + } +} diff --git a/rust/app/src/quick_connect.rs b/rust/app/src/quick_connect.rs index 64d440c..1b80f6f 100644 --- a/rust/app/src/quick_connect.rs +++ b/rust/app/src/quick_connect.rs @@ -1,26 +1,177 @@ //! Quick Connect dialog: an ad-hoc session without saving a profile. -//! STUB — interface fixed; implementation pending. -use crate::state::Shared; +use crate::chrome::modal; +use crate::state::{Request, Shared}; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::{ComboBox, DragValue, Key, RichText, TextEdit}; +use egui_phosphor::regular as icon; +use nexus_core::clients::serial; +use nexus_core::models::{Profile, Protocol}; + +const PROTOCOLS: [Protocol; 5] = [Protocol::Ssh, Protocol::Telnet, Protocol::Raw, Protocol::Irc, Protocol::Serial]; -#[derive(Default)] pub struct QuickConnect { open: bool, + focus: bool, + protocol: Protocol, + host: String, + port: i64, + username: String, + nick: String, + ports: Vec, +} + +impl Default for QuickConnect { + fn default() -> Self { + QuickConnect { open: false, focus: false, protocol: Protocol::Ssh, host: String::new(), port: 22, username: String::new(), nick: "nexus".into(), ports: vec![] } + } } impl QuickConnect { pub fn open(&mut self) { self.open = true; + self.focus = true; + self.host.clear(); + if self.protocol == Protocol::Serial { + self.ports = serial::list_ports(); + } } - pub fn is_open(&self) -> bool { - self.open + fn set_protocol(&mut self, p: Protocol) { + self.protocol = p; + self.port = Profile::new(p).port; + if p == Protocol::Serial { + self.ports = serial::list_ports(); + } + } + + /// The profile to connect, or None when the host is empty. + fn profile(&self) -> Option { + let host = self.host.trim(); + if host.is_empty() { + return None; + } + let mut p = Profile::new(self.protocol); + p.host = host.into(); + p.display_name = host.into(); + if self.protocol != Protocol::Serial { + p.port = self.port; + } + if matches!(self.protocol, Protocol::Ssh | Protocol::Irc) { + p.username = self.username.trim().into(); + } + if let Some(c) = &mut p.irc + && !self.nick.trim().is_empty() + { + c.nick = self.nick.trim().into(); + } + Some(p) } /// Called every frame; draws its window(s) while open. - pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { - let mut open = self.open; - egui::Window::new("QuickConnect").open(&mut open).show(ctx, |ui| ui.label("todo")); - self.open = open; + pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + if !self.open { + return; + } + let t = Tokens::get(ctx); + let (enter, esc) = ctx.input(|i| (i.key_pressed(Key::Enter), i.key_pressed(Key::Escape))); + let mut connect = enter; + let mut close = esc; + let label = |ui: &mut egui::Ui, l: &str| ui.label(RichText::new(l).color(t.text_dim).size(12.0)); + modal(ctx, "quick-connect", 360.0, |ui| { + ui.label(RichText::new(format!("{} Quick Connect", icon::LIGHTNING)).font(theme::semibold(15.0))); + ui.add_space(4.0); + label(ui, "Protocol"); + let mut proto = self.protocol; + ComboBox::from_id_salt("qc-protocol").width(ui.available_width()).selected_text(proto.label()).show_ui(ui, |ui| { + for p in PROTOCOLS { + ui.selectable_value(&mut proto, p, p.label()); + } + }); + if proto != self.protocol { + self.set_protocol(proto); + } + let serial = self.protocol == Protocol::Serial; + label(ui, if serial { "Device" } else { "Host" }); + let r = ui + .horizontal(|ui| { + let w = if serial { ui.available_width() - 34.0 } else { ui.available_width() }; + let r = ui.add(TextEdit::singleline(&mut self.host).hint_text(if serial { "/dev/ttyUSB0 or COM3" } else { "e.g. 192.168.1.1" }).desired_width(w)); + if serial { + ui.menu_button(icon::CARET_DOWN, |ui| { + if self.ports.is_empty() { + widgets::hint(ui, "No serial devices found."); + } + for p in &self.ports { + if ui.selectable_label(self.host == *p, p).clicked() { + self.host = p.clone(); + ui.close(); + } + } + if ui.button(format!("{} Refresh", icon::ARROWS_CLOCKWISE)).clicked() { + self.ports = serial::list_ports(); + } + }); + } + r + }) + .inner; + if std::mem::take(&mut self.focus) { + r.request_focus(); + } + if !serial { + label(ui, "Port"); + ui.add(DragValue::new(&mut self.port).range(1..=65535).speed(0.25)); + } + if matches!(self.protocol, Protocol::Ssh | Protocol::Irc) { + label(ui, "Username"); + ui.add(TextEdit::singleline(&mut self.username).hint_text("root").desired_width(f32::INFINITY)); + } + if self.protocol == Protocol::Irc { + label(ui, "Nick"); + ui.add(TextEdit::singleline(&mut self.nick).desired_width(f32::INFINITY)); + } + ui.add_space(8.0); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::PLUGS_CONNECTED, "Connect").clicked() { + connect = true; + } + if ui.button("Cancel").clicked() { + close = true; + } + }); + }); + if close { + self.open = false; + } else if connect { + match self.profile() { + Some(p) => { + self.open = false; + s.request(Request::ConnectQuick(p)); + } + None => self.focus = true, + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn builds_profiles() { + let mut q = QuickConnect::default(); + assert!(q.profile().is_none()); + q.host = " box ".into(); + q.username = "me".into(); + q.set_protocol(Protocol::Irc); + q.nick = "andy".into(); + let p = q.profile().unwrap(); + assert_eq!((p.host.as_str(), p.port, p.username.as_str(), p.irc.unwrap().nick.as_str()), ("box", 6667, "me", "andy")); + q.set_protocol(Protocol::Serial); + let p = q.profile().unwrap(); + assert_eq!((p.port, p.username.as_str()), (0, "")); } } From 52770923df7ad6f0df670bca1a6d3b74217bd62d Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:23:54 +0100 Subject: [PATCH 25/45] rust: snippets, triggers, recordings and playback --- rust/app/src/chrome.rs | 2 +- rust/app/src/recordings.rs | 321 ++++++++++++++++++++++++++++++++- rust/app/src/snippets.rs | 358 +++++++++++++++++++++++++++++++++++-- rust/app/src/triggers.rs | 310 +++++++++++++++++++++++++++++++- 4 files changed, 965 insertions(+), 26 deletions(-) diff --git a/rust/app/src/chrome.rs b/rust/app/src/chrome.rs index 22d8e2e..0317ea7 100644 --- a/rust/app/src/chrome.rs +++ b/rust/app/src/chrome.rs @@ -611,7 +611,7 @@ pub fn palette(app: &mut App, ctx: &egui::Context) { // --- prompts -------------------------------------------------------------------------------- -fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) { +pub fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) { modal_backdrop(ctx); egui::Area::new(egui::Id::new(id)).order(egui::Order::Foreground).anchor(Align2::CENTER_CENTER, vec2(0.0, -40.0)).show(ctx, |ui| { egui::Frame::popup(ui.style()).corner_radius(10).inner_margin(16).show(ui, |ui| { diff --git a/rust/app/src/recordings.rs b/rust/app/src/recordings.rs index d57d222..bbc6ba5 100644 --- a/rust/app/src/recordings.rs +++ b/rust/app/src/recordings.rs @@ -1,26 +1,333 @@ -//! Recordings browser and playback window. -//! STUB — interface fixed; implementation pending. +//! Recordings browser and playback window (port of the RecordingManager in recordings.js). use crate::state::Shared; +use crate::terminal::{CursorStyle, TermView}; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::{Key, RichText}; +use egui_phosphor::regular as icon; +use nexus_core::recording::{CleanupResult, EVENT_OUTPUT, Event, Header, RecordingMeta, StorageInfo}; +use std::sync::mpsc::{Receiver, channel}; + +const AGES: [(u32, &str); 4] = [(30, "Older than 30 days"), (7, "Older than 7 days"), (90, "Older than 90 days"), (0, "Ignore age")]; +const SIZES: [(u64, &str); 3] = [(1 << 30, "Keep under 1 GB"), (5 << 30, "Keep under 5 GB"), (0, "Ignore size")]; +const SPEEDS: [f64; 5] = [0.5, 1.0, 2.0, 5.0, 10.0]; + +/// m:ss, as the JS `_formatSeconds`. +pub fn mmss(secs: f64) -> String { + if !(secs > 0.0) { + return "0:00".into(); + } + let s = secs.floor() as u64; + format!("{}:{:02}", s / 60, s % 60) +} + +enum Loaded { + List(Vec, StorageInfo), + Cleaned(CleanupResult), + Recording(String, Header, Vec), +} + +struct Playback { + title: String, + term: TermView, + events: Vec, + total: f64, + /// Virtual time played so far, and the next event to feed. + pos: f64, + next: usize, + playing: bool, + speed: f64, +} + +impl Playback { + fn feed_until(&mut self, t: f64) { + while let Some(e) = self.events.get(self.next).filter(|e| e.elapsed <= t) { + if e.kind == EVENT_OUTPUT { + self.term.feed(e.data.as_bytes()); + } + self.next += 1; + } + } + + /// Replays from the start up to `t`. + fn seek(&mut self, t: f64) { + self.term.reset(); + self.next = 0; + self.pos = t.clamp(0.0, self.total); + self.feed_until(self.pos); + } + + fn play(&mut self) { + if self.next >= self.events.len() { + self.seek(0.0); + } + self.playing = true; + } + + fn tick(&mut self, ctx: &egui::Context) { + if !self.playing { + return; + } + let dt = ctx.input(|i| i.stable_dt).min(0.25) as f64; + self.pos = (self.pos + dt * self.speed).min(self.total); + self.feed_until(self.pos); + if self.next >= self.events.len() { + self.playing = false; + self.pos = self.total; + } else { + let wait = (self.events[self.next].elapsed - self.pos) / self.speed; + ctx.request_repaint_after(std::time::Duration::from_secs_f64(wait.clamp(0.0, 0.25))); + } + } +} -#[derive(Default)] pub struct Recordings { open: bool, + list: Vec, + info: Option, + loading: bool, + rx: Option>, + age: u32, + size: u64, + status: String, + confirm_delete: Option, + playback: Option, +} + +impl Default for Recordings { + fn default() -> Self { + Recordings { open: false, list: vec![], info: None, loading: false, rx: None, age: 30, size: 1 << 30, status: String::new(), confirm_delete: None, playback: None } + } } impl Recordings { pub fn open(&mut self) { self.open = true; + self.status.clear(); + self.info = None; } - pub fn is_open(&self) -> bool { - self.open + /// Runs `f` on a worker thread; its result is picked up by `poll`. + fn spawn(&mut self, s: &Shared, f: impl FnOnce(&nexus_core::recording::Store) -> nexus_core::Result + Send + 'static) { + let (tx, rx) = channel(); + let store = s.svc.sessions.recordings().clone(); + self.rx = Some(rx); + self.loading = true; + s.spawn(move || f(&store), move |v| drop(tx.send(v))); + } + + fn reload(&mut self, s: &Shared) { + self.spawn(s, |st| Ok(Loaded::List(st.list()?, st.storage_info()?))); + } + + fn poll(&mut self, s: &mut Shared) { + let Some(rx) = &self.rx else { return }; + let got = match rx.try_recv() { + Ok(v) => v, + Err(std::sync::mpsc::TryRecvError::Empty) => return, + Err(_) => { + // Worker failed (error already toasted). + self.rx = None; + self.loading = false; + return; + } + }; + self.rx = None; + self.loading = false; + match got { + Loaded::List(list, info) => { + self.list = list; + self.info = Some(info); + } + Loaded::Cleaned(r) => { + self.status = format!("Deleted {}; freed {}.", r.deleted, widgets::human_size(r.freed_bytes)); + self.reload(s); + } + Loaded::Recording(title, header, events) => { + if events.is_empty() { + return s.info("Recording is empty — nothing to play back."); + } + let total = events.last().map_or(1.0, |e| e.elapsed).max(f64::EPSILON); + let title = if header.title.is_empty() { title } else { header.title }; + let mut p = Playback { title, term: TermView::new(10_000, 13.0, CursorStyle::Block), events, total, pos: 0.0, next: 0, playing: false, speed: 1.0 }; + p.play(); // autoplay + self.playback = Some(p); + } + } } /// Called every frame; draws its window(s) while open. - pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { + pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + self.poll(s); + self.playback_ui(ctx, s); + self.delete_dialog(ctx, s); + if !self.open { + return; + } + if self.info.is_none() && !self.loading { + self.reload(s); + } let mut open = self.open; - egui::Window::new("Recordings").open(&mut open).show(ctx, |ui| ui.label("todo")); + egui::Window::new(format!("{} Session Recordings", icon::RECORD)) + .id(egui::Id::new("recordings")) + .open(&mut open) + .collapsible(false) + .default_size([640.0, 440.0]) + .show(ctx, |ui| self.browser(ui, s)); self.open = open; } + + fn browser(&mut self, ui: &mut egui::Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + ui.horizontal(|ui| { + let (count, total) = self.info.as_ref().map_or((self.list.len(), 0), |i| (i.count, i.total_bytes)); + ui.label(RichText::new(format!("{count} recording{}", if count == 1 { "" } else { "s" })).font(theme::semibold(13.0))); + ui.label(RichText::new(widgets::human_size(total)).color(t.text_dim)); + if self.loading { + ui.spinner(); + } + }); + ui.horizontal(|ui| { + egui::ComboBox::from_id_salt("rec-age").selected_text(AGES.iter().find(|a| a.0 == self.age).map_or("", |a| a.1)).show_ui(ui, |ui| { + for (v, l) in AGES { + ui.selectable_value(&mut self.age, v, l); + } + }); + egui::ComboBox::from_id_salt("rec-size").selected_text(SIZES.iter().find(|a| a.0 == self.size).map_or("", |a| a.1)).show_ui(ui, |ui| { + for (v, l) in SIZES { + ui.selectable_value(&mut self.size, v, l); + } + }); + if ui.add_enabled(!self.loading, egui::Button::new(format!("{} Clean up", icon::BROOM))).clicked() { + let (age, size) = (self.age, self.size); + self.status.clear(); + self.spawn(s, move |st| Ok(Loaded::Cleaned(st.cleanup(age, size)?))); + } + }); + if !self.status.is_empty() { + widgets::hint(ui, &self.status); + } + ui.separator(); + if self.list.is_empty() { + if !self.loading { + widgets::empty(ui, icon::RECORD, "No recordings yet", "Use the record button on a terminal tab to capture a session."); + } + return; + } + let (mut play, mut delete) = (None, None); + widgets::rows(ui, "rec-list", self.list.len(), 44.0, None, |ui, i| { + let r = &self.list[i]; + ui.vertical(|ui| { + ui.set_width(ui.available_width() - 64.0); + ui.label(RichText::new(if r.title.is_empty() { &r.id } else { &r.title }).font(theme::semibold(13.0))); + let date = r.created_at.with_timezone(&chrono::Local).format("%Y-%m-%d %H:%M"); + ui.label(RichText::new(format!("{date} · {} · {} · {}×{}", mmss(r.duration), widgets::human_size(r.size), r.width, r.height)).size(11.5).color(t.text_dim)); + }); + if widgets::icon_button(ui, icon::PLAY, "Play").clicked() { + play = Some(i); + } + if widgets::icon_button(ui, icon::TRASH, "Delete").clicked() { + delete = Some(i); + } + }); + if let Some(i) = play { + let (id, title) = (self.list[i].id.clone(), self.list[i].title.clone()); + self.open = false; + self.spawn(s, move |st| { + let (h, ev) = st.get(&id)?; + Ok(Loaded::Recording(title, h, ev)) + }); + } + if let Some(i) = delete { + self.confirm_delete = Some(self.list[i].clone()); + } + } + + fn delete_dialog(&mut self, ctx: &egui::Context, s: &mut Shared) { + let Some(r) = &self.confirm_delete else { return }; + let (mut yes, mut no) = (false, ctx.input(|i| i.key_pressed(Key::Escape))); + let name = if r.title.is_empty() { r.id.clone() } else { r.title.clone() }; + crate::chrome::modal(ctx, "rec-delete", 380.0, |ui| { + ui.label(RichText::new("Delete recording?").font(theme::semibold(15.0))); + ui.label(format!("\u{201c}{name}\u{201d} will be permanently deleted.")); + ui.add_space(8.0); + ui.horizontal(|ui| { + yes = widgets::primary(ui, icon::TRASH, "Delete").clicked(); + no |= ui.button("Cancel").clicked(); + }); + }); + if yes { + let id = self.confirm_delete.take().unwrap().id; + match s.svc.sessions.recordings().delete(&id) { + Ok(()) => { + s.success("Recording deleted"); + self.list.retain(|m| m.id != id); + self.reload(s); + } + Err(e) => s.error(format!("Failed to delete recording: {e}")), + } + } else if no { + self.confirm_delete = None; + } + } + + fn playback_ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + let Some(p) = &mut self.playback else { return }; + p.tick(ctx); + let t = Tokens::get(ctx); + let theme = crate::terminal_themes::get(&s.prefs.terminal_theme); + let mut open = true; + egui::Window::new(format!("{} {}", icon::PLAY_CIRCLE, p.title)) + .id(egui::Id::new("rec-playback")) + .open(&mut open) + .collapsible(false) + .default_size([820.0, 520.0]) + .show(ctx, |ui| { + egui::Panel::bottom("rec-controls").frame(egui::Frame::NONE.inner_margin(egui::Margin::symmetric(0, 6))).show(ui, |ui| { + ui.horizontal(|ui| { + let (glyph, tip) = if p.playing { (icon::PAUSE, "Pause") } else { (icon::PLAY, "Play") }; + if widgets::icon_button(ui, glyph, tip).clicked() { + if p.playing { p.playing = false } else { p.play() } + } + if widgets::icon_button(ui, icon::ARROW_COUNTER_CLOCKWISE, "Restart").clicked() { + p.playing = false; + p.seek(0.0); + } + let label = format!("{} / {}", mmss(p.pos), mmss(p.total)); + let lw = 120.0; + let mut pos = p.pos; + ui.spacing_mut().slider_width = (ui.available_width() - lw - 90.0).max(80.0); + if ui.add(egui::Slider::new(&mut pos, 0.0..=p.total).show_value(false)).changed() { + p.seek(pos); + } + ui.add_sized([lw, 20.0], egui::Label::new(RichText::new(label).font(theme::mono(12.0)).color(t.text_dim))); + egui::ComboBox::from_id_salt("rec-speed").width(60.0).selected_text(format!("{}×", p.speed)).show_ui(ui, |ui| { + for v in SPEEDS { + ui.selectable_value(&mut p.speed, v, format!("{v}×")); + } + }); + }); + }); + egui::Frame::NONE.fill(egui::Color32::from_rgba_unmultiplied(theme.background[0], theme.background[1], theme.background[2], 255)).corner_radius(t.radius).show(ui, |ui| { + ui.set_min_size(ui.available_size()); + // Read-only: keyboard input and terminal replies are dropped. + let _ = p.term.ui(ui, theme, false); + }); + }); + if !open { + self.playback = None; + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn mmss() { + assert_eq!(super::mmss(0.0), "0:00"); + assert_eq!(super::mmss(f64::NAN), "0:00"); + assert_eq!(super::mmss(59.9), "0:59"); + assert_eq!(super::mmss(125.0), "2:05"); + } } diff --git a/rust/app/src/snippets.rs b/rust/app/src/snippets.rs index 545ef6d..44cfadf 100644 --- a/rust/app/src/snippets.rs +++ b/rust/app/src/snippets.rs @@ -1,26 +1,364 @@ -//! Snippet library window and the {{variable}} prompt. -//! STUB — interface fixed; implementation pending. +//! Snippet library window and the {{variable}} prompt (port of snippets.js). -use crate::state::Shared; +use crate::state::{Request, Shared}; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::{Key, RichText}; +use egui_phosphor::regular as icon; +use nexus_core::models::{SessionState, Snippet}; +use regex::Regex; +use std::collections::BTreeMap; +use std::sync::LazyLock; + +static VAR_RE: LazyLock = LazyLock::new(|| Regex::new(r"\{\{([^}]+)\}\}").unwrap()); + +/// Replaces built-ins (`hostname`, `date`, `time`, `timestamp`, case-insensitive) and returns the +/// remaining user variable names, de-duplicated in order of appearance. +pub fn expand_builtins(cmd: &str, hostname: &str) -> (String, Vec) { + let now = chrono::Local::now(); + let mut vars: Vec = vec![]; + let out = VAR_RE.replace_all(cmd, |c: ®ex::Captures| { + let name = c[1].trim(); + let v = match name.to_lowercase().as_str() { + "hostname" => Some(if hostname.is_empty() { "unknown".to_owned() } else { hostname.to_owned() }), + // Same formats as JS: toISOString() date (UTC), toTimeString() HH:MM:SS (local), epoch secs. + "date" => Some(now.with_timezone(&chrono::Utc).format("%Y-%m-%d").to_string()), + "time" => Some(now.format("%H:%M:%S").to_string()), + "timestamp" => Some(now.timestamp().to_string()), + _ => None, + }; + v.unwrap_or_else(|| { + if !vars.iter().any(|x| x == name) { + vars.push(name.to_owned()); + } + c[0].to_owned() + }) + }); + (out.into_owned(), vars) +} + +/// Fills user variables (`vars[i]` ← `values[i]`) left by `expand_builtins`. +pub fn fill(resolved: &str, vars: &[String], values: &[String]) -> String { + VAR_RE + .replace_all(resolved, |c: ®ex::Captures| match vars.iter().position(|v| v == c[1].trim()) { + Some(i) => values.get(i).cloned().unwrap_or_default(), + None => c[0].to_owned(), + }) + .into_owned() +} + +enum Pane { + Empty(&'static str), + Detail(String), + Edit(Snippet), +} + +struct VarForm { + resolved: String, + vars: Vec, + values: Vec, +} -#[derive(Default)] pub struct Snippets { open: bool, + list: Vec, + search: String, + pane: Pane, + confirm_delete: Option, + vars: Option, + loaded: bool, +} + +impl Default for Snippets { + fn default() -> Self { + Snippets { open: false, list: vec![], search: String::new(), pane: Pane::Empty("Select a snippet or create a new one"), confirm_delete: None, vars: None, loaded: false } + } } impl Snippets { pub fn open(&mut self) { self.open = true; - } - - pub fn is_open(&self) -> bool { - self.open + self.loaded = false; + self.pane = Pane::Empty("Select a snippet or create a new one"); } /// Called every frame; draws its window(s) while open. - pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { + pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + self.var_form(ctx, s); + self.delete_dialog(ctx, s); + if !self.open { + return; + } + if !self.loaded { + self.list = s.svc.snippets(); + self.loaded = true; + } let mut open = self.open; - egui::Window::new("Snippets").open(&mut open).show(ctx, |ui| ui.label("todo")); + egui::Window::new(format!("{} Snippets", icon::CODE)) + .id(egui::Id::new("snippets")) + .open(&mut open) + .collapsible(false) + .default_size([720.0, 440.0]) + .show(ctx, |ui| { + ui.horizontal_top(|ui| { + let h = ui.available_height().max(400.0); + ui.allocate_ui(egui::vec2(300.0, h), |ui| { + ui.set_width(300.0); + ui.vertical(|ui| self.list_ui(ui)); + }); + ui.separator(); + ui.vertical(|ui| { + ui.set_min_height(h); + self.pane_ui(ui, s); + }); + }); + }); self.open = open; } + + fn list_ui(&mut self, ui: &mut egui::Ui) { + let t = Tokens::get(ui.ctx()); + ui.horizontal(|ui| { + ui.add(egui::TextEdit::singleline(&mut self.search).hint_text("Search snippets…").desired_width(200.0)); + if widgets::primary(ui, icon::PLUS, "New").clicked() { + self.pane = Pane::Edit(blank()); + } + }); + let q = self.search.trim().to_lowercase(); + let mut groups: BTreeMap<&str, Vec<&Snippet>> = BTreeMap::new(); + for sn in &self.list { + if q.is_empty() || [&sn.name, &sn.command, &sn.description].iter().any(|f| f.to_lowercase().contains(&q)) { + groups.entry(sn.folder.as_str()).or_default().push(sn); + } + } + if groups.is_empty() { + widgets::empty(ui, icon::CODE, "No snippets found", if q.is_empty() { "Create one with New" } else { "Try another search" }); + return; + } + let selected = match &self.pane { + Pane::Detail(id) => Some(id.clone()), + _ => None, + }; + let mut pick = None; + egui::ScrollArea::vertical().id_salt("snippet-list").auto_shrink([false, false]).show(ui, |ui| { + // Ungrouped ("") sorts first, folders alphabetically after. + for (folder, items) in groups { + if !folder.is_empty() { + widgets::section(ui, &format!("{} {folder}", icon::FOLDER)); + } + for sn in items { + let sel = selected.as_deref() == Some(sn.id.as_str()); + let r = egui::Frame::NONE + .fill(if sel { t.row_selected } else { egui::Color32::TRANSPARENT }) + .corner_radius(t.radius) + .inner_margin(egui::Margin::symmetric(6, 4)) + .show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Truncate); + ui.label(RichText::new(&sn.name).font(theme::semibold(13.0))); + ui.label(RichText::new(&sn.command).font(theme::mono(11.5)).color(t.text_dim)); + }) + .response + .interact(egui::Sense::click()); + if r.hovered() && !sel { + ui.painter().rect_filled(r.rect, t.radius, t.hover.gamma_multiply(0.4)); + } + if r.clicked() { + pick = Some(sn.id.clone()); + } + } + } + }); + if let Some(id) = pick { + self.pane = Pane::Detail(id); + } + } + + fn pane_ui(&mut self, ui: &mut egui::Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + match &mut self.pane { + Pane::Empty(msg) => { + widgets::empty(ui, icon::TERMINAL_WINDOW, msg, "Snippets are sent to the active session"); + } + Pane::Detail(id) => { + let Some(sn) = self.list.iter().find(|x| &x.id == id).cloned() else { + self.pane = Pane::Empty("Select a snippet or create a new one"); + return; + }; + ui.label(RichText::new(&sn.name).font(theme::semibold(15.0))); + if !sn.description.is_empty() { + ui.label(RichText::new(&sn.description).color(t.text_dim)); + } + if !sn.folder.is_empty() { + widgets::chip(ui, &sn.folder, t.accent); + } + egui::Frame::NONE.fill(t.field).corner_radius(t.radius).inner_margin(10).show(ui, |ui| { + ui.set_width(ui.available_width()); + egui::ScrollArea::vertical().max_height(220.0).show(ui, |ui| { + ui.add(egui::Label::new(RichText::new(&sn.command).font(theme::mono(12.5))).wrap()); + }); + }); + ui.add_space(6.0); + ui.horizontal(|ui| { + if widgets::primary(ui, icon::PAPER_PLANE_RIGHT, "Send to Session").clicked() { + self.send(s, &sn.command); + } + if widgets::button(ui, icon::PENCIL_SIMPLE, "Edit").clicked() { + self.pane = Pane::Edit(sn.clone()); + } + if widgets::button(ui, icon::TRASH, "Delete").clicked() { + self.confirm_delete = Some(sn.id.clone()); + } + }); + } + Pane::Edit(ed) => { + let is_new = ed.id.is_empty(); + ui.label(RichText::new(if is_new { "New Snippet" } else { "Edit Snippet" }).font(theme::semibold(14.0))); + egui::Grid::new("snippet-editor").num_columns(2).spacing([10.0, 6.0]).show(ui, |ui| { + ui.label("Name"); + ui.add(egui::TextEdit::singleline(&mut ed.name).desired_width(f32::INFINITY)); + ui.end_row(); + ui.label("Description"); + ui.add(egui::TextEdit::singleline(&mut ed.description).desired_width(f32::INFINITY)); + ui.end_row(); + ui.label("Command"); + ui.add(egui::TextEdit::multiline(&mut ed.command).font(theme::mono(12.5)).desired_rows(8).desired_width(f32::INFINITY)); + ui.end_row(); + ui.label("Folder"); + ui.add(egui::TextEdit::singleline(&mut ed.folder).hint_text("e.g. Networking, Debugging").desired_width(f32::INFINITY)); + ui.end_row(); + }); + widgets::hint(ui, "Variables: {{hostname}} {{date}} {{time}} {{timestamp}}, or {{anything}} to be asked when sending."); + ui.add_space(6.0); + let (mut save, mut cancel) = (false, false); + ui.horizontal(|ui| { + save = widgets::primary(ui, icon::FLOPPY_DISK, "Save").clicked(); + cancel = ui.button("Cancel").clicked(); + }); + if cancel { + self.pane = if is_new { Pane::Empty("Select a snippet or create a new one") } else { Pane::Detail(ed.id.clone()) }; + } else if save { + let mut sn = ed.clone(); + sn.name = sn.name.trim().to_owned(); + sn.description = sn.description.trim().to_owned(); + sn.folder = sn.folder.trim().to_owned(); + if sn.name.is_empty() { + s.error("Name is required"); + } else if sn.command.is_empty() { + s.error("Command is required"); + } else { + match s.svc.save_snippet(sn) { + Ok(saved) => { + s.info(if is_new { "Snippet created" } else { "Snippet updated" }); + self.list = s.svc.snippets(); + self.pane = Pane::Detail(saved.id); + } + Err(e) => s.error(format!("Failed to save snippet: {e}")), + } + } + } + } + } + } + + /// Resolves variables then types the command (plus newline) into the focused session. + fn send(&mut self, s: &mut Shared, command: &str) { + let Some(id) = s.focused_session() else { return s.error("No active session") }; + if s.session_state(&id) != Some(SessionState::Connected) { + return s.error("Session is not connected"); + } + let host = s.pane(&id).map(|p| p.info.host.clone()).unwrap_or_default(); + let (resolved, vars) = expand_builtins(command, &host); + if vars.is_empty() { + send_text(s, resolved); + } else { + let values = vec![String::new(); vars.len()]; + self.vars = Some(VarForm { resolved, vars, values }); + } + } + + fn var_form(&mut self, ctx: &egui::Context, s: &mut Shared) { + let Some(f) = &mut self.vars else { return }; + let (mut send, mut cancel) = (false, ctx.input(|i| i.key_pressed(Key::Escape))); + crate::chrome::modal(ctx, "snippet-vars", 360.0, |ui| { + ui.label(RichText::new("Snippet Variables").font(theme::semibold(15.0))); + ui.add_space(4.0); + for (i, (name, value)) in f.vars.iter().zip(f.values.iter_mut()).enumerate() { + ui.label(name); + let r = ui.add(egui::TextEdit::singleline(value).hint_text(format!("Enter value for {name}")).desired_width(f32::INFINITY)); + if i == 0 && !ui.ctx().memory(|m| m.focused().is_some()) { + r.request_focus(); + } + } + ui.add_space(8.0); + ui.horizontal(|ui| { + send = widgets::primary(ui, icon::PAPER_PLANE_RIGHT, "Send").clicked() || ui.input(|i| i.key_pressed(Key::Enter)); + cancel |= ui.button("Cancel").clicked(); + }); + }); + if send { + let text = fill(&f.resolved, &f.vars, &f.values); + self.vars = None; + send_text(s, text); + } else if cancel { + self.vars = None; + } + } + + fn delete_dialog(&mut self, ctx: &egui::Context, s: &mut Shared) { + let Some(id) = self.confirm_delete.clone() else { return }; + let name = self.list.iter().find(|x| x.id == id).map(|x| x.name.clone()).unwrap_or_default(); + let (mut yes, mut no) = (false, ctx.input(|i| i.key_pressed(Key::Escape))); + crate::chrome::modal(ctx, "snippet-delete", 360.0, |ui| { + ui.label(RichText::new("Delete snippet?").font(theme::semibold(15.0))); + ui.label(format!("\u{201c}{name}\u{201d} will be permanently deleted.")); + ui.add_space(8.0); + ui.horizontal(|ui| { + yes = widgets::primary(ui, icon::TRASH, "Delete").clicked(); + no |= ui.button("Cancel").clicked(); + }); + }); + if yes { + self.confirm_delete = None; + match s.svc.delete_snippet(&id) { + Ok(()) => { + self.list.retain(|x| x.id != id); + self.pane = Pane::Empty("Snippet deleted"); + } + Err(e) => s.error(format!("Failed to delete: {e}")), + } + } else if no { + self.confirm_delete = None; + } + } +} + +fn blank() -> Snippet { + let now = nexus_core::models::now(); + Snippet { id: String::new(), name: String::new(), command: String::new(), description: String::new(), folder: String::new(), tags: vec![], created_at: now, updated_at: now } +} + +fn send_text(s: &mut Shared, text: String) { + s.request(Request::SendToActive(text + "\n")); + s.info("Snippet sent"); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn builtins_and_prompted_vars() { + let (out, vars) = expand_builtins("ssh {{ HOSTNAME }} {{user}} {{user}} {{port}} at {{timestamp}}", "box"); + assert!(out.starts_with("ssh box {{user}} {{user}} {{port}} at ")); + assert!(out.rsplit(' ').next().unwrap().parse::().is_ok()); + assert_eq!(vars, ["user", "port"]); + assert_eq!(fill(&out, &vars, &["root".into(), "22".into()]).split(" at ").next().unwrap(), "ssh box root root 22"); + let (out, _) = expand_builtins("{{hostname}} {{date}} {{time}}", ""); + let parts: Vec<&str> = out.split(' ').collect(); + assert_eq!(parts[0], "unknown"); + assert!(chrono::NaiveDate::parse_from_str(parts[1], "%Y-%m-%d").is_ok()); + assert!(chrono::NaiveTime::parse_from_str(parts[2], "%H:%M:%S").is_ok()); + assert_eq!(expand_builtins("echo {not} {{}}", "h"), ("echo {not} {{}}".into(), vec![])); + } } diff --git a/rust/app/src/triggers.rs b/rust/app/src/triggers.rs index 31faa7e..864f404 100644 --- a/rust/app/src/triggers.rs +++ b/rust/app/src/triggers.rs @@ -1,11 +1,92 @@ -//! Trigger rules: match terminal output lines and notify / highlight / beep. -//! STUB — interface fixed; implementation pending. +//! Trigger rules: match terminal output lines and notify / highlight / beep (port of triggers.js). +//! Rules live in `triggers.json` in the data directory (the web build's localStorage rules are +//! not migrated). use crate::state::Shared; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::RichText; +use egui_phosphor::regular as icon; +use regex::{Regex, RegexBuilder}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::path::PathBuf; +use std::sync::LazyLock; + +const ACTIONS: [(&str, &str); 4] = [("notify", "Notify"), ("highlight", "Highlight tab"), ("both", "Notify + Highlight"), ("sound", "Sound")]; + +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", default)] +pub struct Rule { + pub name: String, + pub pattern: String, + pub is_regex: bool, + pub action: String, + pub case_sensitive: bool, + pub enabled: bool, + #[serde(skip)] + re: Option, +} + +impl Rule { + fn compile(&mut self) -> Result<(), regex::Error> { + self.re = None; + if self.is_regex { + self.re = Some(RegexBuilder::new(&self.pattern).case_insensitive(!self.case_sensitive).build()?); + } + Ok(()) + } + + /// Whether `line` (already ANSI-stripped) matches. An invalid regex never matches. + pub fn matches(&self, line: &str) -> bool { + if self.is_regex { + self.re.as_ref().is_some_and(|r| r.is_match(line)) + } else if self.case_sensitive { + line.contains(&self.pattern) + } else { + line.to_lowercase().contains(&self.pattern.to_lowercase()) + } + } +} + +/// Strips CSI sequences exactly as the JS did (`/\x1b\[[0-9;]*[a-zA-Z]/g`). +pub fn strip_ansi(line: &str) -> std::borrow::Cow<'_, str> { + static RE: LazyLock = LazyLock::new(|| Regex::new(r"\x1b\[[0-9;]*[a-zA-Z]").unwrap()); + RE.replace_all(line, "") +} + +/// Appends `data` to the session buffer and returns every completed line; the trailing partial +/// line stays buffered. +pub fn complete_lines(buf: &mut Vec, data: &[u8]) -> Vec { + buf.extend_from_slice(data); + let Some(last) = buf.iter().rposition(|&b| b == b'\n') else { + // ponytail: a line with no newline is capped at 64 KiB (keeps the tail) so a stream + // without newlines can't grow forever. + if buf.len() > 64 * 1024 { + buf.drain(..buf.len() - 64 * 1024); + } + return vec![]; + }; + let rest = buf.split_off(last + 1); + let done = std::mem::replace(buf, rest); + done[..done.len() - 1].split(|&b| b == b'\n').map(|l| String::from_utf8_lossy(l).into_owned()).collect() +} + +enum Form { + New(Rule), + Edit(usize, Rule), +} #[derive(Default)] pub struct Triggers { open: bool, + rules: Option>, + buffers: HashMap>, + form: Option

, +} + +fn path(s: &Shared) -> PathBuf { + s.svc.config.data_dir.join("triggers.json") } impl Triggers { @@ -13,13 +94,226 @@ impl Triggers { self.open = true; } - /// Feeds session output; fires matching rules (toasts, highlight request, bell). - /// Returns true if the tab should be highlighted. - pub fn check(&mut self, _s: &mut Shared, _session: &str, _data: &[u8]) -> bool { - false + fn rules(&mut self, s: &Shared) -> &mut Vec { + self.rules.get_or_insert_with(|| { + let mut v: Vec = std::fs::read(path(s)).ok().and_then(|b| serde_json::from_slice(&b).ok()).unwrap_or_default(); + v.iter_mut().for_each(|r| drop(r.compile())); + v + }) } - pub fn cleanup(&mut self, _session: &str) {} + fn save(&mut self, s: &mut Shared) { + let json = serde_json::to_vec_pretty(self.rules(s)).unwrap_or_default(); + if let Err(e) = std::fs::write(path(s), json) { + s.error(format!("Failed to save trigger rules: {e}")); + } + } - pub fn ui(&mut self, _ctx: &egui::Context, _s: &mut Shared) {} + /// Feeds session output; fires matching rules (toasts, highlight request, bell). + /// Returns true if the tab should be highlighted. + pub fn check(&mut self, s: &mut Shared, session: &str, data: &[u8]) -> bool { + if self.rules(s).is_empty() { + return false; + } + let lines = complete_lines(self.buffers.entry(session.to_owned()).or_default(), data); + let mut highlight = false; + let label = s.pane(session).map(|p| if p.info.tab_name.is_empty() { p.info.profile_name.clone() } else { p.info.tab_name.clone() }).unwrap_or_else(|| session.to_owned()); + let mut fired: Vec<(String, String)> = vec![]; + for line in &lines { + let clean = strip_ansi(line); + for r in self.rules(s).iter().filter(|r| r.enabled && r.matches(&clean)) { + let name = if r.name.is_empty() { r.pattern.clone() } else { r.name.clone() }; + fired.push((r.action.clone(), name)); + } + } + for (action, name) in fired { + let msg = format!("[{label}] Trigger: {name}"); + match action.as_str() { + "notify" => s.info(msg), + "highlight" => highlight = true, + "both" => { + s.info(msg); + highlight = true; + } + "sound" => { + s.ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention(egui::UserAttentionType::Informational)); + s.info(format!("{} {msg}", icon::BELL_RINGING)); + } + _ => {} + } + } + highlight + } + + pub fn cleanup(&mut self, session: &str) { + self.buffers.remove(session); + } + + pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + if !self.open { + return; + } + let mut open = self.open; + egui::Window::new(format!("{} Trigger Rules", icon::LIGHTNING)) + .id(egui::Id::new("triggers")) + .open(&mut open) + .collapsible(false) + .default_size([620.0, 380.0]) + .show(ctx, |ui| { + widgets::hint(ui, "Rules match against terminal output and fire actions when a pattern is detected."); + ui.add_space(4.0); + if self.form.is_some() { + self.form_ui(ui, s); + } else { + self.list_ui(ui, s); + } + }); + self.open = open; + } + + fn list_ui(&mut self, ui: &mut egui::Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + if widgets::primary(ui, icon::PLUS, "Add Rule").clicked() { + self.form = Some(Form::New(Rule { action: "notify".into(), enabled: true, ..Default::default() })); + } + ui.add_space(4.0); + let (mut changed, mut edit, mut delete) = (false, None, None); + let rules = self.rules(s); + if rules.is_empty() { + widgets::empty(ui, icon::LIGHTNING, "No trigger rules defined", "Add a rule to watch terminal output"); + return; + } + egui::ScrollArea::vertical().auto_shrink([false, true]).show(ui, |ui| { + ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend); + egui::Grid::new("trigger-rules").num_columns(4).striped(true).spacing([12.0, 6.0]).show(ui, |ui| { + for (i, r) in rules.iter_mut().enumerate() { + changed |= ui.checkbox(&mut r.enabled, "").on_hover_text("Enabled").changed(); + ui.vertical(|ui| { + ui.label(RichText::new(if r.name.is_empty() { &r.pattern } else { &r.name }).font(theme::semibold(13.0))); + let action = ACTIONS.iter().find(|a| a.0 == r.action).map_or(r.action.as_str(), |a| a.1); + let meta = format!("{} · {action} · {}", if r.is_regex { "Regex" } else { "Text" }, if r.case_sensitive { "Case-sensitive" } else { "Case-insensitive" }); + ui.label(RichText::new(meta).size(11.5).color(t.text_dim)); + }); + ui.label(RichText::new(&r.pattern).font(theme::mono(12.0)).color(if r.is_regex && r.re.is_none() { t.danger } else { t.text })); + ui.horizontal(|ui| { + if widgets::icon_button(ui, icon::PENCIL_SIMPLE, "Edit").clicked() { + edit = Some(i); + } + if widgets::icon_button(ui, icon::TRASH, "Delete").clicked() { + delete = Some(i); + } + }); + ui.end_row(); + } + }); + }); + if let Some(i) = delete { + rules.remove(i); + changed = true; + } else if let Some(i) = edit { + let rule = rules[i].clone(); + self.form = Some(Form::Edit(i, rule)); + } + if changed { + self.save(s); + } + } + + fn form_ui(&mut self, ui: &mut egui::Ui, s: &mut Shared) { + let Some(form) = &mut self.form else { return }; + let (is_new, r) = match form { + Form::New(r) => (true, r), + Form::Edit(_, r) => (false, r), + }; + egui::Grid::new("trigger-form").num_columns(2).spacing([10.0, 8.0]).show(ui, |ui| { + ui.label("Name (optional)"); + ui.add(egui::TextEdit::singleline(&mut r.name).hint_text("e.g. Error alert").desired_width(320.0)); + ui.end_row(); + ui.label("Pattern"); + ui.add(egui::TextEdit::singleline(&mut r.pattern).hint_text("e.g. ERROR|FATAL|panic").font(theme::mono(12.5)).desired_width(320.0)); + ui.end_row(); + ui.label("Match type"); + egui::ComboBox::from_id_salt("trigger-type").selected_text(if r.is_regex { "Regex" } else { "Plain text" }).show_ui(ui, |ui| { + ui.selectable_value(&mut r.is_regex, false, "Plain text"); + ui.selectable_value(&mut r.is_regex, true, "Regex"); + }); + ui.end_row(); + ui.label("Action"); + let cur = ACTIONS.iter().find(|a| a.0 == r.action).map_or("Notify", |a| a.1); + egui::ComboBox::from_id_salt("trigger-action").selected_text(cur).show_ui(ui, |ui| { + for (id, label) in ACTIONS { + if ui.selectable_label(r.action == id, label).clicked() { + r.action = id.into(); + } + } + }); + ui.end_row(); + ui.label(""); + ui.checkbox(&mut r.case_sensitive, "Case-sensitive"); + ui.end_row(); + }); + ui.add_space(8.0); + let (mut save, mut cancel) = (false, false); + ui.horizontal(|ui| { + save = widgets::primary(ui, icon::CHECK, if is_new { "Add" } else { "Save" }).clicked(); + cancel = ui.button("Cancel").clicked(); + }); + if cancel { + self.form = None; + } else if save { + let mut rule = r.clone(); + rule.name = rule.name.trim().to_owned(); + rule.pattern = rule.pattern.trim().to_owned(); + if rule.pattern.is_empty() { + return s.error("Pattern is required"); + } + if let Err(e) = rule.compile() { + return s.error(format!("Invalid regex: {e}")); + } + let form = self.form.take(); + let rules = self.rules(s); + match form { + Some(Form::Edit(i, _)) if i < rules.len() => rules[i] = rule, + _ => rules.push(rule), + } + self.save(s); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn line_buffering() { + let mut b = vec![]; + assert!(complete_lines(&mut b, b"par").is_empty()); + assert_eq!(complete_lines(&mut b, b"tial\nnext\n\nlast"), ["partial", "next", ""]); + assert_eq!(b, b"last"); + assert_eq!(complete_lines(&mut b, b"\n"), ["last"]); + assert!(b.is_empty()); + // A UTF-8 char split across chunks survives. + let e = "é".as_bytes(); + complete_lines(&mut b, &e[..1]); + assert_eq!(complete_lines(&mut b, &[e[1], b'\n']), ["é"]); + } + + #[test] + fn ansi_strip_and_match() { + assert_eq!(strip_ansi("\x1b[1;31mERROR\x1b[0m: disk\x1b[K"), "ERROR: disk"); + // Only CSI with digits/semicolons is stripped, as in the JS. + assert_eq!(strip_ansi("\x1b[?25lx\x1b]0;t\x07"), "\x1b[?25lx\x1b]0;t\x07"); + let mut r = Rule { pattern: "error|fatal".into(), is_regex: true, enabled: true, ..Default::default() }; + r.compile().unwrap(); + assert!(r.matches(&strip_ansi("\x1b[31mFATAL\x1b[0m"))); + r.case_sensitive = true; + r.compile().unwrap(); + assert!(!r.matches("FATAL")); + let r = Rule { pattern: "Disk Full".into(), ..Default::default() }; + assert!(r.matches("warning: disk full")); + let mut bad = Rule { pattern: "(".into(), is_regex: true, ..Default::default() }; + assert!(bad.compile().is_err()); + assert!(!bad.matches("(")); + } } From 4b1729892522d53e02e1f65a24a5ab21ebecf42f Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:25:56 +0100 Subject: [PATCH 26/45] rust: settings, SSH key manager, export/import --- rust/app/src/settings.rs | 943 ++++++++++++++++++++++++++++++++++++++- rust/app/src/ssh_keys.rs | 193 +++++++- rust/app/src/transfer.rs | 253 ++++++++++- 3 files changed, 1360 insertions(+), 29 deletions(-) diff --git a/rust/app/src/settings.rs b/rust/app/src/settings.rs index aa110f9..e65bf00 100644 --- a/rust/app/src/settings.rs +++ b/rust/app/src/settings.rs @@ -1,26 +1,945 @@ -//! Settings window: General, Terminal, Security, Known Hosts, Help, About. -//! STUB — interface fixed; implementation pending. +//! Settings window: General, Terminal, Security, Known Hosts, Help, About. Also the +//! master-password dialog (strength meter + confirm) used to create or unlock the vault. -use crate::state::Shared; +use crate::state::{Request, SettingsTab, Shared, keys}; +use crate::theme::{self, ThemeKind, Tokens}; +use crate::widgets; +use egui::text::{LayoutJob, TextFormat}; +use egui::{RichText, Ui, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::models::{AppPreferences, KnownHost, Protocol}; +use nexus_core::updater::UpdateInfo; +use std::sync::{Arc, Mutex}; + +/// A result from a worker thread, polled each frame (`Some` while the work is in flight). +pub struct Job(Arc>>); + +impl Job { + pub fn spawn(ctx: &egui::Context, f: impl FnOnce() -> T + Send + 'static) -> Self { + let slot = Arc::new(Mutex::new(None)); + let (out, ctx) = (slot.clone(), ctx.clone()); + std::thread::spawn(move || { + let v = f(); + *out.lock().unwrap() = Some(v); + ctx.request_repaint(); + }); + Job(slot) + } + + pub fn take(&self) -> Option { + self.0.lock().unwrap().take() + } +} + +/// Polls `job`; when it has finished, clears it and returns the result. +pub fn poll(job: &mut Option>) -> Option { + let v = job.as_ref()?.take()?; + *job = None; + Some(v) +} + +/// Password strength 0–100 and its label, the same formula as the web build's `_measureStrength`. +pub fn strength(pw: &str) -> (u32, &'static str) { + if pw.is_empty() { + return (0, ""); + } + let len = pw.chars().count() as u32; + let mut score = (len * 3).min(40); + let classes = [ + pw.chars().any(|c| c.is_ascii_lowercase()), + pw.chars().any(|c| c.is_ascii_uppercase()), + pw.chars().any(|c| c.is_ascii_digit()), + pw.chars().any(|c| !c.is_ascii_alphanumeric()), + ]; + score += classes.iter().filter(|b| **b).count() as u32 * 10; + let unique = pw.chars().collect::>().len() as u32; + score += (unique * 20 / len).min(20); + let score = score.min(100); + let label = match score { + 0..25 => "Weak", + 25..50 => "Fair", + 50..75 => "Good", + _ => "Strong", + }; + (score, label) +} + +/// Strength bar + label under a new-password field. +pub fn strength_meter(ui: &mut Ui, pw: &str) { + let t = Tokens::get(ui.ctx()); + let (score, label) = strength(pw); + let color = match label { + "Weak" => t.danger, + "Fair" => t.warning, + "Good" => t.accent, + _ => t.success, + }; + ui.horizontal(|ui| { + let (r, _) = ui.allocate_exact_size(vec2(ui.available_width() - 60.0, 6.0), egui::Sense::hover()); + ui.painter().rect_filled(r, 3.0, t.field); + let mut fill = r; + fill.set_width(r.width() * score as f32 / 100.0); + ui.painter().rect_filled(fill, 3.0, color); + ui.label(RichText::new(label).color(color).size(11.5)); + }); +} + +/// Create or unlock the vault. +struct MasterPrompt { + new: bool, + pw: String, + confirm: String, + error: String, +} #[derive(Default)] pub struct Settings { open: bool, + tab: SettingsTab, + /// Load the working copy from `Shared` on the next frame. + fresh: bool, + prefs: AppPreferences, + theme: ThemeKind, + known: Vec, + default_shell: String, + help_topic: usize, + master: Option, + vault_job: Option>>, + update_job: Option>>, + update: Option>, } impl Settings { - pub fn open(&mut self, _tab: crate::state::SettingsTab) { + pub fn open(&mut self, tab: SettingsTab) { self.open = true; - } - - pub fn is_open(&self) -> bool { - self.open + self.fresh = true; + self.tab = tab; } /// Called every frame; draws its window(s) while open. - pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { - let mut open = self.open; - egui::Window::new("Settings").open(&mut open).show(ctx, |ui| ui.label("todo")); - self.open = open; + pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + self.poll(s); + if !self.open { + return; + } + if std::mem::take(&mut self.fresh) { + self.prefs = s.prefs.clone(); + self.theme = crate::app::theme_for(&self.prefs.theme); + self.known = s.svc.known_hosts(); + self.default_shell = nexus_core::clients::local::default_shell(); + } + let mut open = true; + let mut close = false; + egui::Window::new(RichText::new(format!("{} Settings", icon::GEAR)).font(theme::semibold(14.0))) + .id(egui::Id::new("settings-window")) + .open(&mut open) + .collapsible(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center()) + .resizable(true) + .default_size([760.0, 540.0]) + .min_size([560.0, 380.0]) + .show(ctx, |ui| { + self.tab_bar(ui); + ui.separator(); + let footer = 40.0; + let h = (ui.available_height() - footer).max(200.0); + ui.allocate_ui(vec2(ui.available_width(), h), |ui| { + ui.set_min_height(h); + match self.tab { + SettingsTab::General => self.general(ui), + SettingsTab::Terminal => self.terminal(ui), + SettingsTab::Security => self.security(ui, s), + SettingsTab::KnownHosts => self.known_hosts(ui, s), + SettingsTab::Help => self.help(ui), + SettingsTab::About => self.about(ui, s), + } + }); + ui.separator(); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if widgets::primary(ui, icon::CHECK, "Save").clicked() { + self.save(s); + close = true; + } + if ui.button("Cancel").clicked() { + close = true; + } + }); + }); + self.open = open && !close; + self.master_dialog(ctx, s); + } + + fn poll(&mut self, s: &mut Shared) { + match poll(&mut self.vault_job) { + Some(Ok(msg)) => s.success(msg), + Some(Err(e)) => s.error(e), + None => {} + } + if let Some(r) = poll(&mut self.update_job) { + self.update = Some(r); + } + } + + fn save(&mut self, s: &mut Shared) { + let mut p = self.prefs.clone(); + p.theme = self.theme.id().into(); + p.sidebar_width = p.sidebar_width.clamp(180, 500); + p.default_font_size = p.default_font_size.clamp(8, 36); + p.default_scrollback = p.default_scrollback.clamp(0, 100_000); + p.local_shell = p.local_shell.trim().into(); + match s.svc.save_preferences(&p) { + Ok(()) => { + s.prefs = p; + s.request(Request::ReloadPreferences); + s.success("Settings saved"); + } + Err(e) => s.error(format!("Failed to save settings: {e}")), + } + } + + fn tab_bar(&mut self, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + ui.horizontal(|ui| { + for (tab, glyph, label) in [ + (SettingsTab::General, icon::SLIDERS_HORIZONTAL, "General"), + (SettingsTab::Terminal, icon::TERMINAL_WINDOW, "Terminal"), + (SettingsTab::Security, icon::SHIELD_CHECK, "Security"), + (SettingsTab::KnownHosts, icon::FINGERPRINT, "Known Hosts"), + (SettingsTab::Help, icon::BOOK_OPEN, "Help"), + (SettingsTab::About, icon::INFO, "About"), + ] { + let on = self.tab == tab; + let text = RichText::new(format!("{glyph} {label}")).color(if on { t.text } else { t.text_dim }); + if ui.selectable_label(on, text).clicked() { + self.tab = tab; + } + } + }); + } + + // --- General / Terminal --------------------------------------------------------------- + + fn general(&mut self, ui: &mut Ui) { + let p = &mut self.prefs; + form(ui, "general-form", |ui| { + field(ui, "App theme"); + egui::ComboBox::from_id_salt("app-theme").selected_text(self.theme.label()).width(220.0).show_ui(ui, |ui| { + for k in ThemeKind::ALL { + ui.selectable_value(&mut self.theme, k, k.label()); + } + }); + ui.end_row(); + + field(ui, "Default protocol"); + let cur = Protocol::from_id(&p.default_protocol).map_or("SSH", |x| x.label()); + egui::ComboBox::from_id_salt("default-proto").selected_text(cur).width(220.0).show_ui(ui, |ui| { + for proto in Protocol::SAVED { + if ui.selectable_label(p.default_protocol == proto.id(), proto.label()).clicked() { + p.default_protocol = proto.id().into(); + } + } + }); + ui.end_row(); + + field(ui, "Confirm on close"); + ui.checkbox(&mut p.confirm_on_close, "Ask before closing a connected tab"); + ui.end_row(); + field(ui, "Confirm on disconnect"); + ui.checkbox(&mut p.confirm_on_disconnect, "Ask before disconnecting sessions"); + ui.end_row(); + field(ui, "Status bar"); + ui.checkbox(&mut p.show_status_bar, "Show status bar"); + ui.end_row(); + + field(ui, "Sidebar width"); + ui.add(egui::DragValue::new(&mut p.sidebar_width).range(180..=500).suffix(" px")); + ui.end_row(); + + field(ui, "Local shell"); + ui.vertical(|ui| { + ui.horizontal(|ui| { + ui.add(egui::TextEdit::singleline(&mut p.local_shell).hint_text(&self.default_shell).desired_width(300.0)); + if widgets::button(ui, icon::FOLDER_OPEN, "Browse…").clicked() + && let Some(path) = rfd::FileDialog::new().set_title("Choose a shell").pick_file() + { + p.local_shell = path.to_string_lossy().into(); + } + }); + widgets::hint(ui, "Leave empty to use the system default"); + }); + ui.end_row(); + }); + } + + fn terminal(&mut self, ui: &mut Ui) { + let p = &mut self.prefs; + form(ui, "terminal-form", |ui| { + field(ui, "Colour scheme"); + let cur = crate::terminal_themes::get(&p.terminal_theme).label; + egui::ComboBox::from_id_salt("term-theme").selected_text(cur).width(220.0).show_ui(ui, |ui| { + for th in crate::terminal_themes::THEMES { + if ui.selectable_label(p.terminal_theme == th.id, th.label).clicked() { + p.terminal_theme = th.id.into(); + } + } + }); + ui.end_row(); + + field(ui, "Font family"); + ui.add(egui::TextEdit::singleline(&mut p.default_font_family).hint_text("JetBrains Mono, Menlo, Monaco, Consolas, monospace").desired_width(300.0)); + ui.end_row(); + field(ui, "Font size"); + ui.add(egui::DragValue::new(&mut p.default_font_size).range(8..=36).suffix(" pt")); + ui.end_row(); + field(ui, "Scrollback lines"); + ui.add(egui::DragValue::new(&mut p.default_scrollback).range(0..=100_000).speed(100)); + ui.end_row(); + + field(ui, "Cursor style"); + let label = |c: &str| match c { + "underline" => "Underline", + "bar" => "Bar", + _ => "Block", + }; + egui::ComboBox::from_id_salt("cursor").selected_text(label(&p.default_cursor_style)).width(220.0).show_ui(ui, |ui| { + for c in ["block", "underline", "bar"] { + if ui.selectable_label(p.default_cursor_style == c, label(c)).clicked() { + p.default_cursor_style = c.into(); + } + } + }); + ui.end_row(); + }); + ui.add_space(6.0); + widgets::hint(ui, "Font, size, scrollback and cursor are the defaults for new profiles; profiles can override them."); + } + + // --- Security ------------------------------------------------------------------------- + + fn security(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + let sec = &s.svc.secrets; + let (init, unlocked) = (sec.vault.is_initialised(), sec.vault.is_unlocked()); + widgets::section(ui, "Credential storage"); + form(ui, "security-form", |ui| { + field(ui, "OS keychain"); + if sec.keychain_ok { + ui.label(RichText::new(format!("{} Available", icon::CHECK_CIRCLE)).color(t.success)); + } else { + ui.label(RichText::new(format!("{} Unavailable", icon::WARNING_CIRCLE)).color(t.warning)); + } + ui.end_row(); + field(ui, "Encrypted vault"); + let (text, color) = match (init, unlocked) { + (false, _) => ("Not initialised", t.text_faint), + (true, false) => ("Locked", t.text_dim), + (true, true) => ("Unlocked", t.success), + }; + ui.label(RichText::new(text).color(color)); + ui.end_row(); + }); + ui.add_space(4.0); + widgets::hint( + ui, + if sec.keychain_ok { + "Saved credentials go to the OS keychain first. The encrypted vault (AES-256-GCM, Argon2id key) is the fallback when the keychain refuses a write." + } else { + "The OS keychain isn't available, so saved credentials go to the encrypted vault (AES-256-GCM, Argon2id key). Unlock it to save or use credentials." + }, + ); + ui.add_space(8.0); + ui.horizontal(|ui| { + if self.vault_job.is_some() { + ui.spinner(); + ui.label("Working…"); + } else if !init { + if widgets::primary(ui, icon::SHIELD_PLUS, "Initialise Secret Store").clicked() { + self.master = Some(MasterPrompt { new: true, pw: String::new(), confirm: String::new(), error: String::new() }); + } + } else if !unlocked { + if widgets::primary(ui, icon::LOCK_OPEN, "Unlock").clicked() { + self.master = Some(MasterPrompt { new: false, pw: String::new(), confirm: String::new(), error: String::new() }); + } + } else if widgets::button(ui, icon::LOCK, "Lock").clicked() { + s.svc.secrets.vault.lock(); + s.info("Secret store locked"); + } + }); + } + + fn master_dialog(&mut self, ctx: &egui::Context, s: &mut Shared) { + let Some(m) = &mut self.master else { return }; + let mut submit = false; + let mut cancel = false; + let r = egui::Modal::new(egui::Id::new("master-password")).show(ctx, |ui| { + ui.set_width(380.0); + let title = if m.new { "Create Master Password" } else { "Unlock Secret Store" }; + ui.label(RichText::new(format!("{} {title}", icon::KEY)).font(theme::semibold(15.0))); + ui.label(if m.new { + "Choose a strong master password. This protects all saved credentials." + } else { + "Enter your master password to unlock the secret store." + }); + ui.add_space(4.0); + ui.label("Master password"); + let r = ui.add(egui::TextEdit::singleline(&mut m.pw).password(true).hint_text("Password…").desired_width(f32::INFINITY)); + if !r.has_focus() && m.pw.is_empty() && m.confirm.is_empty() { + r.request_focus(); + } + let mut enter = r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); + if m.new { + strength_meter(ui, &m.pw); + ui.label("Confirm password"); + let r = ui.add(egui::TextEdit::singleline(&mut m.confirm).password(true).hint_text("Confirm…").desired_width(f32::INFINITY)); + enter |= r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); + } + if !m.error.is_empty() { + ui.label(RichText::new(&m.error).color(Tokens::get(ui.ctx()).danger).size(12.0)); + } + ui.add_space(6.0); + ui.horizontal(|ui| { + submit = widgets::primary(ui, icon::CHECK, if m.new { "Create" } else { "Unlock" }).clicked() || enter; + cancel = ui.button("Cancel").clicked(); + }); + }); + if submit { + if m.pw.is_empty() { + m.error = "Enter a password.".into(); + } else if m.new && m.pw != m.confirm { + m.error = "Passwords do not match.".into(); + } else { + let (new, pw) = (m.new, std::mem::take(&mut m.pw)); + let svc = s.svc.clone(); + self.vault_job = Some(Job::spawn(ctx, move || { + let v = &svc.secrets.vault; + if new { + v.initialise(&pw).map(|_| "Secret store created").map_err(|e| format!("Failed to initialise: {e}")) + } else { + v.unlock(&pw).map(|_| "Secret store unlocked").map_err(|e| format!("Unlock failed: {e}")) + } + })); + cancel = true; + } + } + if cancel || r.should_close() { + self.master = None; + } + } + + // --- Known hosts ---------------------------------------------------------------------- + + fn known_hosts(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + if self.known.is_empty() { + widgets::empty(ui, icon::FINGERPRINT, "No known hosts recorded", "Host keys are recorded the first time you connect over SSH."); + return; + } + widgets::count(ui, self.known.len(), "known host(s)", false); + let when = |d: &nexus_core::models::Time| { + use chrono::Datelike; + if d.year() <= 1 { "—".to_string() } else { d.with_timezone(&chrono::Local).format("%Y-%m-%d %H:%M").to_string() } + }; + let mut remove = None; + egui::ScrollArea::both().auto_shrink([false, false]).show(ui, |ui| { + egui::Grid::new("known-hosts").num_columns(7).striped(true).spacing(vec2(14.0, 6.0)).show(ui, |ui| { + for h in ["Host", "Port", "Key Type", "Fingerprint", "First Seen", "Last Seen", ""] { + ui.label(RichText::new(h).font(theme::semibold(11.5)).color(t.text_faint)); + } + ui.end_row(); + for kh in &self.known { + ui.label(&kh.host); + ui.label(kh.port.to_string()); + ui.label(RichText::new(&kh.key_type).font(theme::mono(11.5))); + let short: String = kh.fingerprint.chars().take(24).collect(); + let fp = ui.add(egui::Label::new(RichText::new(format!("{short}…")).font(theme::mono(11.5))).sense(egui::Sense::click())); + if fp.on_hover_text(format!("{}\nClick to copy", kh.fingerprint)).clicked() { + ui.ctx().copy_text(kh.fingerprint.clone()); + } + ui.label(RichText::new(when(&kh.first_seen)).color(t.text_dim)); + ui.label(RichText::new(when(&kh.last_seen)).color(t.text_dim)); + if ui.button(RichText::new(format!("{} Remove", icon::TRASH)).color(t.danger)).clicked() { + remove = Some(kh.id.clone()); + } + ui.end_row(); + } + }); + }); + if let Some(id) = remove { + match s.svc.delete_known_host(&id) { + Ok(()) => { + self.known.retain(|h| h.id != id); + s.success("Known host removed"); + } + Err(e) => s.error(format!("Failed to remove: {e}")), + } + } + } + + // --- Help ----------------------------------------------------------------------------- + + fn help(&mut self, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + ui.horizontal_top(|ui| { + ui.allocate_ui_with_layout(vec2(170.0, ui.available_height()), egui::Layout::top_down_justified(egui::Align::Min), |ui| { + ui.set_width(170.0); + for (i, (title, _)) in HELP.iter().enumerate() { + let on = self.help_topic == i; + if ui.selectable_label(on, RichText::new(*title).color(if on { t.text } else { t.text_dim })).clicked() { + self.help_topic = i; + } + } + }); + ui.separator(); + let (title, body) = HELP[self.help_topic.min(HELP.len() - 1)]; + ui.vertical(|ui| { + egui::ScrollArea::vertical().id_salt(("help", self.help_topic)).auto_shrink([false, false]).show(ui, |ui| { + ui.label(RichText::new(title).font(theme::semibold(17.0))); + ui.add_space(4.0); + render_help(ui, body); + }); + }); + }); + } + + // --- About ---------------------------------------------------------------------------- + + fn about(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + ui.add_space(8.0); + ui.label(RichText::new("Nexus Terminal").font(theme::semibold(22.0))); + ui.label(RichText::new(format!("Version {}", nexus_core::service::VERSION)).color(t.text_dim)); + ui.label(RichText::new("Developed by Andy Dixon - alchementary.co.uk").color(t.text_faint)); + ui.add_space(8.0); + for f in [ + "SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local terminals", + "AES-256-GCM encrypted credential vault", + "Platform-native keychain integration", + "SFTP file browser and transfer", + "Session recording and playback", + ] { + ui.label(RichText::new(format!("{} {f}", icon::CHECK)).color(t.text_dim)); + } + ui.add_space(12.0); + ui.horizontal(|ui| { + if self.update_job.is_some() { + ui.spinner(); + ui.label("Checking…"); + } else if widgets::primary(ui, icon::ARROW_CLOCKWISE, "Check for Updates").clicked() { + let svc = s.svc.clone(); + self.update = None; + self.update_job = Some(Job::spawn(ui.ctx(), move || svc.check_for_updates().map_err(|e| e.to_string()))); + } + if widgets::button(ui, icon::DOWNLOAD_SIMPLE, "Import ~/.ssh/config").clicked() { + match s.svc.import_ssh_config(None, "SSH Config") { + Ok(n) => { + s.success(format!("Imported {n} host(s) from ~/.ssh/config")); + s.request(Request::ReloadProfiles); + } + Err(e) => s.error(format!("SSH config import failed: {e}")), + } + } + }); + ui.add_space(8.0); + match &self.update { + None => {} + Some(Err(e)) => { + ui.label(RichText::new(format!("Update check failed: {e}")).color(t.danger)); + } + Some(Ok(info)) => { + widgets::kv(ui, "update-kv", &[("Current", info.current_version.clone()), ("Latest", info.latest_version.clone())]); + if info.available { + let name = if info.release_name.is_empty() { &info.latest_version } else { &info.release_name }; + ui.label(RichText::new(format!("Update available: {name}")).color(t.accent)); + if !info.release_notes.trim().is_empty() { + egui::Frame::new().fill(t.field).corner_radius(t.radius).inner_margin(8).show(ui, |ui| { + egui::ScrollArea::vertical().max_height(150.0).show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.label(RichText::new(info.release_notes.trim()).color(t.text_dim)); + }); + }); + } + if !info.release_url.is_empty() && widgets::button(ui, icon::ARROW_SQUARE_OUT, "View release").clicked() { + ui.ctx().open_url(egui::OpenUrl::new_tab(&info.release_url)); + } + } else { + ui.label(RichText::new("You are running the latest version.").color(t.success)); + } + } + } + } +} + +/// Two-column label/control grid. +fn form(ui: &mut Ui, id: &str, body: impl FnOnce(&mut Ui)) { + egui::Grid::new(id).num_columns(2).spacing(vec2(18.0, 10.0)).min_col_width(150.0).show(ui, body); +} + +fn field(ui: &mut Ui, label: &str) { + let t = Tokens::get(ui.ctx()); + ui.label(RichText::new(label).color(t.text_dim)); +} + +/// Draws help text: `## heading`, `- bullet`, `1. step`, `> key :: description` (shortcut row), +/// blank-line separated paragraphs, inline `**bold**` and `` `code` ``. `{Mod+K}` becomes the +/// platform's shortcut ("Ctrl+K" or "⌘K"). +fn render_help(ui: &mut Ui, text: &str) { + let t = Tokens::get(ui.ctx()); + for line in text.lines().map(str::trim) { + if line.is_empty() { + ui.add_space(4.0); + } else if let Some(h) = line.strip_prefix("## ") { + ui.add_space(6.0); + ui.label(RichText::new(h).font(theme::semibold(13.5)).color(t.text)); + } else if let Some(row) = line.strip_prefix("> ") { + let (k, d) = row.split_once(" :: ").unwrap_or((row, "")); + ui.horizontal(|ui| { + ui.add_sized([150.0, 18.0], egui::Label::new(inline(&format!("`{}`", expand_keys(k)), &t))); + ui.label(inline(d, &t)); + }); + } else if let Some(b) = line.strip_prefix("- ") { + ui.horizontal_top(|ui| { + ui.add_space(8.0); + ui.label(RichText::new("•").color(t.text_faint)); + ui.add(egui::Label::new(inline(b, &t)).wrap()); + }); + } else if line.len() > 2 && line.as_bytes()[0].is_ascii_digit() && line[1..].starts_with(". ") { + ui.horizontal_top(|ui| { + ui.add_space(8.0); + ui.label(RichText::new(&line[..2]).color(t.text_faint)); + ui.add(egui::Label::new(inline(&line[3..], &t)).wrap()); + }); + } else { + ui.add(egui::Label::new(inline(line, &t)).wrap()); + } + } +} + +fn expand_keys(s: &str) -> String { + let mut out = String::new(); + let mut rest = s; + while let Some(i) = rest.find('{') { + let Some(j) = rest[i..].find('}') else { break }; + out.push_str(&rest[..i]); + out.push_str(&keys(&rest[i + 1..i + j])); + rest = &rest[i + j + 1..]; + } + out + rest +} + +/// `**bold**` and `` `code` `` spans as a wrapped layout job; `{Mod+X}` keys render as code. +fn inline(s: &str, t: &Tokens) -> LayoutJob { + let s = s.replace('{', "`{").replace('}', "}`"); + let s = expand_keys(&s); + let mut job = LayoutJob::default(); + let (mut bold, mut code) = (false, false); + let mut buf = String::new(); + let flush = |job: &mut LayoutJob, buf: &mut String, bold: bool, code: bool| { + if buf.is_empty() { + return; + } + let fmt = if code { + TextFormat { font_id: theme::mono(12.0), color: t.text, background: t.field, ..Default::default() } + } else if bold { + TextFormat { font_id: theme::semibold(13.0), color: t.text, ..Default::default() } + } else { + TextFormat { font_id: egui::FontId::proportional(13.0), color: t.text_dim, ..Default::default() } + }; + job.append(&std::mem::take(buf), 0.0, fmt); + }; + let mut chars = s.chars().peekable(); + while let Some(c) = chars.next() { + if c == '`' { + flush(&mut job, &mut buf, bold, code); + code = !code; + } else if c == '*' && !code && chars.peek() == Some(&'*') { + chars.next(); + flush(&mut job, &mut buf, bold, code); + bold = !bold; + } else { + buf.push(c); + } + } + flush(&mut job, &mut buf, bold, code); + job +} + +/// Help topics (the web build's, minus Leviathan, plus Serial). +const HELP: &[(&str, &str)] = &[ + ( + "Getting Started", + "Welcome to **Nexus Terminal** — a desktop application for SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local terminal sessions. +## Creating Your First Connection +1. Click the **+** button at the top of the sidebar, or press {Mod+N}. +2. Choose a protocol (SSH, Telnet, RAW, Serial, VNC, IRC or GitHub). +3. Fill in the hostname, port, and any credentials required. +4. Optionally assign the connection to a **group** to keep your sidebar organised. +5. Click **Save**, then click the entry in the sidebar to connect. +## Quick Connect +Need to connect quickly without saving a profile? Press {Mod+Shift+Q} to open the quick connect dialog. Choose a protocol, enter the host and port, and you're connected immediately. +## Managing Connections +Right-click any connection in the sidebar to **edit**, **duplicate**, **favourite** or **delete** it. To move connections to another group, Ctrl/Cmd-click to select them and choose **Move Group** in the bulk-action bar. Use the search box to filter by name, host, tag or protocol.", + ), + ( + "SSH Connections", + "SSH (Secure Shell) is the most common way to connect to remote servers. Nexus Terminal supports: +- **Password authentication** — enter your password when prompted, or save it in the OS keychain or encrypted vault. +- **Public key authentication** — point to your private key file (e.g. `~/.ssh/id_ed25519`). If the key has a passphrase, you will be prompted. +- **Keyboard-interactive** — for servers that use challenge-response (e.g. two-factor prompts). +- **SSH agent** — tick \"Use SSH Agent\" to authenticate via your system's SSH agent (e.g. `ssh-agent` or 1Password). +## TOTP / 2FA Auto-Fill +If your server requires a time-based one-time password (TOTP) during login, store the TOTP secret in the profile editor under the **TOTP / 2FA** section. When connecting, Nexus Terminal detects 2FA prompts (such as \"Verification code\" or \"OTP\") and automatically generates and submits a 6-digit code. Use the **Test** button to verify your secret before connecting. +## Jump Hosts (Bastions) +If you need to reach a server through an intermediate host, add one or more jump hosts in the connection editor. Nexus Terminal tunnels through each hop automatically. +## Port Forwarding +You can set up **local**, **remote**, and **dynamic** (SOCKS proxy) port forwards in the connection editor. These start automatically when the session connects. +## Proxy Support +Open the **Proxy** section in the profile editor to connect through a **SOCKS5** or **HTTP CONNECT** proxy, with optional username/password authentication. The proxy is used to reach the SSH server (or the first jump host if jump hosts are configured). +## SFTP File Browser +Once connected to an SSH server, click the folder icon on the tab to open the SFTP file browser. You can browse directories, upload, download, rename, and delete files from a side panel. +## SSH Agent Forwarding +Tick \"Agent Forwarding\" in the SSH settings to forward your local SSH agent to the remote server, so you can use your local keys when connecting onward from that server. +## Auto-Login Scripts +Define expect-style login scripts to automate interactions after connecting. In the **Login Script** section of the profile editor, add a sequence of **Wait for** (match text from the server) and **Send** (send text + newline) steps. For example: +1. Wait for: `login:` +2. Send: `admin` +3. Wait for: `Password:` +4. Send: `mysecretpassword` + +Each wait step has a configurable timeout (default 10 seconds). Steps execute in order after the connection is established. +## Auto-Reconnect +Enable **Auto-Reconnect** in the Advanced section to reconnect automatically when the connection drops unexpectedly. Reconnection uses exponential backoff (1s, 2s, 4s, 8s… up to 30s). You can configure the maximum number of attempts (default 5).", + ), + ( + "Telnet Connections", + "Telnet is an older, unencrypted protocol still used for network equipment, legacy systems, and some fun internet services. +- **Option negotiation** — Nexus Terminal handles standard Telnet options (echo, terminal type, window size) automatically. +- **Character encoding** — choose UTF-8, ASCII, or Latin-1 for the connection. +- **Local echo** — enable this if the remote host does not echo back what you type. +- **Line mode** — send data line-by-line (press Enter to send) rather than character-by-character. +- **Auto-login scripts** — the same expect-style send/wait sequences as SSH. +- **Auto-reconnect** — reconnect automatically with exponential backoff when the connection drops. + +**Note:** Telnet sends everything in plain text. Avoid using it for anything sensitive over an untrusted network.", + ), + ( + "RAW TCP Connections", + "RAW TCP lets you connect to any TCP port and exchange data directly — useful for debugging network services, testing APIs, or working with custom protocols. +- **Text view** — shows incoming data as readable text. +- **Hex view** — shows raw bytes in a hex dump format. +- **Split view** — text and hex side by side. +- **Payload presets** — save frequently-used commands or data packets for quick sending. +- **Line endings** — choose CR, LF, or CRLF to match the remote service. +- **Auto-reconnect** — reconnect automatically if the connection drops.", + ), + ( + "Serial Connections", + "Serial profiles open a terminal on a local serial port — USB-serial adapters, console cables, development boards and network gear. +- **Device** — pick the port from the list (refresh after plugging in an adapter), e.g. `/dev/ttyUSB0`, `/dev/cu.usbserial-…` or `COM3`. +- **Line settings** — baud rate (default 115200), data bits, parity, stop bits and flow control (none, XON/XOFF, RTS/CTS). +- **Behaviour** — line ending, local echo, encoding, and whether DTR/RTS are raised on connect. +- **Tab controls** — send a break and toggle DTR/RTS from the tab. +- **Auto-reconnect** — unplugging the adapter disconnects the session; it reopens the port when it comes back. + +On Linux your user usually needs to be in the `dialout` (or `uucp`) group. On macOS use the `/dev/cu.*` device. A \"busy\" error means another program has the port open.", + ), + ( + "VNC Connections", + "VNC profiles open a remote desktop viewer inside the Nexus Terminal workspace. +- **Password handling** — ask each time, use the session only, or save the password in the secret store. +- **View-only mode** — observe a desktop without sending mouse or keyboard input. +- **Scale-to-fit** — resize the remote desktop to fit the available workspace. +- **Shared session** — request a shared VNC session where the server supports it. +- **Quality** — tune image quality for slower links. +- **SSH tunnel** — carry VNC traffic through an SSH connection using password, public key, or SSH agent authentication. + +Prefer **SSH tunnel** for VNC outside trusted networks. Direct VNC is supported for isolated or trusted networks, but many classic VNC deployments do not provide strong transport encryption.", + ), + ( + "GitHub Connections", + "The GitHub connection type lets you browse your repositories without leaving Nexus Terminal. +## Setting Up +1. Create a new connection and choose **GitHub** as the protocol. +2. Enter your **Personal Access Token** (create one at GitHub → Settings → Developer settings → Personal access tokens). The token needs the `repo` scope. +3. Save and connect. +## What You'll See +Your repositories are listed grouped by organisation or owner, sorted by last commit date (newest first). The list refreshes automatically every few minutes.", + ), + ( + "Sessions & Tabs", + "## Tab Management +- **Rename** — right-click a tab and choose \"Rename\". +- **Reorder** — drag tabs left or right to rearrange. +- **Pin** — right-click and pin a tab so it stays at the left of the bar. +- **Scroll** — use the mouse wheel or trackpad over the tab strip when many tabs are open. +- **Hover hints** — hover a tab to see its full name and connected-since time where available. +- **Close** — click the **×** on the tab, or press {Mod+W}. +## Split Panes +You can split the terminal into multiple panes: +- {Mod+Shift+D} — split vertically (side by side). +- {Mod+Shift+E} — split horizontally (top and bottom). + +Drag the divider between panes to resize them. +## Broadcast Input +Press {Mod+Shift+B} to toggle **broadcast mode**. When active, everything you type is sent to **all connected sessions** simultaneously — perfect for running the same command across multiple servers. +## Session Recording +Press {Mod+Shift+R} to start recording a session. A red dot appears on the tab. Press the same shortcut again to stop. Browse and replay recordings from the Recordings window. +## Right-Click Context Menu +Right-click in the terminal for: +- **Copy** — copy selected text to the clipboard +- **Paste** — paste from the clipboard into the terminal +- **Select All** — select all terminal content +- **Find** — open the terminal search overlay +- **Send Snippet** — quickly send a saved command from the snippet library +- **Clear** / **Reset** — clear the terminal buffer", + ), + ( + "Power Features", + "## Snippet Library +Press {Mod+Shift+S} to open the **snippet library**. Save frequently-used commands with a name, description, and optional folder. Click **Send to Session** to inject a command into the active terminal. You can also reach snippets from the right-click context menu. +## Terminal Search +Press {Mod+F} to open the search overlay on the active terminal: +- `Aa` — toggle case-sensitive matching +- `.*` — toggle regular expression mode +- A match counter showing current/total matches +- Move between matches with the arrows or Enter / Shift+Enter +## Quick Connect +Press {Mod+Shift+Q} to open a lightweight connect dialog and connect without creating a saved profile. +## Auto-Login Scripts +For SSH and Telnet connections you can define expect-style login scripts — a sequence of \"wait for text\" and \"send text\" steps that run automatically after connecting. Configure them in the **Login Script** section of the profile editor. +## Auto-Reconnect +Enable auto-reconnect in a profile's settings to reconnect automatically when a connection drops, using exponential backoff with a configurable maximum number of attempts.", + ), + ( + "Security & Credentials", + "## How Your Passwords Are Stored +Nexus Terminal uses a **two-tier** approach to keep your credentials safe: +1. **OS keychain** (preferred) — your operating system's credential store (macOS Keychain, Windows Credential Manager, or Linux Secret Service). If available, it is used automatically. +2. **Encrypted vault** (fallback) — if the OS keychain is not available, Nexus Terminal keeps a local vault encrypted with AES-256-GCM. You set a master password to unlock it. +## Encryption Details +The vault uses **Argon2id** to derive the encryption key from your master password (128 MiB memory, making brute-force attacks very expensive). Each secret is individually encrypted and never stored in plain text. +## TOTP / 2FA Secrets +TOTP secrets for SSH two-factor authentication are stored alongside your other credentials — in the OS keychain or the encrypted vault. They are never saved in the profile database in plain text. +## Known Hosts +When you connect to an SSH server for the first time, Nexus Terminal records the server's fingerprint. If the fingerprint changes on a later connection, you'll be warned — this protects against man-in-the-middle attacks. + +You can review and remove known hosts in **Settings → Known Hosts**. +## Encrypted Export +When exporting profiles, encrypted export (a `.nexus` file) protects the file with a password, using the same AES-256-GCM encryption as the vault.", + ), + ( + "Keyboard Shortcuts", + "On macOS, use ⌘ (Cmd). On Windows and Linux, use Ctrl. +> {Mod+K} :: Open the command palette +> {Mod+N} :: New connection +> {Mod+W} :: Close the active tab +> {Mod+Tab} :: Next tab +> {Mod+Shift+Tab} :: Previous tab +> {Mod+1} – {Mod+9} :: Switch to tab by number +> {Mod+,} :: Open Settings +> {Mod+F} :: Search in terminal +> {Mod+Shift+B} :: Toggle broadcast input mode +> {Mod+Shift+D} :: Split pane vertically +> {Mod+Shift+E} :: Split pane horizontally +> {Mod+Shift+Q} :: Quick connect (no saved profile) +> {Mod+Shift+R} :: Toggle session recording +> {Mod+Shift+S} :: Open snippet library +> F11 :: Toggle fullscreen", + ), + ( + "Import & Export", + "## Exporting Profiles +Use **Export (Encrypted)** from the sidebar menu or command palette. Choose which profiles to include and set a password to encrypt the file. Exported files use the `.nexus` extension. Plain **JSON** export is also available, optionally including secrets. +## Importing Profiles +Use **Import (Encrypted)**, select a `.nexus` file (older `.link` exports from Alchementary Link work too) and enter the password used when exporting. Choose whether existing profiles are skipped, overwritten or duplicated. Imported profiles appear in your sidebar immediately. +## Importing from OpenSSH +If you have an existing `~/.ssh/config` file, go to **Settings → About** and click **Import ~/.ssh/config**. Nexus Terminal creates profiles from your SSH Host blocks, including hostnames, usernames, ports, identity files, and jump hosts.", + ), + ( + "Troubleshooting", + "## I can't connect to my server +- Double-check the **hostname** and **port** — a common mistake is using port 22 for Telnet (which is usually 23) or vice versa. +- Make sure the server is reachable from your machine. Try `ping hostname` from another terminal. +- If using SSH keys, check that the key file path is correct and that the file permissions are not too open (should be `600` or `400`). +- If you're behind a firewall or VPN, ensure the relevant port is allowed through. +## SSH connection fails through a proxy +- Check the proxy host, port, and type (SOCKS5 or HTTP CONNECT) in the profile's **Proxy** section. +- If the proxy requires authentication, make sure the username and password are filled in. +- SOCKS5 defaults to port 1080; HTTP CONNECT defaults to port 8080. +- Some proxies block SSH traffic — check with your network administrator. +## TOTP/2FA code is rejected +- Ensure the TOTP secret is correct (base32-encoded, as shown during authenticator app setup). +- Check that your system clock is accurate — TOTP codes are only valid for 30-second windows. +- Use the **Test** button in the profile's TOTP section to verify the secret generates valid codes. +## Login script times out +- Each \"wait\" step has a configurable timeout (default 10 seconds). Increase it if the server takes longer. +- Check that the wait pattern matches exactly — it's a plain text substring match, not a regular expression. +- Make sure the server actually sends the expected text; prompts can differ by locale or configuration. +## Auto-reconnect keeps failing +- Auto-reconnect only works after a successful initial connection. +- If the server is genuinely unreachable, attempts stop after the configured maximum (default 5). +- Check the reconnection settings in the profile's Advanced section. +## Serial port won't open +- Check the device path and that the adapter is plugged in; refresh the device list in the editor. +- On Linux, add your user to the `dialout` (or `uucp`) group and log in again. +- Close any other program (screen, minicom, an IDE monitor) that has the port open. +## VNC will not connect +- Check the VNC host and port. The common port is `5900` plus the display number. +- If a password is required, check whether the profile is set to saved, session-only, or ask-every-time mode. +- If using an SSH tunnel, verify the tunnel host, username, authentication method, private key, and host-key policy. +- If using direct VNC, test from a trusted network and prefer SSH tunnelling on untrusted networks. +## The terminal looks garbled or the wrong size +- Try resizing the window — the terminal re-fits automatically. +- If you're using split panes, drag the divider to give the terminal more space. +- Some older servers don't support modern terminal features. Try a different terminal type or colour scheme. +## \"Host key verification failed\" +This means the server's fingerprint has changed since your last connection. This can happen if the server was reinstalled, but it could also indicate a security threat. If you're certain it's safe, go to **Settings → Known Hosts**, find the entry, and remove it. You'll be asked to accept the new key on your next connection. +## I forgot my vault master password +The vault is designed so that without the master password the stored credentials cannot be recovered. You would need to delete the vault and set up a new one. Your connection profiles (without saved passwords) remain intact. +## SFTP panel doesn't open +SFTP is only available for **SSH** connections. Make sure you are connected to an SSH session first, then click the folder icon on the tab. +## Export/import isn't working +Use the same password to import that was used to export. Encrypted `.nexus` files cannot be opened with the wrong password.", + ), + ( + "Fun Things to Try", + "Nexus Terminal isn't just for work! Here are some entertaining things you can connect to: +## Star Wars in ASCII Art +Create a **Telnet** connection to `towel.blinkenlights.nl` on port **23**. Sit back and enjoy the original Star Wars film retold entirely in ASCII art. +## MapSCII — World Map in Your Terminal +Connect via **SSH** to `mapscii.me` on port **22**. Use the arrow keys to pan and `a`/`z` to zoom in and out of a fully interactive world map rendered in text. +## Telnet BBS Systems +Connect via **Telnet** to `particlesbbs.dyndns.org` on port **23** for a retro bulletin board system, complete with door games, message boards, and ASCII art. +## Fortune Cookies +Connect via **Telnet** to `djxmmx.net` on port **17** (the Quote of the Day protocol). You'll receive a random quote each time you connect — great for testing RAW TCP connections too. +## Test Your SSH Setup +Connect via **SSH** to `ssh.github.com` on port **443** (or `github.com` on port **22**) with your GitHub username. GitHub greets you by name if your key is set up correctly. +## RAW TCP Experiments +Create a **RAW TCP** connection to any HTTP server on port **80**, then type `GET / HTTP/1.0` followed by two blank lines to see the raw HTTP response. +## Broadcast Mode Fun +Open two or more sessions and press {Mod+Shift+B} to turn on broadcast mode. Everything you type goes to all sessions at once — try running `hostname` and watch them all respond!", + ), +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn strength_matches_web_formula() { + assert_eq!(strength(""), (0, "")); + // "abc": 9 + 10 + 20 = 39 + assert_eq!(strength("abc"), (39, "Fair")); + // "aaaa": 12 + 10 + floor(1/4*20)=5 → 27 + assert_eq!(strength("aaaa"), (27, "Fair")); + assert_eq!(strength("a"), (3 + 10 + 20, "Fair")); + assert_eq!(strength("Tr0ub4dor&3xyz!"), (98, "Strong")); + assert_eq!(strength("password1").1, "Good"); + } + + #[test] + fn keys_expand() { + assert_eq!(expand_keys("x {Mod+N} y"), format!("x {} y", keys("Mod+N"))); + assert!(HELP.iter().all(|(t, b)| !t.contains("Leviathan") && !b.contains("Leviathan") && !b.contains("drag connections"))); } } diff --git a/rust/app/src/ssh_keys.rs b/rust/app/src/ssh_keys.rs index c5c62e8..4d526de 100644 --- a/rust/app/src/ssh_keys.rs +++ b/rust/app/src/ssh_keys.rs @@ -1,26 +1,201 @@ -//! SSH Key Manager window. -//! STUB — interface fixed; implementation pending. +//! SSH Key Manager window: key pairs in ~/.ssh — copy public key, delete, generate (ssh-keygen). +use crate::settings::{Job, poll}; use crate::state::Shared; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::{RichText, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::service::SshKeyInfo; + +struct Generate { + name: String, + kind: &'static str, + bits: u32, + pass: String, + confirm: String, +} #[derive(Default)] pub struct SshKeys { open: bool, + /// Reload the list on the next frame. + stale: bool, + keys: Vec, + load_error: Option, + confirm_delete: Option, + form: Option, + job: Option>>, } impl SshKeys { pub fn open(&mut self) { self.open = true; - } - - pub fn is_open(&self) -> bool { - self.open + self.stale = true; } /// Called every frame; draws its window(s) while open. - pub fn ui(&mut self, ctx: &egui::Context, _s: &mut Shared) { - let mut open = self.open; - egui::Window::new("SshKeys").open(&mut open).show(ctx, |ui| ui.label("todo")); + pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + match poll(&mut self.job) { + Some(Ok(name)) => { + s.success(format!("Key \"{name}\" generated successfully")); + self.form = None; + self.stale = true; + } + Some(Err(e)) => s.error(format!("Generation failed: {e}")), + None => {} + } + if !self.open { + return; + } + if std::mem::take(&mut self.stale) { + match s.svc.ssh_key_pairs() { + Ok(k) => (self.keys, self.load_error) = (k, None), + Err(e) => self.load_error = Some(e.to_string()), + } + } + let t = Tokens::get(ctx); + let mut open = true; + egui::Window::new(RichText::new(format!("{} SSH Key Manager", icon::KEY)).font(theme::semibold(14.0))) + .id(egui::Id::new("ssh-keys-window")) + .open(&mut open) + .collapsible(false) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center()) + .resizable(true) + .default_size([620.0, 460.0]) + .min_size([480.0, 300.0]) + .show(ctx, |ui| { + widgets::section(ui, "Key pairs in ~/.ssh"); + if let Some(e) = &self.load_error { + ui.label(RichText::new(format!("Failed to load keys: {e}")).color(t.danger)); + } else if self.keys.is_empty() { + widgets::empty(ui, icon::KEY, "No SSH keys found", "Generate a key pair below."); + } + let list_h = if self.form.is_some() { 150.0 } else { (ui.available_height() - 40.0).max(120.0) }; + egui::ScrollArea::vertical().id_salt("keys").max_height(list_h).auto_shrink([false, true]).show(ui, |ui| { + for k in &self.keys { + egui::Frame::new().fill(t.field).corner_radius(t.radius).inner_margin(8).show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.horizontal(|ui| { + ui.label(RichText::new(&k.name).font(theme::semibold(13.0))); + widgets::chip(ui, &k.kind.to_uppercase(), t.accent); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if widgets::icon_button(ui, icon::TRASH, "Delete").clicked() { + self.confirm_delete = Some(k.name.clone()); + } + if widgets::icon_button(ui, icon::COPY, "Copy public key").clicked() { + ui.ctx().copy_text(k.public_key.clone()); + s.success("Public key copied to clipboard"); + } + }); + }); + ui.add(egui::Label::new(RichText::new(&k.public_key).font(theme::mono(11.0)).color(t.text_dim)).truncate()).on_hover_text(&k.public_key); + }); + ui.add_space(2.0); + } + }); + ui.add_space(6.0); + match &mut self.form { + None => { + if widgets::primary(ui, icon::PLUS, "Generate New Key").clicked() { + self.form = Some(Generate { name: String::new(), kind: "ed25519", bits: 4096, pass: String::new(), confirm: String::new() }); + } + } + Some(f) => { + if generate_form(ui, f, self.job.is_some()) { + let name = f.name.trim().to_string(); + if name.is_empty() { + s.error("Key name is required"); + } else if f.pass != f.confirm { + s.error("Passphrases do not match"); + } else { + let (svc, kind, bits, pass) = (s.svc.clone(), f.kind, f.bits, f.pass.clone()); + self.job = Some(Job::spawn(ctx, move || svc.generate_ssh_key(&name, kind, bits, &pass).map(|_| name).map_err(|e| e.to_string()))); + } + } + if self.job.is_none() && ui.button("Cancel").clicked() { + self.form = None; + } + } + } + }); self.open = open; + self.delete_dialog(ctx, s); + } + + fn delete_dialog(&mut self, ctx: &egui::Context, s: &mut Shared) { + let Some(name) = self.confirm_delete.clone() else { return }; + let t = Tokens::get(ctx); + let mut done = false; + let r = egui::Modal::new(egui::Id::new("delete-key")).show(ctx, |ui| { + ui.set_width(360.0); + ui.label(RichText::new("Delete Key").font(theme::semibold(15.0))); + ui.label(format!("Delete key pair \"{name}\"? This cannot be undone.")); + ui.add_space(8.0); + ui.horizontal(|ui| { + if ui.add(egui::Button::new(RichText::new(format!("{} Delete", icon::TRASH)).color(t.accent_text)).fill(t.danger)).clicked() { + match s.svc.delete_ssh_key(&name) { + Ok(()) => s.success(format!("Key \"{name}\" deleted")), + Err(e) => s.error(format!("Delete failed: {e}")), + } + self.stale = true; + done = true; + } + if ui.button("Cancel").clicked() { + done = true; + } + }); + }); + if done || r.should_close() { + self.confirm_delete = None; + } } } + +/// The generate form; true when Generate is clicked. +fn generate_form(ui: &mut egui::Ui, f: &mut Generate, busy: bool) -> bool { + widgets::section(ui, "Generate new key"); + let mut go = false; + egui::Grid::new("gen-key").num_columns(2).spacing(vec2(14.0, 8.0)).show(ui, |ui| { + ui.label("Key name"); + ui.add(egui::TextEdit::singleline(&mut f.name).hint_text(format!("id_{}_nexus", f.kind)).desired_width(260.0)); + ui.end_row(); + ui.label("Key type"); + let label = |k: &str| match k { + "rsa" => "RSA", + "ecdsa" => "ECDSA", + _ => "Ed25519 (recommended)", + }; + egui::ComboBox::from_id_salt("key-type").selected_text(label(f.kind)).width(260.0).show_ui(ui, |ui| { + for k in ["ed25519", "rsa", "ecdsa"] { + ui.selectable_value(&mut f.kind, k, label(k)); + } + }); + ui.end_row(); + if f.kind == "rsa" { + ui.label("Key size"); + ui.horizontal(|ui| { + ui.selectable_value(&mut f.bits, 4096, "4096"); + ui.selectable_value(&mut f.bits, 2048, "2048"); + }); + ui.end_row(); + } + ui.label("Passphrase"); + ui.add(egui::TextEdit::singleline(&mut f.pass).password(true).hint_text("Leave blank for no passphrase").desired_width(260.0)); + ui.end_row(); + ui.label("Confirm"); + ui.add(egui::TextEdit::singleline(&mut f.confirm).password(true).desired_width(260.0)); + ui.end_row(); + }); + ui.add_space(4.0); + ui.horizontal(|ui| { + if busy { + ui.spinner(); + ui.label("Generating…"); + } else { + go = widgets::primary(ui, icon::KEY, "Generate").clicked(); + } + }); + go +} diff --git a/rust/app/src/transfer.rs b/rust/app/src/transfer.rs index f61a671..896529d 100644 --- a/rust/app/src/transfer.rs +++ b/rust/app/src/transfer.rs @@ -1,16 +1,253 @@ //! Profile export/import: encrypted `.nexus` (also reads `.link`) and plain JSON. -//! STUB — interface fixed; implementation pending. -use crate::state::Shared; +use crate::settings::{Job, poll}; +use crate::state::{Request, Shared}; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::{RichText, Ui, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::exportimport::EXTENSION; +use std::collections::HashSet; +use std::path::PathBuf; + +const STRATEGIES: [(&str, &str); 3] = [("skip", "Skip existing"), ("overwrite", "Overwrite existing"), ("duplicate", "Create duplicates")]; + +enum Mode { + /// `selected` is filled from `preselect` (or every profile) on the first frame. + Export { preselect: Option>, selected: HashSet, pw: String, confirm: String }, + Import { path: PathBuf, pw: String, strategy: &'static str, status: String }, + ExportJson { include_secrets: bool }, + ImportJson { path: PathBuf, strategy: &'static str }, +} + +enum Done { + Exported(usize, PathBuf), + Imported(usize), +} #[derive(Default)] -pub struct Transfer {} +pub struct Transfer { + mode: Option, + job: Option>>, +} impl Transfer { /// Encrypted export of these profiles (all when empty): pick profiles, password, file. - pub fn export_encrypted(&mut self, _ids: Vec) {} - pub fn import_encrypted(&mut self) {} - pub fn export_json(&mut self) {} - pub fn import_json(&mut self) {} - pub fn ui(&mut self, _ctx: &egui::Context, _s: &mut Shared) {} + pub fn export_encrypted(&mut self, ids: Vec) { + self.mode = Some(Mode::Export { preselect: Some(ids), selected: HashSet::new(), pw: String::new(), confirm: String::new() }); + } + + pub fn import_encrypted(&mut self) { + if let Some(path) = rfd::FileDialog::new().set_title("Import Profiles").add_filter("Nexus Export", &[EXTENSION, "link"]).add_filter("All files", &["*"]).pick_file() { + self.mode = Some(Mode::Import { path, pw: String::new(), strategy: "skip", status: String::new() }); + } + } + + pub fn export_json(&mut self) { + self.mode = Some(Mode::ExportJson { include_secrets: false }); + } + + pub fn import_json(&mut self) { + if let Some(path) = rfd::FileDialog::new().set_title("Import Profiles (JSON)").add_filter("JSON", &["json"]).pick_file() { + self.mode = Some(Mode::ImportJson { path, strategy: "skip" }); + } + } + + pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + if let Some(r) = poll(&mut self.job) { + match r { + Ok(Done::Exported(n, path)) => { + s.success(format!("Exported {n} profile(s) to {}", path.display())); + self.mode = None; + } + Ok(Done::Imported(n)) => { + s.success(format!("Imported {n} profile(s)")); + s.request(Request::ReloadProfiles); + self.mode = None; + } + Err(e) => { + if let Some(Mode::Import { status, .. }) = &mut self.mode { + *status = e.split_whitespace().collect::>().join(" "); + s.error("Import failed"); + } else { + s.error(e); + } + } + } + } + let Some(mode) = &mut self.mode else { return }; + if let Mode::Export { preselect: pre @ Some(_), selected, .. } = mode { + if s.profiles.is_empty() { + s.info("No profiles to export."); + self.mode = None; + return; + } + let ids = pre.take().unwrap_or_default(); + *selected = if ids.is_empty() { s.profiles.iter().map(|p| p.id.clone()).collect() } else { ids.into_iter().collect() }; + } + let busy = self.job.is_some(); + let mut close = false; + let mut start: Option Result + Send>> = None; + let r = egui::Modal::new(egui::Id::new("transfer")).show(ctx, |ui| { + let t = Tokens::get(ui.ctx()); + match mode { + Mode::Export { selected, pw, confirm, .. } => { + ui.set_width(440.0); + title(ui, icon::EXPORT, "Export Profiles (Encrypted)"); + ui.horizontal(|ui| { + ui.label(RichText::new("Select profiles to export").color(t.text_dim)); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.small_button("None").clicked() { + selected.clear(); + } + if ui.small_button("All").clicked() { + *selected = s.profiles.iter().map(|p| p.id.clone()).collect(); + } + }); + }); + egui::Frame::new().fill(t.field).corner_radius(t.radius).inner_margin(6).show(ui, |ui| { + egui::ScrollArea::vertical().max_height(200.0).auto_shrink([false, true]).show(ui, |ui| { + for p in &s.profiles { + let mut on = selected.contains(&p.id); + let name = if p.display_name.is_empty() { &p.host } else { &p.display_name }; + let label = format!("{name} ({})", p.protocol.id()); + if ui.checkbox(&mut on, label).changed() { + if on { selected.insert(p.id.clone()) } else { selected.remove(&p.id) }; + } + } + }); + }); + ui.add_space(4.0); + ui.label("Export password"); + ui.add(egui::TextEdit::singleline(pw).password(true).hint_text("Enter a strong password").desired_width(f32::INFINITY)); + crate::settings::strength_meter(ui, pw); + widgets::hint(ui, "This password will be required to import the file."); + ui.label("Confirm password"); + ui.add(egui::TextEdit::singleline(confirm).password(true).hint_text("Confirm password").desired_width(f32::INFINITY)); + if buttons(ui, busy, icon::EXPORT, "Export", &mut close) { + if pw.is_empty() { + s.error("Please enter a password."); + } else if pw != confirm { + s.error("Passwords do not match."); + } else if selected.is_empty() { + s.error("Please select at least one profile."); + } else if let Some(path) = rfd::FileDialog::new().set_title("Export Profiles").set_file_name(format!("nexus-profiles.{EXTENSION}")).add_filter("Nexus Export", &[EXTENSION]).save_file() { + // Keep the sidebar's order. + let ids: Vec = s.profiles.iter().filter(|p| selected.contains(&p.id)).map(|p| p.id.clone()).collect(); + let (svc, pw) = (s.svc.clone(), pw.clone()); + start = Some(Box::new(move || svc.export_encrypted(&ids, &pw, &path).map(|p| Done::Exported(ids.len(), p)).map_err(|e| format!("Export failed: {e}")))); + } + } + } + Mode::Import { path, pw, strategy, status } => { + ui.set_width(420.0); + title(ui, icon::DOWNLOAD_SIMPLE, "Import Profiles (Encrypted)"); + file_line(ui, path); + ui.label("Decryption password"); + let r = ui.add(egui::TextEdit::singleline(pw).password(true).hint_text("Enter the export password").desired_width(f32::INFINITY)); + if pw.is_empty() && !busy { + r.request_focus(); + } + let enter = r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); + strategy_combo(ui, strategy); + if !status.is_empty() { + ui.label(RichText::new(status.as_str()).color(t.danger).size(12.0)); + } + if buttons(ui, busy, icon::DOWNLOAD_SIMPLE, "Import", &mut close) || (enter && !busy) { + if pw.is_empty() { + *status = "Please enter the decryption password.".into(); + } else { + status.clear(); + let (svc, pw, path, strategy) = (s.svc.clone(), pw.clone(), path.clone(), *strategy); + start = Some(Box::new(move || svc.import_encrypted(&path, &pw, strategy).map(Done::Imported).map_err(|e| e.to_string()))); + } + } + } + Mode::ExportJson { include_secrets } => { + ui.set_width(400.0); + title(ui, icon::FILE_ARROW_UP, "Export Profiles (JSON)"); + ui.label(format!("Exports all {} profile(s) as plain JSON.", s.profiles.len())); + ui.checkbox(include_secrets, "Include secrets (proxy passwords, TOTP secrets)"); + if *include_secrets { + ui.label(RichText::new(format!("{} The file will contain secrets in plain text. Prefer the encrypted export.", icon::WARNING)).color(t.warning).size(12.0)); + } + if buttons(ui, false, icon::FILE_ARROW_UP, "Export", &mut close) + && let Some(path) = rfd::FileDialog::new().set_title("Export Profiles (JSON)").set_file_name("nexus-profiles.json").add_filter("JSON", &["json"]).save_file() + { + match s.svc.export_json(*include_secrets).map_err(|e| e.to_string()).and_then(|j| std::fs::write(&path, j).map_err(|e| e.to_string())) { + Ok(()) => { + s.success(format!("Exported {} profile(s) to {}", s.profiles.len(), path.display())); + close = true; + } + Err(e) => s.error(format!("Export failed: {e}")), + } + } + } + Mode::ImportJson { path, strategy } => { + ui.set_width(400.0); + title(ui, icon::FILE_ARROW_DOWN, "Import Profiles (JSON)"); + file_line(ui, path); + strategy_combo(ui, strategy); + if buttons(ui, false, icon::FILE_ARROW_DOWN, "Import", &mut close) { + match std::fs::read_to_string(&path).map_err(|e| e.to_string()).and_then(|j| s.svc.import_json(&j, strategy).map_err(|e| e.to_string())) { + Ok(n) => { + s.success(format!("Imported {n} profile(s)")); + s.request(Request::ReloadProfiles); + close = true; + } + Err(e) => s.error(format!("Import failed: {e}")), + } + } + } + } + }); + if let Some(f) = start { + self.job = Some(Job::spawn(ctx, f)); + } + if (close || r.should_close()) && self.job.is_none() { + self.mode = None; + } + } +} + +fn title(ui: &mut Ui, glyph: &str, text: &str) { + ui.label(RichText::new(format!("{glyph} {text}")).font(theme::semibold(15.0))); + ui.add_space(4.0); +} + +fn file_line(ui: &mut Ui, path: &std::path::Path) { + let t = Tokens::get(ui.ctx()); + let name = path.file_name().map_or_else(|| path.display().to_string(), |n| n.to_string_lossy().into()); + ui.label(RichText::new(format!("{} {name}", icon::FILE)).color(t.text_dim)).on_hover_text(path.display().to_string()); +} + +fn strategy_combo(ui: &mut Ui, strategy: &mut &'static str) { + ui.horizontal(|ui| { + ui.label("If a profile already exists"); + let cur = STRATEGIES.iter().find(|s| s.0 == *strategy).map_or("", |s| s.1); + egui::ComboBox::from_id_salt("import-strategy").selected_text(cur).show_ui(ui, |ui| { + for (id, label) in STRATEGIES { + ui.selectable_value(strategy, id, label); + } + }); + }); +} + +/// Primary + Cancel row; true when the primary button is clicked. +fn buttons(ui: &mut Ui, busy: bool, glyph: &str, label: &str, close: &mut bool) -> bool { + ui.add_space(8.0); + let mut go = false; + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing = vec2(8.0, 0.0); + if busy { + ui.spinner(); + ui.label("Working…"); + } else { + go = widgets::primary(ui, glyph, label).clicked(); + if ui.button("Cancel").clicked() { + *close = true; + } + } + }); + go } From 143e4900e0c910f44e3edb85e723bb068572909e Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:32:07 +0100 Subject: [PATCH 27/45] rust: SFTP browser, file drop, port forwards --- rust/app/src/chrome.rs | 2 +- rust/app/src/forwards.rs | 137 ++++- rust/app/src/sftp.rs | 1073 +++++++++++++++++++++++++++++++++++++- 3 files changed, 1197 insertions(+), 15 deletions(-) diff --git a/rust/app/src/chrome.rs b/rust/app/src/chrome.rs index 22d8e2e..0317ea7 100644 --- a/rust/app/src/chrome.rs +++ b/rust/app/src/chrome.rs @@ -611,7 +611,7 @@ pub fn palette(app: &mut App, ctx: &egui::Context) { // --- prompts -------------------------------------------------------------------------------- -fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) { +pub fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut Ui)) { modal_backdrop(ctx); egui::Area::new(egui::Id::new(id)).order(egui::Order::Foreground).anchor(Align2::CENTER_CENTER, vec2(0.0, -40.0)).show(ctx, |ui| { egui::Frame::popup(ui.style()).corner_radius(10).inner_margin(16).show(ui, |ui| { diff --git a/rust/app/src/forwards.rs b/rust/app/src/forwards.rs index ef494ea..b16fa76 100644 --- a/rust/app/src/forwards.rs +++ b/rust/app/src/forwards.rs @@ -1,11 +1,39 @@ //! Port forwards dialog for a live SSH session: list, stop, add. -//! STUB — interface fixed; implementation pending. +use crate::chrome::modal; use crate::state::Shared; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::{Align, Key, Layout, RichText}; +use egui_phosphor::regular as icon; +use nexus_core::models::{ForwardDirection, PortForward, new_id}; +use std::sync::{Arc, Mutex}; + +struct Form { + remote: bool, + bind_host: String, + bind_port: String, + dest_host: String, + dest_port: String, + label: String, +} + +impl Default for Form { + fn default() -> Form { + Form { remote: false, bind_host: "127.0.0.1".into(), bind_port: String::new(), dest_host: "localhost".into(), dest_port: String::new(), label: String::new() } + } +} #[derive(Default)] pub struct Forwards { session: Option, + form: Form, + /// Worker results: true = a forward started, false = one stopped. + done: Arc>>, +} + +fn port(s: &str) -> Option { + s.trim().parse::().ok().filter(|&p| p > 0).map(i64::from) } impl Forwards { @@ -13,5 +41,110 @@ impl Forwards { self.session = Some(session.into()); } - pub fn ui(&mut self, _ctx: &egui::Context, _s: &mut Shared) {} + pub fn ui(&mut self, ctx: &egui::Context, s: &mut Shared) { + for started in std::mem::take(&mut *self.done.lock().unwrap()) { + if started { + s.success("Forward started"); + self.form.bind_port.clear(); + self.form.dest_port.clear(); + self.form.label.clear(); + } else { + s.info("Forward stopped"); + } + } + let Some(sid) = self.session.clone() else { return }; + // A lock and a clone in the client; no network. + let list = match s.svc.sessions.forwards(&sid) { + Ok(l) => l, + Err(_) => { + s.error("Port forwarding is only available for SSH sessions"); + self.session = None; + return; + } + }; + let t = Tokens::get(ctx); + let mut keep = !ctx.input(|i| i.key_pressed(Key::Escape)); + let mut stop = None; + let mut start = false; + let f = &mut self.form; + modal(ctx, "port-forwards", 600.0, |ui| { + ui.horizontal(|ui| { + ui.label(RichText::new(format!("{} Port Forwards", icon::ARROWS_LEFT_RIGHT)).font(theme::semibold(15.0))); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if widgets::icon_button(ui, icon::X, "Close").clicked() { + keep = false; + } + }); + }); + ui.add_space(4.0); + if list.is_empty() { + widgets::hint(ui, "No active port forwards"); + } + for fwd in &list { + ui.horizontal(|ui| { + let local = fwd.direction == ForwardDirection::Local; + widgets::chip(ui, if local { "L" } else { "R" }, if local { t.accent } else { t.warning }); + let or = |s: &str, d: &str| if s.is_empty() { d.to_string() } else { s.to_string() }; + let text = format!("{}:{} → {}:{}", or(&fwd.bind_host, "127.0.0.1"), fwd.bind_port, or(&fwd.dest_host, "localhost"), fwd.dest_port); + ui.label(RichText::new(text).font(theme::mono(12.0))); + if !fwd.label.is_empty() { + ui.label(RichText::new(&fwd.label).size(11.5).color(t.text_faint)); + } + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if ui.add(egui::Button::new(RichText::new(format!("{} Stop", icon::STOP)).color(t.accent_text)).fill(t.danger)).clicked() { + stop = Some(fwd.id.clone()); + } + }); + }); + } + ui.separator(); + widgets::section(ui, "Add new forward"); + ui.horizontal(|ui| { + egui::ComboBox::from_id_salt("pf-dir").width(96.0).selected_text(if f.remote { "Remote ←" } else { "Local →" }).show_ui(ui, |ui| { + ui.selectable_value(&mut f.remote, false, "Local →"); + ui.selectable_value(&mut f.remote, true, "Remote ←"); + }); + ui.add(egui::TextEdit::singleline(&mut f.bind_host).hint_text("127.0.0.1").desired_width(110.0)); + ui.add(egui::TextEdit::singleline(&mut f.bind_port).hint_text("Port").desired_width(52.0)); + ui.label(RichText::new(icon::ARROW_RIGHT).color(t.text_faint)); + ui.add(egui::TextEdit::singleline(&mut f.dest_host).hint_text("localhost").desired_width(110.0)); + ui.add(egui::TextEdit::singleline(&mut f.dest_port).hint_text("Port").desired_width(52.0)); + }); + ui.horizontal(|ui| { + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + start = widgets::primary(ui, icon::PLUS, "Start Forward").clicked(); + ui.add(egui::TextEdit::singleline(&mut f.label).hint_text("Label (optional)").desired_width(f32::INFINITY)); + }); + }); + }); + + if let Some(id) = stop { + let (svc, sid, done) = (s.svc.clone(), sid.clone(), self.done.clone()); + s.spawn(move || svc.sessions.stop_forward(&sid, &id).map_err(|e| format!("Failed to stop: {e}").into()), move |_| done.lock().unwrap().push(false)); + } + if start { + let f = &self.form; + match (port(&f.bind_port), port(&f.dest_port)) { + (Some(bind_port), Some(dest_port)) => { + let or = |s: &str, d: &str| if s.trim().is_empty() { d.to_string() } else { s.trim().to_string() }; + let rule = PortForward { + id: new_id(), + direction: if f.remote { ForwardDirection::Remote } else { ForwardDirection::Local }, + bind_host: or(&f.bind_host, "127.0.0.1"), + bind_port, + dest_host: or(&f.dest_host, "localhost"), + dest_port, + enabled: true, + label: f.label.trim().into(), + }; + let (svc, done) = (s.svc.clone(), self.done.clone()); + s.spawn(move || svc.sessions.start_forward(&sid, rule).map_err(|e| format!("Failed to start forward: {e}").into()), move |_| done.lock().unwrap().push(true)); + } + _ => s.error("Bind port and destination port are required"), + } + } + if !keep { + self.session = None; + } + } } diff --git a/rust/app/src/sftp.rs b/rust/app/src/sftp.rs index 46e2b2e..c9c8c41 100644 --- a/rust/app/src/sftp.rs +++ b/rust/app/src/sftp.rs @@ -1,18 +1,212 @@ //! SFTP browser (right dock), the file-drop dialog and the remote directory picker. -//! STUB — interface fixed; implementation pending. +//! Every SFTP call blocks on the network, so it runs on a worker thread; results come back +//! through `inbox` and are applied on the UI thread at the start of the next frame. -use crate::state::Shared; -use nexus_core::clients::sftp::Progress; -use std::path::PathBuf; +use crate::chrome::modal; +use crate::state::{Request, Shared}; +use crate::theme::{self, Tokens}; +use crate::views::View; +use crate::widgets; +use egui::{Align, Key, Layout, Modifiers, PointerButton, RichText, Sense, Ui, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::clients::sftp::{self as rsftp, FileEntry, Progress}; +use nexus_core::keyfiles::safe_sftp_upload_name; +use nexus_core::models::Protocol; +use nexus_core::service::Service; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +/// Worker results, applied on the UI thread. +enum Done { + Listed { session: String, path: String, files: Result, String> }, + Picker { session: String, result: Result<(String, Vec, bool), String> }, + Refresh(String), + TransferEnded { session: String, path: String }, + /// Always shown. + Toast(String), + /// Shown only while the panel for `session` is open (otherwise per-file toasts cover it). + Summary { session: String, text: String }, +} + +#[derive(Clone)] +struct Inbox(Arc>>, egui::Context); + +impl Inbox { + fn push(&self, d: Done) { + self.0.lock().unwrap().push(d); + self.1.request_repaint(); + } +} + +#[derive(Default)] +struct Panel { + cwd: String, + files: Vec, + loading: bool, + error: Option, + /// Selected paths. + selected: HashSet, + /// Index into `files` for Shift-range selection. + anchor: Option, +} + +struct Transfer { + session: String, + path: String, + upload: bool, + transferred: u64, + total: u64, +} + +enum InputKind { + Mkdir, + Rename(FileEntry), + GoTo, +} + +struct Picker { + session: String, + files: Vec, + cwd: String, + entries: Vec, + cwd_writable: bool, + filter: String, + /// Index into the visible (filtered) entries. + cursor: Option, + loading: bool, + error: Option, + focused: bool, +} + +enum Dialog { + Input { session: String, kind: InputKind, value: String }, + Delete { session: String, entries: Vec }, + Drop { session: String, paths: Vec, ssh: bool }, + Picker(Picker), +} #[derive(Default)] pub struct Sftp { open_for: Option, + panels: HashMap, + transfers: Vec, + dialog: Option, + /// Profile id (or host:port for quick connects) → bookmarked paths. Loaded on first use. + bookmarks: Option>>, + inbox: Arc>>, +} + +// --- path helpers --------------------------------------------------------------------------- + +fn parent(p: &str) -> String { + let t = p.trim_end_matches('/'); + match t.rfind('/') { + Some(i) if i > 0 => t[..i].to_string(), + _ => "/".into(), + } +} + +/// "/a/b" → [("/", "/"), ("a", "/a"), ("b", "/a/b")] +fn segments(p: &str) -> Vec<(String, String)> { + let mut out = vec![("/".to_string(), "/".to_string())]; + let mut acc = String::new(); + for part in p.split('/').filter(|s| !s.is_empty()) { + acc = format!("{acc}/{part}"); + out.push((part.to_string(), acc.clone())); + } + out +} + +/// Permission checks from the mode string, as the web build did (any r/w bit counts). +fn can_read(mode: &str) -> bool { + mode.is_empty() || mode.contains('r') +} + +fn can_write(mode: &str) -> bool { + mode.is_empty() || mode.contains('w') +} + +fn file_icon(e: &FileEntry) -> &'static str { + if e.is_dir { + return icon::FOLDER; + } + let ext = e.name.rsplit('.').next().unwrap_or("").to_lowercase(); + match ext.as_str() { + "jpg" | "jpeg" | "png" | "gif" | "svg" | "webp" => icon::FILE_IMAGE, + "mp3" | "wav" | "ogg" | "flac" => icon::FILE_AUDIO, + "mp4" | "avi" | "mkv" | "mov" => icon::FILE_VIDEO, + "zip" | "tar" | "gz" | "bz2" | "xz" | "7z" => icon::FILE_ZIP, + "sh" | "bash" | "zsh" => icon::TERMINAL, + "py" | "js" | "ts" | "go" | "rs" | "c" | "h" | "cpp" | "java" => icon::FILE_CODE, + "md" | "txt" | "log" | "csv" => icon::FILE_TEXT, + "json" | "yaml" | "yml" | "toml" | "xml" => icon::GEAR, + "key" | "pem" | "crt" | "pub" => icon::KEY, + _ => icon::FILE, + } +} + +fn base_name(p: &str) -> &str { + p.rsplit('/').next().filter(|s| !s.is_empty()).unwrap_or(p) +} + +fn local_name(p: &Path) -> String { + p.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default() +} + +fn modified(ms: i64) -> String { + use chrono::TimeZone; + chrono::Local.timestamp_millis_opt(ms).single().map(|t| t.format("%Y-%m-%d %H:%M:%S").to_string()).unwrap_or_default() +} + +fn danger(ui: &mut Ui, label: &str) -> egui::Response { + let t = Tokens::get(ui.ctx()); + ui.add(egui::Button::new(RichText::new(label).color(t.accent_text)).fill(t.danger)) +} + +fn breadcrumb(ui: &mut Ui, path: &str) -> Option { + let t = Tokens::get(ui.ctx()); + let mut go = None; + ui.horizontal_wrapped(|ui| { + ui.spacing_mut().item_spacing.x = 2.0; + for (i, (label, p)) in segments(path).into_iter().enumerate() { + if i > 1 { + ui.label(RichText::new(icon::CARET_RIGHT).size(10.0).color(t.text_faint)); + } + let last = p == path || (path.is_empty() && i == 0); + let text = RichText::new(label).size(12.0).color(if last { t.text } else { t.text_dim }); + if ui.add(egui::Button::new(text).frame(false)).on_hover_text(&p).clicked() { + go = Some(p); + } + } + }); + go +} + +fn state_line(ui: &mut Ui, loading: bool, error: Option<&str>) -> bool { + let t = Tokens::get(ui.ctx()); + let mut retry = false; + ui.vertical_centered(|ui| { + ui.add_space(28.0); + if loading { + ui.add(egui::Spinner::new()); + ui.label(RichText::new("Loading…").color(t.text_dim)); + } else if let Some(e) = error { + ui.label(RichText::new(icon::WARNING).size(28.0).color(t.warning)); + ui.label(RichText::new(e).color(t.text_dim).size(12.0)); + retry = ui.button(format!("{} Retry", icon::ARROWS_CLOCKWISE)).clicked(); + } + }); + retry } impl Sftp { pub fn toggle(&mut self, session: &str) { - self.open_for = if self.open_for.as_deref() == Some(session) { None } else { Some(session.into()) }; + if self.open_for.as_deref() == Some(session) { + self.close(session); + } else { + self.open_for = Some(session.into()); + } } /// The session whose browser is showing in the dock, if any. @@ -24,18 +218,873 @@ impl Sftp { if self.open_for.as_deref() == Some(session) { self.open_for = None; } + self.panels.remove(session); } - pub fn on_progress(&mut self, _session: &str, _p: Progress) {} + pub fn on_progress(&mut self, session: &str, p: Progress) { + let i = match self.transfers.iter().position(|t| t.session == session && t.path == p.path) { + Some(i) => i, + None if p.done => return, + None => { + self.transfers.push(Transfer { session: session.into(), path: p.path.clone(), upload: false, transferred: 0, total: 0 }); + self.transfers.len() - 1 + } + }; + let tr = &mut self.transfers[i]; + tr.transferred = p.transferred; + tr.total = p.total; + if p.done { + let tr = self.transfers.remove(i); + if self.open_for.as_deref() != Some(session) { + let verb = if tr.upload { "↑ Uploaded" } else { "↓ Downloaded" }; + self.inbox.lock().unwrap().push(Done::Toast(format!("{verb} {}", base_name(&tr.path)))); + } + } + } /// Files dropped on a terminal: offer "paste contents" or (SSH) "upload to a directory". - pub fn drop_files(&mut self, _s: &mut Shared, _session: &str, _paths: Vec) {} - - /// The dock panel. - pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { - ui.label("SFTP"); + pub fn drop_files(&mut self, s: &mut Shared, session: &str, paths: Vec) { + let ssh = s.pane(session).is_some_and(|p| p.info.protocol == Protocol::Ssh); + self.dialog = Some(Dialog::Drop { session: session.into(), paths, ssh }); } + // --- workers -------------------------------------------------------------------------- + + fn work(&self, s: &Shared, f: impl FnOnce(&Service, &Inbox) + Send + 'static) { + let (svc, inbox) = (s.svc.clone(), Inbox(self.inbox.clone(), s.ctx.clone())); + std::thread::spawn(move || f(&svc, &inbox)); + } + + fn drain(&mut self, s: &mut Shared) { + let items = std::mem::take(&mut *self.inbox.lock().unwrap()); + for d in items { + match d { + Done::Listed { session, path, files } => { + if let Some(p) = self.panels.get_mut(&session) { + p.loading = false; + match files { + Ok(f) => { + p.cwd = path; + p.files = f; + p.error = None; + } + Err(e) => p.error = Some(e), + } + } + } + Done::Picker { session, result } => { + if let Some(Dialog::Picker(pk)) = &mut self.dialog + && pk.session == session + { + pk.loading = false; + match result { + Ok((cwd, files, writable)) => { + pk.cwd = cwd; + pk.entries = files.into_iter().filter(|e| e.is_dir).collect(); + pk.cwd_writable = writable; + pk.cursor = None; + pk.error = None; + } + Err(e) => pk.error = Some(e), + } + } + } + Done::Refresh(session) => { + if self.open_for.as_deref() == Some(session.as_str()) + && let Some(cwd) = self.panels.get(&session).map(|p| p.cwd.clone()) + { + self.navigate(s, &session, Some(cwd)); + } + } + Done::TransferEnded { session, path } => self.transfers.retain(|t| !(t.session == session && t.path == path)), + Done::Toast(text) => s.success(text), + Done::Summary { session, text } => { + if self.open_for.as_deref() == Some(session.as_str()) { + s.success(text); + } + } + } + } + } + + /// Lists `path` (None: the session's cwd, falling back to the SFTP home). + fn navigate(&mut self, s: &Shared, session: &str, path: Option) { + let p = self.panels.entry(session.into()).or_default(); + if p.loading { + return; + } + p.loading = true; + p.selected.clear(); + p.anchor = None; + let sid = session.to_string(); + self.work(s, move |svc, out| { + let path = path.filter(|p| !p.is_empty()).map(Ok).unwrap_or_else(|| start_dir(svc, &sid)); + let (path, files) = match path { + Ok(p) => { + let f = svc.sftp_list(&sid, &p).map_err(|e| e.to_string()); + (p, f) + } + Err(e) => (String::new(), Err(e)), + }; + out.push(Done::Listed { session: sid, path, files }); + }); + } + + fn refresh(&mut self, s: &Shared, session: &str) { + let cwd = self.panels.get(session).map(|p| p.cwd.clone()).filter(|c| !c.is_empty()); + self.navigate(s, session, cwd); + } + + /// Uploads local files into `dir`, one after another; stops at the first failure. + fn upload(&mut self, s: &Shared, session: &str, dir: &str, files: Vec) { + if files.is_empty() { + return; + } + for f in &files { + if let Ok(name) = safe_sftp_upload_name(&local_name(f)) { + self.transfers.push(Transfer { session: session.into(), path: rsftp::join(dir, &name), upload: true, transferred: 0, total: 0 }); + } + } + let (sid, dir) = (session.to_string(), dir.to_string()); + self.work(s, move |svc, out| { + let n = files.len(); + for f in &files { + let name = local_name(f); + let remote = safe_sftp_upload_name(&name).map(|n| rsftp::join(&dir, &n)).unwrap_or_default(); + let r = svc.sftp_upload_path(&sid, &dir, f); + out.push(Done::TransferEnded { session: sid.clone(), path: remote }); + out.push(Done::Refresh(sid.clone())); + if let Err(e) = r { + // Drop the queued entries that will never start. + for g in &files { + if let Ok(n) = safe_sftp_upload_name(&local_name(g)) { + out.push(Done::TransferEnded { session: sid.clone(), path: rsftp::join(&dir, &n) }); + } + } + crate::app::post_error(format!("Upload failed for {name}: {e}")); + return; + } + } + let text = if n == 1 { format!("Uploaded {}", local_name(&files[0])) } else { format!("Uploaded {n} files") }; + out.push(Done::Summary { session: sid, text }); + }); + } + + fn download(&mut self, s: &Shared, session: &str, files: Vec) { + let targets: Vec<(FileEntry, PathBuf)> = if files.len() == 1 { + let Some(p) = rfd::FileDialog::new().set_file_name(&files[0].name).save_file() else { return }; + vec![(files[0].clone(), p)] + } else { + let Some(dir) = rfd::FileDialog::new().set_title("Download to folder").pick_folder() else { return }; + files.into_iter().filter_map(|e| safe_sftp_upload_name(&e.name).ok().map(|n| (e, dir.join(n)))).collect() + }; + for (e, _) in &targets { + self.transfers.push(Transfer { session: session.into(), path: e.path.clone(), upload: false, transferred: 0, total: e.size }); + } + let sid = session.to_string(); + self.work(s, move |svc, out| { + let n = targets.len(); + let mut failed = 0; + for (e, local) in &targets { + let r = svc.sessions.sftp_download(&sid, &e.path, local); + out.push(Done::TransferEnded { session: sid.clone(), path: e.path.clone() }); + if let Err(err) = r { + failed += 1; + crate::app::post_error(format!("Download failed: {} — {err}", e.name)); + } + } + if failed == 0 { + let text = if n == 1 { format!("Downloaded {}", targets[0].0.name) } else { format!("All {n} files downloaded") }; + out.push(Done::Summary { session: sid, text }); + } + }); + } + + fn delete(&mut self, s: &Shared, session: &str, entries: Vec) { + let sid = session.to_string(); + self.work(s, move |svc, out| { + let mut failed = 0; + for e in &entries { + if let Err(err) = svc.sftp_remove(&sid, &e.path) { + failed += 1; + crate::app::post_error(format!("Delete failed: {} — {err}", e.name)); + } + } + if failed == 0 { + let text = match entries.as_slice() { + [e] => format!("{} deleted", if e.is_dir { "Directory" } else { "File" }), + _ => format!("Deleted {} items", entries.len()), + }; + out.push(Done::Summary { session: sid.clone(), text }); + } + out.push(Done::Refresh(sid)); + }); + } + + /// Runs a single SFTP operation, then toasts `ok` and refreshes the listing. + fn op(&mut self, s: &Shared, session: &str, ok: &'static str, what: &'static str, f: impl FnOnce(&Service, &str) -> nexus_core::Result<()> + Send + 'static) { + let sid = session.to_string(); + self.work(s, move |svc, out| { + match f(svc, &sid) { + Ok(()) => out.push(Done::Summary { session: sid.clone(), text: ok.into() }), + Err(e) => crate::app::post_error(format!("{what} failed: {e}")), + } + out.push(Done::Refresh(sid)); + }); + } + + // --- bookmarks ------------------------------------------------------------------------ + + fn bookmarks_path(s: &Shared) -> PathBuf { + s.svc.config.data_dir.join("sftp-bookmarks.json") + } + + fn bookmarks(&mut self, s: &Shared) -> &mut BTreeMap> { + self.bookmarks.get_or_insert_with(|| std::fs::read(Self::bookmarks_path(s)).ok().and_then(|b| serde_json::from_slice(&b).ok()).unwrap_or_default()) + } + + fn save_bookmarks(&mut self, s: &mut Shared) { + let json = serde_json::to_vec_pretty(self.bookmarks(s)).unwrap_or_default(); + if let Err(e) = std::fs::write(Self::bookmarks_path(s), json) { + s.error(format!("Couldn't save bookmarks: {e}")); + } + } + + fn bookmark_key(s: &Shared, session: &str) -> Option { + let i = &s.pane(session)?.info; + Some(if i.profile_id.is_empty() { format!("{}:{}", i.host, i.port) } else { i.profile_id.clone() }) + } + + // --- dock panel ----------------------------------------------------------------------- + + /// The dock panel. + pub fn ui(&mut self, ui: &mut Ui, s: &mut Shared) { + self.drain(s); + let Some(session) = self.open_for.clone() else { return }; + if !self.panels.contains_key(&session) { + self.navigate(s, &session, None); + } + let t = Tokens::get(ui.ctx()); + ui.style_mut().interaction.selectable_labels = false; + let title = s.pane(&session).map(|p| p.info.tab_name.clone()).unwrap_or_default(); + let bm_key = Self::bookmark_key(s, &session); + let marks = bm_key.as_ref().and_then(|k| self.bookmarks(s).get(k).cloned()).unwrap_or_default(); + + enum Act { + Nav(String), + Refresh, + Close, + Upload, + Mkdir, + GoTo, + AddMark, + DelMark(usize), + Download(Vec), + Rename(FileEntry), + Delete(Vec), + } + let mut act = None; + + // Header: title + toolbar. + ui.horizontal(|ui| { + ui.label(RichText::new(icon::FOLDER_OPEN).size(15.0).color(t.accent)); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + ui.spacing_mut().item_spacing.x = 2.0; + if widgets::icon_button(ui, icon::X, "Close").clicked() { + act = Some(Act::Close); + } + if widgets::icon_button(ui, icon::ARROWS_CLOCKWISE, "Refresh").clicked() { + act = Some(Act::Refresh); + } + if widgets::icon_button(ui, icon::UPLOAD_SIMPLE, "Upload files").clicked() { + act = Some(Act::Upload); + } + if widgets::icon_button(ui, icon::FOLDER_PLUS, "New folder").clicked() { + act = Some(Act::Mkdir); + } + let star = widgets::icon_button(ui, icon::STAR, "Bookmarks"); + egui::Popup::menu(&star).show(|ui| { + ui.set_min_width(220.0); + widgets::section(ui, "Bookmarks"); + if marks.is_empty() { + widgets::hint(ui, "No bookmarks yet."); + } + for (i, m) in marks.iter().enumerate() { + ui.horizontal(|ui| { + if widgets::icon_button(ui, icon::X, "Remove bookmark").clicked() { + act = Some(Act::DelMark(i)); + } + if ui.add(egui::Button::new(RichText::new(m).font(theme::mono(12.0))).frame(false)).clicked() { + act = Some(Act::Nav(m.clone())); + ui.close(); + } + }); + } + ui.separator(); + if ui.button(format!("{} Bookmark current", icon::STAR)).clicked() { + act = Some(Act::AddMark); + ui.close(); + } + }); + if widgets::icon_button(ui, icon::NAVIGATION_ARROW, "Go to path").clicked() { + act = Some(Act::GoTo); + } + ui.with_layout(Layout::left_to_right(Align::Center), |ui| { + ui.add(egui::Label::new(RichText::new(&title).font(theme::semibold(13.0))).truncate()); + }); + }); + }); + + let Some(p) = self.panels.get(&session) else { return }; + if let Some(go) = breadcrumb(ui, &p.cwd) { + act = Some(Act::Nav(go)); + } + ui.separator(); + + // Transfer queue. + let mine: Vec<&Transfer> = self.transfers.iter().filter(|x| x.session == session).collect(); + if !mine.is_empty() { + widgets::section(ui, &format!("Transfers ({})", mine.len())); + egui::ScrollArea::vertical().id_salt("sftp-transfers").max_height(120.0).show(ui, |ui| { + for x in mine { + let frac = if x.total > 0 { (x.transferred as f32 / x.total as f32).min(1.0) } else { 0.0 }; + ui.horizontal(|ui| { + let arrow = if x.upload { icon::ARROW_UP } else { icon::DOWNLOAD_SIMPLE }; + ui.label(RichText::new(arrow).color(t.accent)); + ui.vertical(|ui| { + ui.add(egui::Label::new(base_name(&x.path)).truncate()).on_hover_text(&x.path); + ui.add(egui::ProgressBar::new(frac).desired_height(4.0).fill(t.accent)); + let total = if x.total > 0 { widgets::human_size(x.total) } else { "?".into() }; + ui.label(RichText::new(format!("{} / {total} ({:.0}%)", widgets::human_size(x.transferred), frac * 100.0)).size(11.0).color(t.text_faint)); + }); + }); + } + }); + ui.separator(); + } + + // File list. + let sel: Vec = p.files.iter().filter(|f| p.selected.contains(&f.path)).cloned().collect(); + let bar_h = if sel.is_empty() { 0.0 } else { 34.0 }; + let list_h = (ui.available_height() - bar_h).max(60.0); + let has_parent = !p.cwd.is_empty() && p.cwd != "/"; + let off = has_parent as usize; + let mut pick = None; + ui.allocate_ui(vec2(ui.available_width(), list_h), |ui| { + if p.loading || p.error.is_some() { + if state_line(ui, p.loading, p.error.as_deref()) { + act = Some(Act::Refresh); + } + return; + } + if p.files.is_empty() { + if has_parent && ui.add(egui::Button::new(format!("{} ..", icon::ARROW_BEND_UP_LEFT)).frame(false)).double_clicked() { + act = Some(Act::Nav(parent(&p.cwd))); + } + widgets::empty(ui, icon::FOLDER_OPEN, "Empty directory", ""); + return; + } + let clicked = widgets::rows(ui, &format!("sftp-{session}"), p.files.len() + off, 24.0, None, |ui, i| { + let r = ui.max_rect(); + if i < off { + ui.label(RichText::new(icon::ARROW_BEND_UP_LEFT).size(14.0).color(t.text_dim)); + ui.label(RichText::new("..").color(t.text_dim)); + return; + } + let e = &p.files[i - off]; + if p.selected.contains(&e.path) { + ui.painter().rect_filled(r.expand2(vec2(6.0, 0.0)), 3.0, t.row_selected); + } + ui.painter().text(r.left_center() + vec2(8.0, 0.0), egui::Align2::CENTER_CENTER, file_icon(e), egui::FontId::proportional(14.0), if e.is_dir { t.accent } else { t.text_dim }); + let meta_w = if e.is_dir { 0.0 } else { 72.0 }; + let nr = egui::Rect::from_min_max(egui::pos2(r.min.x + 22.0, r.min.y), egui::pos2(r.max.x - meta_w - 4.0, r.max.y)); + ui.scope_builder(egui::UiBuilder::new().max_rect(nr).layout(Layout::left_to_right(Align::Center)), |ui| { + ui.add(egui::Label::new(&e.name).truncate()); + }); + if !e.is_dir { + let mr = egui::Rect::from_min_max(egui::pos2(r.max.x - meta_w, r.min.y), r.max); + ui.painter().text(mr.right_center(), egui::Align2::RIGHT_CENTER, widgets::human_size(e.size), egui::FontId::proportional(11.5), t.text_faint); + if e.modified_at > 0 { + ui.interact(mr, ui.id().with(("meta", i)), Sense::hover()).on_hover_text(modified(e.modified_at)); + } + } + }); + if let Some(i) = clicked { + let dbl = ui.input(|inp| inp.pointer.button_double_clicked(PointerButton::Primary)); + if i < off { + if dbl { + act = Some(Act::Nav(parent(&p.cwd))); + } + } else if dbl && p.files[i - off].is_dir { + act = Some(Act::Nav(p.files[i - off].path.clone())); + } else if !dbl { + pick = Some((i - off, ui.input(|inp| inp.modifiers))); + } + } + }); + if let Some((fi, m)) = pick + && let Some(p) = self.panels.get_mut(&session) + { + let path = p.files[fi].path.clone(); + if m.command { + if !p.selected.remove(&path) { + p.selected.insert(path); + } + p.anchor = Some(fi); + } else if m.shift + && let Some(a) = p.anchor + { + let (lo, hi) = (a.min(fi), a.max(fi)); + p.selected = p.files[lo..=hi].iter().map(|f| f.path.clone()).collect(); + } else { + let only = p.selected.len() == 1 && p.selected.contains(&path); + p.selected.clear(); + if !only { + p.selected.insert(path); + } + p.anchor = Some(fi); + } + } + + // Selection actions. + if !sel.is_empty() { + ui.separator(); + ui.horizontal(|ui| { + let files: Vec = sel.iter().filter(|e| !e.is_dir).cloned().collect(); + let label = if sel.len() == 1 { sel[0].name.clone() } else { format!("{} items selected", sel.len()) }; + let tip = sel.iter().map(|e| e.path.as_str()).collect::>().join("\n"); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + let n = if sel.len() == 1 { String::new() } else { format!(" ({})", sel.len()) }; + if danger(ui, &format!("{} Delete{n}", icon::TRASH)).clicked() { + act = Some(Act::Delete(sel.clone())); + } + if sel.len() == 1 && widgets::button(ui, icon::PENCIL_SIMPLE, "Rename").clicked() { + act = Some(Act::Rename(sel[0].clone())); + } + if !files.is_empty() { + let n = if files.len() == 1 { String::new() } else { format!(" ({})", files.len()) }; + if widgets::primary(ui, icon::DOWNLOAD_SIMPLE, &format!("Download{n}")).clicked() { + act = Some(Act::Download(files)); + } + } + ui.with_layout(Layout::left_to_right(Align::Center), |ui| { + ui.add(egui::Label::new(RichText::new(label).color(t.text_dim)).truncate()).on_hover_text(tip); + }); + }); + }); + } + + let cwd = self.panels.get(&session).map(|p| p.cwd.clone()).unwrap_or_default(); + match act { + None => {} + Some(Act::Nav(path)) => self.navigate(s, &session, Some(path)), + Some(Act::Refresh) => self.refresh(s, &session), + Some(Act::Close) => self.close(&session), + Some(Act::Upload) => { + if let Some(files) = rfd::FileDialog::new().set_title("Upload files").pick_files() { + self.upload(s, &session, &cwd, files); + } + } + Some(Act::Mkdir) => self.dialog = Some(Dialog::Input { session, kind: InputKind::Mkdir, value: String::new() }), + Some(Act::GoTo) => self.dialog = Some(Dialog::Input { session, kind: InputKind::GoTo, value: cwd }), + Some(Act::Rename(e)) => self.dialog = Some(Dialog::Input { session, value: e.name.clone(), kind: InputKind::Rename(e) }), + Some(Act::Delete(entries)) => self.dialog = Some(Dialog::Delete { session, entries }), + Some(Act::Download(files)) => self.download(s, &session, files), + Some(Act::AddMark) => { + if let Some(k) = bm_key + && !cwd.is_empty() + { + let list = self.bookmarks(s).entry(k).or_default(); + if list.contains(&cwd) { + s.info("Already bookmarked"); + } else { + list.push(cwd); + self.save_bookmarks(s); + } + } + } + Some(Act::DelMark(i)) => { + if let Some(k) = bm_key + && let Some(list) = self.bookmarks(s).get_mut(&k) + && i < list.len() + { + list.remove(i); + if list.is_empty() { + self.bookmarks(s).remove(&k); + } + self.save_bookmarks(s); + } + } + } + } + + + + // --- dialogs -------------------------------------------------------------------------- + /// Modal dialogs (file drop, directory picker), drawn every frame. - pub fn dialogs(&mut self, _ctx: &egui::Context, _s: &mut Shared) {} + pub fn dialogs(&mut self, ctx: &egui::Context, s: &mut Shared) { + self.drain(s); + let Some(mut d) = self.dialog.take() else { return }; + let t = Tokens::get(ctx); + let esc = ctx.input(|i| i.key_pressed(Key::Escape)); + let enter = ctx.input(|i| i.key_pressed(Key::Enter)); + let mut keep = !esc; + match &mut d { + Dialog::Input { session, kind, value } => { + let (title, hint, ok) = match kind { + InputKind::Mkdir => ("New Folder", "Folder name", "Create"), + InputKind::Rename(_) => ("Rename", "New name", "Rename"), + InputKind::GoTo => ("Go to Path", "/path/to/directory", "Navigate"), + }; + let mut go = enter; + modal(ctx, "sftp-input", 360.0, |ui| { + ui.label(RichText::new(title).font(theme::semibold(15.0))); + ui.add(egui::TextEdit::singleline(value).hint_text(hint).desired_width(f32::INFINITY)).request_focus(); + ui.add_space(6.0); + ui.horizontal(|ui| { + go |= widgets::primary(ui, icon::CHECK, ok).clicked(); + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + let v = value.trim().to_string(); + if go && keep { + keep = false; + let cwd = self.panels.get(session.as_str()).map(|p| p.cwd.clone()).unwrap_or_default(); + match kind { + _ if v.is_empty() => {} + InputKind::GoTo => self.navigate(s, session, Some(v)), + InputKind::Mkdir => { + let path = rsftp::join(&cwd, &v); + self.op(s, session, "Directory created", "mkdir", move |svc, sid| svc.sftp_mkdir(sid, &path)); + } + InputKind::Rename(e) if v != e.name => { + let (from, to) = (e.path.clone(), rsftp::join(&parent(&e.path), &v)); + self.op(s, session, "Renamed", "Rename", move |svc, sid| svc.sftp_rename(sid, &from, &to)); + } + InputKind::Rename(_) => {} + } + } + } + Dialog::Delete { session, entries } => { + let (title, what, ok) = match entries.as_slice() { + [e] => (format!("Delete {}", if e.is_dir { "Folder" } else { "File" }), e.name.clone(), "Delete"), + _ => (format!("Delete {} Items", entries.len()), format!("{} selected items", entries.len()), "Delete All"), + }; + let mut go = false; + modal(ctx, "sftp-delete", 380.0, |ui| { + ui.label(RichText::new(format!("{} {title}", icon::TRASH)).font(theme::semibold(15.0))); + ui.label(format!("Permanently delete {what}?")); + ui.add_space(6.0); + ui.horizontal(|ui| { + go = danger(ui, ok).clicked(); + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + if go { + keep = false; + self.delete(s, session, std::mem::take(entries)); + } + } + Dialog::Drop { session, paths, ssh } => { + let title = match paths.as_slice() { + [p] => { + let size = std::fs::metadata(p).ok().filter(|m| m.is_file()).map(|m| format!(" ({})", widgets::human_size(m.len()))); + format!("{}{}", local_name(p), size.unwrap_or_default()) + } + _ => format!("{} files", paths.len()), + }; + let (mut paste, mut upload) = (false, false); + modal(ctx, "sftp-drop", 420.0, |ui| { + ui.label(RichText::new(format!("{} File Drop", icon::FILE_ARROW_UP)).font(theme::semibold(15.0))); + ui.label(RichText::new(title).strong()); + ui.label(RichText::new(format!("Choose what to do with the dropped {}.", if paths.len() == 1 { "file" } else { "files" })).color(t.text_dim)); + ui.add_space(8.0); + ui.horizontal(|ui| { + upload = ui + .add_enabled_ui(*ssh, |ui| widgets::primary(ui, icon::UPLOAD_SIMPLE, "Upload to current directory")) + .inner + .on_disabled_hover_text("Upload is only available for SSH sessions.") + .clicked(); + paste = widgets::button(ui, icon::CLIPBOARD_TEXT, "Paste contents").clicked(); + if ui.button("Cancel").clicked() { + keep = false; + } + }); + }); + if paste { + keep = false; + paste_files(s, session, paths); + } else if upload { + let start = self.panels.get(session.as_str()).map(|p| p.cwd.clone()).filter(|c| !c.is_empty()); + let pk = Picker { + session: session.clone(), + files: std::mem::take(paths), + cwd: start.clone().unwrap_or_default(), + entries: vec![], + cwd_writable: true, + filter: String::new(), + cursor: None, + loading: false, + error: None, + focused: false, + }; + d = Dialog::Picker(pk); + if let Dialog::Picker(pk) = &mut d { + self.picker_load(s, pk, start); + } + } + } + Dialog::Picker(pk) => { + if let Some(dir) = self.picker_ui(ctx, s, pk, &mut keep) { + keep = false; + let files = std::mem::take(&mut pk.files); + if files.len() > 1 { + s.info(format!("Uploading {} files…", files.len())); + } + let session = pk.session.clone(); + self.upload(s, &session, &dir, files); + } + } + } + if keep && self.dialog.is_none() { + self.dialog = Some(d); + } + } + + fn picker_load(&self, s: &Shared, pk: &mut Picker, path: Option) { + if pk.loading { + return; + } + pk.loading = true; + let sid = pk.session.clone(); + self.work(s, move |svc, out| { + let result = (|| -> Result<_, String> { + let path = match path.filter(|p| !p.is_empty()) { + Some(p) => p, + None => start_dir(svc, &sid)?, + }; + let files = svc.sftp_list(&sid, &path).map_err(|e| e.to_string())?; + let sftp = svc.sessions.ssh(&sid).and_then(|c| c.sftp()).map_err(|e| e.to_string())?; + let st = rsftp::stat(&sftp, &path).map_err(|e| e.to_string())?; + Ok((path, files, can_write(&st.mode))) + })(); + out.push(Done::Picker { session: sid, result }); + }); + } + + /// Draws the directory picker; returns the chosen directory. + fn picker_ui(&self, ctx: &egui::Context, s: &Shared, pk: &mut Picker, keep: &mut bool) -> Option { + let t = Tokens::get(ctx); + let filter = pk.filter.trim().to_lowercase(); + let visible: Vec = (0..pk.entries.len()).filter(|&i| pk.entries[i].name.to_lowercase().contains(&filter)).collect(); + pk.cursor = pk.cursor.filter(|&c| c < visible.len()); + let sel = pk.cursor.map(|c| &pk.entries[visible[c]]); + let (target, writable) = match sel { + Some(e) => (e.path.clone(), can_write(&e.mode)), + None => (pk.cwd.clone(), pk.cwd_writable), + }; + let ready = !pk.loading && pk.error.is_none(); + let mut chosen = None; + let mut load: Option> = None; + + // Keyboard: ↑↓ move (skipping unreadable folders), Enter uses, Backspace goes up. + if ready { + let (down, up, back) = ctx.input_mut(|i| (i.consume_key(Modifiers::NONE, Key::ArrowDown), i.consume_key(Modifiers::NONE, Key::ArrowUp), i.key_pressed(Key::Backspace))); + let delta: isize = if down { 1 } else if up { -1 } else { 0 }; + if delta != 0 && !visible.is_empty() { + let n = visible.len() as isize; + let mut idx = pk.cursor.map_or(if delta > 0 { -1 } else { n }, |c| c as isize); + for _ in 0..n { + idx = (idx + delta).rem_euclid(n); + if can_read(&pk.entries[visible[idx as usize]].mode) { + pk.cursor = Some(idx as usize); + break; + } + } + } + if ctx.input(|i| i.key_pressed(Key::Enter)) && writable && !target.is_empty() { + chosen = Some(target.clone()); + } + if back && pk.filter.is_empty() && !pk.cwd.is_empty() && pk.cwd != "/" { + load = Some(Some(parent(&pk.cwd))); + } + } + + modal(ctx, "sftp-picker", 580.0, |ui| { + ui.style_mut().interaction.selectable_labels = false; + ui.horizontal(|ui| { + ui.label(RichText::new(format!("{} Select Upload Directory", icon::FOLDER_OPEN)).font(theme::semibold(15.0))); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if widgets::icon_button(ui, icon::X, "Close").clicked() { + *keep = false; + } + }); + }); + if let Some(p) = breadcrumb(ui, &pk.cwd) { + load = Some(Some(p)); + } + ui.horizontal(|ui| { + let r = ui.add(egui::TextEdit::singleline(&mut pk.filter).hint_text("Filter folders").desired_width(260.0)); + if !pk.focused { + r.request_focus(); + pk.focused = true; + } + if r.changed() { + pk.cursor = None; + } + if widgets::button(ui, icon::ARROW_UP, "Up").clicked() && !pk.cwd.is_empty() { + load = Some(Some(parent(&pk.cwd))); + } + if widgets::button(ui, icon::ARROWS_CLOCKWISE, "Refresh").clicked() { + load = Some(Some(pk.cwd.clone())); + } + }); + ui.separator(); + let has_parent = !pk.cwd.is_empty() && pk.cwd != "/"; + let off = has_parent as usize; + ui.allocate_ui(vec2(ui.available_width(), 300.0), |ui| { + ui.set_min_height(300.0); + if !ready { + if state_line(ui, pk.loading, pk.error.as_deref()) { + load = Some(Some(pk.cwd.clone())); + } + return; + } + if visible.is_empty() && !has_parent { + widgets::empty(ui, icon::FOLDER_DASHED, "No folders found", ""); + return; + } + let n = visible.len() + off; + let clicked = widgets::rows(ui, "sftp-picker-rows", n.max(1), 24.0, pk.cursor.map(|c| c + off), |ui, i| { + if i < off { + ui.label(RichText::new(icon::ARROW_BEND_UP_LEFT).size(14.0).color(t.text_dim)); + ui.label(RichText::new("..").color(t.text_dim)); + return; + } + let Some(&ei) = visible.get(i - off) else { + widgets::hint(ui, "No folders found"); + return; + }; + let e = &pk.entries[ei]; + let (r, w) = (can_read(&e.mode), can_write(&e.mode)); + let dim = if r { t.text } else { t.text_faint }; + ui.label(RichText::new(icon::FOLDER).size(14.0).color(if r { t.accent } else { t.text_faint })); + ui.label(RichText::new(&e.name).color(dim)); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + if !r { + widgets::chip(ui, "NO READ", t.danger); + } else if !w { + widgets::chip(ui, "READ ONLY", t.warning); + } + }); + }); + if let Some(i) = clicked { + let dbl = ui.input(|inp| inp.pointer.button_double_clicked(PointerButton::Primary)); + if i < off { + if dbl { + load = Some(Some(parent(&pk.cwd))); + } + } else if let Some(&ei) = visible.get(i - off) + && can_read(&pk.entries[ei].mode) + { + if dbl { + load = Some(Some(pk.entries[ei].path.clone())); + } else { + pk.cursor = Some(i - off); + } + } + } + }); + ui.separator(); + ui.horizontal(|ui| { + let suffix = if sel.is_none() && !pk.cwd_writable { " (read only)" } else { "" }; + ui.add(egui::Label::new(RichText::new(format!("{target}{suffix}")).font(theme::mono(11.5)).color(t.text_dim)).truncate()).on_hover_text(&target); + let status = match (sel.is_some(), writable) { + (_, true) => "", + (true, false) => "No write permission", + (false, false) => "Current folder is read only", + }; + if !status.is_empty() { + ui.label(RichText::new(status).size(11.5).color(t.warning)); + } + }); + ui.horizontal(|ui| { + widgets::chip(ui, "READ ONLY", t.warning); + widgets::chip(ui, "NO READ", t.danger); + ui.with_layout(Layout::right_to_left(Align::Center), |ui| { + let ok = ui + .add_enabled_ui(ready && writable && !target.is_empty(), |ui| widgets::primary(ui, icon::CHECK, "Use this folder")) + .inner + .on_disabled_hover_text("No write permission"); + if ok.clicked() { + chosen = Some(target.clone()); + } + if ui.button("Cancel").clicked() { + *keep = false; + } + }); + }); + }); + if let Some(path) = load { + self.picker_load(s, pk, path); + } + chosen + } +} + +/// The directory a browser starts in: the shell's cwd, else the SFTP home. +fn start_dir(svc: &Service, sid: &str) -> Result { + match svc.sessions.cwd(sid) { + Ok(c) if c.trim().starts_with('/') => Ok(c.trim().to_string()), + _ => svc.sftp_pwd(sid).map_err(|e| e.to_string()), + } +} + +/// "Paste contents": the dropped text files, joined by newlines, as a terminal paste. +fn paste_files(s: &mut Shared, session: &str, paths: &[PathBuf]) { + let mut combined = String::new(); + for p in paths { + match std::fs::read_to_string(p) { + Ok(text) if !text.is_empty() => { + if !combined.is_empty() { + combined.push('\n'); + } + combined.push_str(&text); + } + Ok(_) => {} + Err(_) => { + s.error("Failed to read file contents."); + return; + } + } + } + if combined.is_empty() { + return; + } + if let Some(View::Terminal(term)) = s.panes.get_mut(session).map(|p| &mut p.view) { + let mut out = crate::terminal::TermResponse::default(); + term.paste(&combined, &mut out); + s.request(Request::Write(session.into(), out.input)); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn paths() { + assert_eq!(parent("/a/b/"), "/a"); + assert_eq!(parent("/a"), "/"); + assert_eq!(parent("/"), "/"); + let segs: Vec = segments("/home/me").into_iter().map(|s| s.1).collect(); + assert_eq!(segs, ["/", "/home", "/home/me"]); + assert!(can_read("dr-x------") && !can_write("dr-x------")); + assert!(!can_read("d-wx--x--x") && can_write("")); + } } From 19c58622888a7531e49b459515c0b1202fc30448 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:32:39 +0100 Subject: [PATCH 28/45] rust: apply sidebar width from Settings; egui Panel::show --- rust/app/src/app.rs | 11 +++++++++-- rust/app/src/sidebar.rs | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs index b284da7..d861f1d 100644 --- a/rust/app/src/app.rs +++ b/rust/app/src/app.rs @@ -80,6 +80,8 @@ pub struct App { screenshot: Option<(PathBuf, u32)>, shot_settle: u32, run_local: Option<(String, Option)>, + /// Apply `prefs.sidebar_width` to the panel on the next frame (after Settings changed it). + resize_sidebar: bool, } const fn sc(m: Modifiers, k: Key) -> KeyboardShortcut { @@ -125,6 +127,7 @@ impl App { screenshot: opts.screenshot.map(|p| (p, 0)), shot_settle: 0, run_local: opts.run_local.map(|c| (c, None)), + resize_sidebar: false, }; if let Some((_, sid)) = &mut app.run_local { let id = app.s.svc.connect_local(); @@ -488,6 +491,7 @@ impl App { Request::ReloadProfiles => self.s.reload_profiles(), Request::ReloadPreferences => { self.s.prefs = self.s.svc.preferences(); + self.resize_sidebar = true; self.theme = theme_for(&self.s.prefs.theme); theme::apply(&self.s.ctx, self.theme); } @@ -783,8 +787,11 @@ impl eframe::App for App { egui::Panel::bottom("status").exact_size(26.0).frame(egui::Frame::NONE.fill(t.chrome)).show(ui, |ui| chrome::status_bar(self, ui)); } let width = self.s.prefs.sidebar_width.clamp(180, 500) as f32; - let side = egui::Panel::left("sidebar") - .default_size(width) + let mut panel = egui::Panel::left("sidebar").default_size(width); + if std::mem::take(&mut self.resize_sidebar) { + panel = panel.exact_size(width); + } + let side = panel .min_size(180.0) .max_size(500.0) .resizable(true) diff --git a/rust/app/src/sidebar.rs b/rust/app/src/sidebar.rs index 35ad290..9e97533 100644 --- a/rust/app/src/sidebar.rs +++ b/rust/app/src/sidebar.rs @@ -32,7 +32,7 @@ pub fn ui(app: &mut App, ui: &mut Ui) { ui.add_space(4.0); // Footer actions, pinned to the bottom of the sidebar. - egui::Panel::bottom("sidebar-footer").exact_size(36.0).frame(egui::Frame::NONE.fill(t.dock)).show_inside(ui, |ui| { + egui::Panel::bottom("sidebar-footer").exact_size(36.0).frame(egui::Frame::NONE.fill(t.dock)).show(ui, |ui| { ui.horizontal_centered(|ui| { ui.add_space(6.0); if widgets::icon_button(ui, icon::PLUS, "New connection (Ctrl/Cmd+N)").clicked() { @@ -56,7 +56,7 @@ pub fn ui(app: &mut App, ui: &mut Ui) { }); }); if !app.sidebar.selected.is_empty() { - egui::Panel::bottom("sidebar-bulk").frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)).show_inside(ui, |ui| bulk_bar(app, ui)); + egui::Panel::bottom("sidebar-bulk").frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)).show(ui, |ui| bulk_bar(app, ui)); } egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| list(app, ui)); } From b6469507384a5f4abc1f9a420e740184d71230be Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:34:15 +0100 Subject: [PATCH 29/45] rust: --open for headless screenshot checks --- rust/app/src/app.rs | 33 +++++++++++++++++++++++++++++++++ rust/app/src/main.rs | 3 ++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs index d861f1d..e78cf0c 100644 --- a/rust/app/src/app.rs +++ b/rust/app/src/app.rs @@ -20,6 +20,9 @@ pub struct Options { pub theme: Option, /// Open a local terminal at startup and type this command once connected (testing aid). pub run_local: Option, + /// Open a window at startup: editor, quick, settings[:tab], keys, snippets, triggers, + /// recordings, palette, export, import (screenshot checks). + pub open: Option, } /// Errors from worker threads, drained into toasts each frame. @@ -129,6 +132,9 @@ impl App { run_local: opts.run_local.map(|c| (c, None)), resize_sidebar: false, }; + if let Some(w) = opts.open.as_deref() { + app.open_window(w); + } if let Some((_, sid)) = &mut app.run_local { let id = app.s.svc.connect_local(); *sid = Some(id.clone()); @@ -151,6 +157,33 @@ impl App { app } + fn open_window(&mut self, w: &str) { + let (w, arg) = w.split_once(':').unwrap_or((w, "")); + match w { + "editor" => match self.s.profiles.iter().find(|p| p.protocol.id() == arg || p.id == arg).map(|p| p.id.clone()) { + Some(id) => self.editor.open_edit(&self.s, &id), + None => self.editor.open_new(&self.s, Protocol::from_id(arg), ""), + }, + "quick" => self.quick.open(), + "settings" => self.settings.open(match arg { + "terminal" => SettingsTab::Terminal, + "security" => SettingsTab::Security, + "knownhosts" => SettingsTab::KnownHosts, + "help" => SettingsTab::Help, + "about" => SettingsTab::About, + _ => SettingsTab::General, + }), + "keys" => self.keys.open(), + "snippets" => self.snippets.open(), + "triggers" => self.triggers.open(), + "recordings" => self.recordings.open(), + "palette" => self.palette = Some(Default::default()), + "export" => self.transfer.export_encrypted(vec![]), + "import" => self.transfer.import_encrypted(), + _ => eprintln!("--open: unknown window {w:?}"), + } + } + // --- sessions ------------------------------------------------------------------------- fn view_for(&self, info: &SessionInfo, profile: Option<&Profile>) -> View { diff --git a/rust/app/src/main.rs b/rust/app/src/main.rs index 07c5e6f..4519310 100644 --- a/rust/app/src/main.rs +++ b/rust/app/src/main.rs @@ -33,7 +33,7 @@ use nexus_core::service::Service; use std::sync::Arc; fn main() -> eframe::Result { - let mut opts = app::Options { screenshot: None, theme: None, run_local: None }; + let mut opts = app::Options { screenshot: None, theme: None, run_local: None, open: None }; let mut size = [1360.0, 860.0]; let mut data_dir = None; let mut args = std::env::args().skip(1); @@ -41,6 +41,7 @@ fn main() -> eframe::Result { match a.as_str() { "--theme" => opts.theme = args.next(), "--run-local" => opts.run_local = args.next(), + "--open" => opts.open = args.next(), "--screenshot" => opts.screenshot = args.next().map(Into::into), // Use another data directory (testing, screenshots, portable installs). "--data-dir" => data_dir = args.next(), From 3ed4454081dea6be8a6f3ffd90fb9a4f5a7e295f Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:35:15 +0100 Subject: [PATCH 30/45] rust: RAW, IRC, GitHub and VNC views --- rust/app/src/github_view.rs | 360 +++++++++++++++++++++++- rust/app/src/irc_view.rs | 530 +++++++++++++++++++++++++++++++++++- rust/app/src/raw_view.rs | 513 +++++++++++++++++++++++++++++++++- rust/app/src/vnc_view.rs | 391 +++++++++++++++++++++++++- 4 files changed, 1761 insertions(+), 33 deletions(-) diff --git a/rust/app/src/github_view.rs b/rust/app/src/github_view.rs index 0b9a472..583598d 100644 --- a/rust/app/src/github_view.rs +++ b/rust/app/src/github_view.rs @@ -1,25 +1,367 @@ -//! GitHub repository browser. -//! STUB — interface fixed; implementation pending. +//! GitHub repository browser: owner groups of repo cards with ZIP download and Visit. Port of +//! `github.js`. use crate::state::Shared; +use crate::theme::{self, Tokens}; +use crate::widgets; +use chrono::{DateTime, Utc}; +use egui::{Color32, RichText, Sense, Ui, vec2}; +use egui_phosphor::regular as icon; use nexus_core::models::GitHubRepo; +use std::sync::{Arc, Mutex}; + +enum Msg { + Repos(Vec), + RefreshFailed(String), + Downloaded(String, String), + DownloadFailed(String), +} pub struct GithubView { pub session: String, + title: String, + login: String, + repos: Vec, + filter: String, + status: String, + status_error: bool, + inbox: Arc>>, +} + +fn parse_time(s: &str) -> Option> { + DateTime::parse_from_rfc3339(s).ok().map(|d| d.with_timezone(&Utc)) +} + +/// Latest commit, else push, else update time, in unix ms (0 when unknown). +fn commit_ts(r: &GitHubRepo) -> i64 { + [&r.latest_commit_at, &r.pushed_at, &r.updated_at].into_iter().find(|s| !s.is_empty()).and_then(|s| parse_time(s)).map_or(0, |d| d.timestamp_millis()) +} + +/// Filters, groups by owner (repos newest first), and orders groups by their newest repo with +/// the user's own login first on ties, then by name. +fn grouped<'a>(repos: &'a [GitHubRepo], login: &str, filter: &str) -> Vec<(String, Vec<&'a GitHubRepo>)> { + let f = filter.to_lowercase(); + let mut groups: Vec<(String, Vec<&GitHubRepo>)> = Vec::new(); + for r in repos.iter().filter(|r| { + f.is_empty() || r.full_name.to_lowercase().contains(&f) || r.description.to_lowercase().contains(&f) || r.language.to_lowercase().contains(&f) + }) { + let owner = if r.owner.is_empty() { "unknown" } else { &r.owner }; + match groups.iter_mut().find(|(o, _)| o == owner) { + Some((_, v)) => v.push(r), + None => groups.push((owner.to_string(), vec![r])), + } + } + for (_, v) in &mut groups { + v.sort_by_key(|r| std::cmp::Reverse(commit_ts(r))); + } + let newest = |v: &[&GitHubRepo]| v.first().map_or(0, |r| commit_ts(r)); + groups.sort_by(|(a, av), (b, bv)| newest(bv).cmp(&newest(av)).then_with(|| (b == login).cmp(&(a == login))).then_with(|| a.cmp(b))); + groups +} + +/// "just now", "3 minutes ago", "1 day ago"… (the web build's relativeTime). +fn relative_time(iso: &str, now: DateTime) -> String { + let Some(d) = parse_time(iso) else { return String::new() }; + let secs = (now - d).num_seconds(); + if secs < 60 { + return "just now".into(); + } + let plural = |n: i64, unit: &str| if n == 1 { format!("1 {unit} ago") } else { format!("{n} {unit}s ago") }; + let mins = secs / 60; + let hrs = mins / 60; + let days = hrs / 24; + let months = days / 30; + if mins < 60 { + plural(mins, "minute") + } else if hrs < 24 { + plural(hrs, "hour") + } else if days < 30 { + plural(days, "day") + } else if months < 12 { + plural(months, "month") + } else { + plural(months / 12, "year") + } +} + +fn local_time(iso: &str) -> String { + parse_time(iso).map(|d| d.with_timezone(&chrono::Local).format("%Y-%m-%d %H:%M").to_string()).unwrap_or_else(|| iso.to_string()) +} + +/// A subset of GitHub's linguist colours. +fn lang_colour(lang: &str) -> Color32 { + let hex = match lang { + "JavaScript" => 0xf1e05a, + "TypeScript" => 0x3178c6, + "Python" => 0x3572a5, + "Go" => 0x00add8, + "Rust" => 0xdea584, + "Java" => 0xb07219, + "C" => 0x555555, + "C++" => 0xf34b7d, + "C#" => 0x178600, + "Ruby" => 0x701516, + "PHP" => 0x4f5d95, + "Swift" => 0xf05138, + "Kotlin" => 0xa97bff, + "Shell" => 0x89e051, + "HTML" => 0xe34c26, + "CSS" => 0x563d7c, + "Dart" => 0x00b4ab, + "Lua" => 0x000080, + "Scala" => 0xc22d40, + "Elixir" => 0x6e4a7e, + "Haskell" => 0x5e5086, + "Zig" => 0xec915c, + "Vue" => 0x41b883, + "SCSS" => 0xc6538c, + _ => 0x8b949e, + }; + Color32::from_rgb((hex >> 16) as u8, (hex >> 8) as u8, hex as u8) +} + +enum Action { + Zip(GitHubRepo), + Visit(String), + Copy(String), } impl GithubView { - pub fn new(session: &str, _title: &str) -> GithubView { - GithubView { session: session.into() } + pub fn new(session: &str, title: &str) -> GithubView { + GithubView { + session: session.into(), + title: if title.is_empty() { "GitHub".into() } else { title.into() }, + login: String::new(), + repos: vec![], + filter: String::new(), + status: "Connecting…".into(), + status_error: false, + inbox: Default::default(), + } } - pub fn on_status(&mut self, _status: String) {} + pub fn on_status(&mut self, status: String) { + self.status = status; + self.status_error = false; + } - pub fn on_repos(&mut self, _login: String, _repos: Vec) {} + pub fn on_repos(&mut self, login: String, repos: Vec) { + self.login = login; + self.repos = repos; + self.update_status(); + } - pub fn on_error(&mut self, _error: String) {} + pub fn on_error(&mut self, error: String) { + self.status = format!("{} {error}", icon::WARNING); + self.status_error = true; + } - pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { - ui.label("GitHub"); + fn update_status(&mut self) { + let n = self.repos.len(); + let now = chrono::Local::now().format("%H:%M:%S"); + self.on_status(format!("{} · {n} repositor{} · updated {now}", self.login, if n == 1 { "y" } else { "ies" })); + } + + fn refresh(&mut self, s: &Shared) { + self.on_status("Refreshing…".into()); + let (svc, sid, inbox, ctx) = (s.svc.clone(), self.session.clone(), self.inbox.clone(), s.ctx.clone()); + std::thread::spawn(move || { + let msg = match svc.sessions.github(&sid).and_then(|c| c.fetch_repos()) { + Ok(r) => Msg::Repos(r), + Err(e) => Msg::RefreshFailed(e.to_string()), + }; + inbox.lock().unwrap().push(msg); + ctx.request_repaint(); + }); + } + + fn download(&mut self, s: &Shared, repo: &GitHubRepo) { + let reference = repo.default_branch.clone(); + let name = if reference.is_empty() { format!("{}.zip", repo.name) } else { format!("{}-{reference}.zip", repo.name) }; + let Some(path) = rfd::FileDialog::new().set_file_name(name).add_filter("ZIP archive", &["zip"]).save_file() else { return }; + self.on_status(format!("Downloading {}...", repo.full_name)); + let (svc, sid, inbox, ctx, full) = (s.svc.clone(), self.session.clone(), self.inbox.clone(), s.ctx.clone(), repo.full_name.clone()); + std::thread::spawn(move || { + let msg = match svc.sessions.github(&sid).and_then(|c| c.download_repo_archive(&full, &reference, &path)) { + Ok(()) => Msg::Downloaded(full, path.display().to_string()), + Err(e) => Msg::DownloadFailed(e.to_string()), + }; + inbox.lock().unwrap().push(msg); + ctx.request_repaint(); + }); + } + + pub fn ui(&mut self, ui: &mut Ui, s: &mut Shared) { + let msgs = std::mem::take(&mut *self.inbox.lock().unwrap()); + for m in msgs { + match m { + Msg::Repos(r) => { + let login = self.login.clone(); + self.on_repos(login, r); + } + Msg::RefreshFailed(e) => self.on_error(e), + Msg::Downloaded(name, path) => { + self.update_status(); + s.success(format!("Downloaded {name} to {path}")); + } + Msg::DownloadFailed(e) => { + self.on_error(format!("Download failed: {e}")); + s.error(format!("Download failed: {e}")); + } + } + } + + let t = Tokens::get(ui.ctx()); + egui::Frame::new().fill(t.chrome).inner_margin(egui::Margin::symmetric(12, 8)).show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.horizontal(|ui| { + ui.label(RichText::new(icon::GITHUB_LOGO).size(18.0)); + ui.label(RichText::new(&self.title).font(theme::semibold(14.0))); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if widgets::icon_button(ui, icon::ARROW_CLOCKWISE, "Refresh now").clicked() { + self.refresh(s); + } + ui.add(egui::TextEdit::singleline(&mut self.filter).hint_text(format!("{} Filter repositories…", icon::MAGNIFYING_GLASS)).desired_width(240.0)); + }); + }); + let c = if self.status_error { t.danger } else { t.text_dim }; + ui.label(RichText::new(&self.status).color(c).size(11.5)); + }); + + let mut action = None; + egui::ScrollArea::vertical().id_salt(("gh", &self.session)).auto_shrink([false, false]).show(ui, |ui| { + ui.add_space(8.0); + let groups = grouped(&self.repos, &self.login, &self.filter); + if groups.is_empty() { + let msg = if self.filter.is_empty() { "No repositories found." } else { "No matching repositories." }; + widgets::empty(ui, icon::GITHUB_LOGO, msg, ""); + return; + } + let now = Utc::now(); + egui::Frame::new().inner_margin(egui::Margin::symmetric(14, 0)).show(ui, |ui| { + for (owner, repos) in groups { + ui.horizontal(|ui| { + ui.label(RichText::new(&owner).font(theme::semibold(13.0))); + widgets::chip(ui, &repos.len().to_string(), t.text_dim); + }); + ui.add_space(2.0); + for r in repos { + if let Some(a) = card(ui, r, now) { + action = Some(a); + } + ui.add_space(6.0); + } + ui.add_space(8.0); + } + }); + }); + match action { + Some(Action::Zip(r)) => self.download(s, &r), + Some(Action::Visit(url)) if !url.is_empty() => ui.ctx().open_url(egui::OpenUrl::new_tab(url)), + Some(Action::Copy(url)) => { + ui.ctx().copy_text(url); + s.info("URL copied"); + } + _ => {} + } + } +} + +fn card(ui: &mut Ui, r: &GitHubRepo, now: DateTime) -> Option { + let t = Tokens::get(ui.ctx()); + let mut action = None; + let resp = egui::Frame::new().fill(t.card).stroke(egui::Stroke::new(1.0, t.card_border)).corner_radius(6).inner_margin(egui::Margin::symmetric(12, 9)).show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.horizontal(|ui| { + ui.label(RichText::new(&r.name).font(theme::semibold(13.5)).color(t.accent)); + if r.private { + widgets::chip(ui, "Private", t.warning); + } + if r.fork { + widgets::chip(ui, "Fork", t.text_dim); + } + if r.archived { + widgets::chip(ui, "Archived", t.danger); + } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button(format!("{} Visit", icon::ARROW_SQUARE_OUT)).on_hover_text("Open repository in browser").clicked() { + action = Some(Action::Visit(r.html_url.clone())); + } + if ui.button(format!("{} ZIP", icon::FILE_ZIP)).on_hover_text("Download repository as zip").clicked() { + action = Some(Action::Zip(r.clone())); + } + }); + }); + if !r.latest_committer.is_empty() && !r.latest_commit_at.is_empty() { + ui.label(RichText::new(format!("Last commit: {} by {}", local_time(&r.latest_commit_at), r.latest_committer)).color(t.text_faint).size(11.5)); + } + if !r.description.is_empty() { + ui.label(RichText::new(&r.description).color(t.text_dim)); + } + ui.horizontal(|ui| { + if !r.language.is_empty() { + let (dot, _) = ui.allocate_exact_size(vec2(10.0, 10.0), Sense::hover()); + ui.painter().circle_filled(dot.center(), 5.0, lang_colour(&r.language)); + ui.label(RichText::new(&r.language).color(t.text_dim).size(11.5)); + ui.add_space(8.0); + } + if r.stars > 0 { + ui.label(RichText::new(format!("{} {}", icon::STAR, widgets::group(r.stars as u64))).color(t.text_dim).size(11.5)); + ui.add_space(8.0); + } + if !r.pushed_at.is_empty() { + ui.label(RichText::new(relative_time(&r.pushed_at, now)).color(t.text_faint).size(11.5)).on_hover_text(local_time(&r.pushed_at)); + } + }); + }); + let resp = resp.response.interact(Sense::click()); + if resp.double_clicked() { + action = Some(Action::Visit(r.html_url.clone())); + } + resp.context_menu(|ui| { + if ui.button(format!("{} Visit", icon::ARROW_SQUARE_OUT)).clicked() { + action = Some(Action::Visit(r.html_url.clone())); + } + if ui.button(format!("{} Download ZIP…", icon::FILE_ZIP)).clicked() { + action = Some(Action::Zip(r.clone())); + } + if ui.button(format!("{} Copy URL", icon::COPY)).clicked() { + action = Some(Action::Copy(r.html_url.clone())); + } + }); + action +} + +#[cfg(test)] +mod tests { + use super::*; + + fn repo(owner: &str, name: &str, at: &str) -> GitHubRepo { + GitHubRepo { owner: owner.into(), name: name.into(), full_name: format!("{owner}/{name}"), pushed_at: at.into(), ..Default::default() } + } + + #[test] + fn groups_by_newest_with_login_first_on_ties() { + let repos = vec![ + repo("org", "a", "2024-01-01T00:00:00Z"), + repo("me", "old", "2023-01-01T00:00:00Z"), + repo("me", "new", "2024-01-01T00:00:00Z"), + repo("zed", "z", "2025-01-01T00:00:00Z"), + ]; + let g = grouped(&repos, "me", ""); + let names: Vec<(&str, Vec<&str>)> = g.iter().map(|(o, v)| (o.as_str(), v.iter().map(|r| r.name.as_str()).collect())).collect(); + assert_eq!(names, [("zed", vec!["z"]), ("me", vec!["new", "old"]), ("org", vec!["a"])]); + assert_eq!(grouped(&repos, "me", "OLD").len(), 1); + } + + #[test] + fn relative_times() { + let now = parse_time("2025-06-10T12:00:00Z").unwrap(); + assert_eq!(relative_time("2025-06-10T11:59:30Z", now), "just now"); + assert_eq!(relative_time("2025-06-10T11:59:00Z", now), "1 minute ago"); + assert_eq!(relative_time("2025-06-10T09:00:00Z", now), "3 hours ago"); + assert_eq!(relative_time("2025-06-01T12:00:00Z", now), "9 days ago"); + assert_eq!(relative_time("2023-06-01T12:00:00Z", now), "2 years ago"); + assert_eq!(relative_time("bad", now), ""); } } diff --git a/rust/app/src/irc_view.rs b/rust/app/src/irc_view.rs index 76dc455..a4c974d 100644 --- a/rust/app/src/irc_view.rs +++ b/rust/app/src/irc_view.rs @@ -1,23 +1,535 @@ -//! IRC client view: room tabs, message log, composer and user/room lists. -//! STUB — interface fixed; implementation pending. +//! IRC client view: room tabs, message log, composer and room/user lists. Port of `irc.js`; +//! lines are `ircformat::format_event` spans drawn in the terminal theme's colours. use crate::state::Shared; -use nexus_core::clients::irc; +use crate::terminal_themes::TermTheme; +use crate::theme::Tokens; +use crate::widgets; +use egui::text::LayoutJob; +use egui::{Color32, FontId, Galley, Rect, RichText, Sense, Stroke, TextFormat, Ui, pos2, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::clients::irc::{self, RoomInfo}; +use nexus_core::ircformat::{self, Span}; use nexus_core::models::Profile; +use std::collections::{HashMap, HashSet, VecDeque}; +use std::sync::Arc; + +const SERVER: &str = "server"; +const MAX_LINES: usize = 2000; +const SIDEBAR_W: f32 = 230.0; + +struct Line { + spans: Vec, + /// Laid-out line for the wrap width and theme it was built with. + galley: Option<(f32, &'static str, Arc)>, +} + +struct Target { + name: String, + lines: VecDeque, +} + +/// Where an event goes (irc.js `handleEvent`). +#[derive(Debug, PartialEq)] +enum Route { + Rooms, + Users(String), + RoomList, + Line(String), +} + +fn route(e: &irc::Event) -> Route { + match e.kind.as_str() { + "rooms" => Route::Rooms, + "users" => Route::Users(e.target.clone()), + "room-list" => Route::RoomList, + _ => Route::Line(if e.target.is_empty() { SERVER.into() } else { e.target.clone() }), + } +} + +fn is_room(t: &str) -> bool { + t.starts_with(['#', '&', '+', '!']) +} + +fn display_nick(u: &str) -> &str { + u.trim_start_matches(['~', '&', '@', '%', '+']) +} + +/// Tab completion of the last word: `@pre` → `@nick `, `pre` → `nick ` (first match, case-insensitive). +fn complete(input: &str, users: &[String]) -> Option { + let start = input.rfind(char::is_whitespace).map_or(0, |i| i + 1); + let word = &input[start..]; + let (at, prefix) = match word.strip_prefix('@') { + Some(p) => ("@", p), + None => ("", word), + }; + if prefix.contains('@') || (at.is_empty() && prefix.is_empty()) { + return None; + } + let lower = prefix.to_lowercase(); + let nick = users.iter().map(|u| display_nick(u)).find(|n| n.to_lowercase().starts_with(&lower))?; + Some(format!("{}{at}{nick} ", &input[..start])) +} + +fn time(ms: i64) -> String { + use chrono::{Local, TimeZone}; + let d = if ms > 0 { Local.timestamp_millis_opt(ms).single() } else { None }.unwrap_or_else(Local::now); + d.format("%H:%M:%S").to_string() +} + +/// xterm-256 palette: the theme's 16 colours, the 6×6×6 cube, then the grey ramp. +fn xterm(i: u8, theme: &TermTheme) -> Color32 { + let i = i as usize; + if i < 16 { + let [r, g, b, _] = theme.ansi[i]; + return Color32::from_rgb(r, g, b); + } + if i < 232 { + let i = i - 16; + let v = |x: usize| if x == 0 { 0 } else { (55 + x * 40) as u8 }; + return Color32::from_rgb(v(i / 36), v((i / 6) % 6), v(i % 6)); + } + let g = (8 + (i - 232) * 10) as u8; + Color32::from_rgb(g, g, g) +} + +fn rgb4(c: [u8; 4]) -> Color32 { + Color32::from_rgb(c[0], c[1], c[2]) +} + +fn layout(spans: &[Span], theme: &TermTheme, font: &FontId, wrap: f32) -> LayoutJob { + let mut job = LayoutJob { wrap: egui::text::TextWrapping { max_width: wrap, ..Default::default() }, ..Default::default() }; + let (dfg, dbg) = (rgb4(theme.foreground), Color32::TRANSPARENT); + for s in spans { + let st = s.style; + let mut fg = st.fg_rgb.map(|[r, g, b]| Color32::from_rgb(r, g, b)).or(st.fg.map(|i| xterm(i, theme))).unwrap_or(dfg); + let mut bg = st.bg_rgb.map(|[r, g, b]| Color32::from_rgb(r, g, b)).or(st.bg.map(|i| xterm(i, theme))).unwrap_or(dbg); + if st.reverse { + (fg, bg) = (if bg == Color32::TRANSPARENT { rgb4(theme.background) } else { bg }, fg); + } + if st.dim { + fg = fg.gamma_multiply(0.65); + } + let line = Stroke::new(1.0, fg); + job.append( + &s.text, + 0.0, + TextFormat { + font_id: font.clone(), + color: fg, + background: bg, + italics: st.italic, + underline: if st.underline { line } else { Stroke::NONE }, + strikethrough: if st.strike { line } else { Stroke::NONE }, + ..Default::default() + }, + ); + } + job +} pub struct IrcView { pub session: String, + targets: Vec, + active: String, + unread: HashSet, + users: HashMap>, + joined: Vec, + room_list: Vec, + listing: bool, + room_filter: String, + join_input: String, + input: String, + refreshed: bool, + focus_composer: bool, + font_size: f32, } impl IrcView { - pub fn new(session: &str, _profile: &Profile) -> IrcView { - IrcView { session: session.into() } + pub fn new(session: &str, profile: &Profile) -> IrcView { + let mut v = IrcView { + session: session.into(), + targets: vec![], + active: SERVER.into(), + unread: HashSet::new(), + users: HashMap::new(), + joined: vec![], + room_list: vec![], + listing: false, + room_filter: String::new(), + join_input: String::new(), + input: String::new(), + refreshed: false, + focus_composer: true, + font_size: if profile.terminal_font_size > 0 { profile.terminal_font_size as f32 } else { 13.0 }, + }; + v.append(SERVER, &irc::Event { kind: "system".into(), text: "Connecting...".into(), ..Default::default() }); + v } - pub fn on_event(&mut self, _event: irc::Event) {} + fn target(&mut self, name: &str) -> &mut Target { + let name = if name.is_empty() { SERVER } else { name }; + let i = match self.targets.iter().position(|t| t.name == name) { + Some(i) => i, + None => { + self.targets.push(Target { name: name.into(), lines: VecDeque::new() }); + self.targets.len() - 1 + } + }; + &mut self.targets[i] + } - /// True when an event for a room other than the visible one arrived (for the tab marker). - pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { - ui.label("IRC"); + fn append(&mut self, target: &str, e: &irc::Event) { + let spans = ircformat::format_event(e, &time(e.timestamp)); + let t = self.target(target); + t.lines.push_back(Line { spans, galley: None }); + while t.lines.len() > MAX_LINES { + t.lines.pop_front(); + } + } + + pub fn on_event(&mut self, event: irc::Event) { + match route(&event) { + Route::Rooms => { + self.joined = event.rooms; + for r in self.joined.clone() { + self.target(&r.name); + } + } + Route::Users(target) => { + if !target.is_empty() { + self.users.insert(target, event.users); + } + } + Route::RoomList => { + self.room_list = event.rooms; + self.listing = false; + } + Route::Line(target) => { + self.append(&target, &event); + if target != self.active { + self.unread.insert(target); + } + } + } + } + + fn set_active(&mut self, target: &str) { + self.target(target); + self.active = target.into(); + self.unread.remove(target); + self.focus_composer = true; + } + + fn send(&mut self, s: &mut Shared) { + let text = std::mem::take(&mut self.input); + if text.trim().is_empty() { + return; + } + let r = s.svc.sessions.irc(&self.session).and_then(|c| c.handle_input(&self.active, &text)); + if let Err(e) = r { + s.error(e.to_string()); + let active = self.active.clone(); + self.append(&active, &irc::Event { kind: "error".into(), text: e.to_string(), ..Default::default() }); + } + } + + fn join(&mut self, s: &mut Shared, room: &str) { + if let Err(e) = s.svc.sessions.irc(&self.session).and_then(|c| c.join(room)) { + s.error(format!("Join failed: {e}")); + } + } + + fn insert_mention(&mut self, nick: &str) { + if !self.input.is_empty() && !self.input.ends_with(char::is_whitespace) { + self.input.push(' '); + } + self.input.push_str(&format!("@{nick} ")); + self.focus_composer = true; + } + + pub fn ui(&mut self, ui: &mut Ui, s: &mut Shared) { + if !self.refreshed { + self.refreshed = true; + if let Ok(c) = s.svc.sessions.irc(&self.session) { + self.on_event(irc::Event { kind: "rooms".into(), rooms: c.rooms(), ..Default::default() }); + } + } + let t = Tokens::get(ui.ctx()); + let full = ui.available_rect_before_wrap(); + let side_w = SIDEBAR_W.min(full.width() * 0.4); + let main = Rect::from_min_max(full.min, pos2(full.right() - side_w, full.bottom())); + let side = Rect::from_min_max(pos2(main.right(), full.top()), full.max); + ui.allocate_rect(full, Sense::hover()); + + let tabs_h = 32.0; + let comp_h = 40.0; + let tabs = Rect::from_min_max(main.min, pos2(main.right(), main.top() + tabs_h)); + let log = Rect::from_min_max(pos2(main.left(), tabs.bottom()), pos2(main.right(), main.bottom() - comp_h)); + let comp = Rect::from_min_max(pos2(main.left(), log.bottom()), main.max); + + ui.painter().rect_filled(tabs, 0.0, t.tab_strip); + let mut c = ui.new_child(egui::UiBuilder::new().max_rect(tabs.shrink2(vec2(6.0, 4.0))).layout(egui::Layout::left_to_right(egui::Align::Center))); + self.tabs(&mut c, s); + + let theme = crate::terminal_themes::get(&s.prefs.terminal_theme); + ui.painter().rect_filled(log, 0.0, rgb4(theme.background)); + let mut c = ui.new_child(egui::UiBuilder::new().max_rect(log.shrink2(vec2(10.0, 6.0)))); + self.log(&mut c, theme); + + ui.painter().rect_filled(comp, 0.0, t.chrome); + let mut c = ui.new_child(egui::UiBuilder::new().max_rect(comp.shrink2(vec2(8.0, 6.0))).layout(egui::Layout::left_to_right(egui::Align::Center))); + self.composer(&mut c, s); + + ui.painter().rect_filled(side, 0.0, t.dock); + ui.painter().line_segment([side.left_top(), side.left_bottom()], Stroke::new(1.0, t.separator)); + let mut c = ui.new_child(egui::UiBuilder::new().max_rect(side.shrink2(vec2(10.0, 8.0)))); + self.sidebar(&mut c, s); + } + + fn tabs(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + egui::ScrollArea::horizontal().id_salt("irc-tabs").show(ui, |ui| { + ui.horizontal(|ui| { + let names: Vec = self.targets.iter().map(|t| t.name.clone()).collect(); + for name in names { + let label = if name == SERVER { format!("{} Server", icon::HARD_DRIVES) } else { name.clone() }; + let unread = self.unread.contains(&name); + let text = if unread { RichText::new(format!("{label} •")).color(t.accent) } else { RichText::new(label) }; + let r = ui.selectable_label(self.active == name, text); + if r.clicked() { + self.set_active(&name); + } + if is_room(&name) { + r.context_menu(|ui| { + if ui.button(format!("{} Leave {name}", icon::SIGN_OUT)).clicked() + && let Err(e) = s.svc.sessions.irc(&self.session).and_then(|c| c.part(&name)) + { + s.error(format!("Part failed: {e}")); + } + }); + } + } + }); + }); + } + + fn log(&mut self, ui: &mut Ui, theme: &'static TermTheme) { + let font = FontId::monospace(self.font_size); + let wrap = ui.available_width().max(100.0); + let Some(target) = self.targets.iter_mut().find(|t| t.name == self.active) else { return }; + // Lay out lines once per width/theme; the galley cache keeps scrolling cheap. + let mut tops = Vec::with_capacity(target.lines.len()); + let mut y = 0.0; + for line in target.lines.iter_mut() { + let stale = line.galley.as_ref().is_none_or(|(w, id, _)| *w != wrap || *id != theme.id); + if stale { + let g = ui.fonts_mut(|f| f.layout_job(layout(&line.spans, theme, &font, wrap))); + line.galley = Some((wrap, theme.id, g)); + } + tops.push(y); + y += line.galley.as_ref().map_or(0.0, |(_, _, g)| g.size().y) + 1.0; + } + let total = y; + egui::ScrollArea::vertical().id_salt(("irc-log", &self.active)).auto_shrink([false, false]).stick_to_bottom(true).show_viewport(ui, |ui, vp| { + let (rect, _) = ui.allocate_exact_size(vec2(wrap, total), Sense::hover()); + let first = tops.partition_point(|&top| top < vp.min.y).saturating_sub(1); + for (i, line) in target.lines.iter().enumerate().skip(first) { + if tops[i] > vp.max.y { + break; + } + if let Some((_, _, g)) = &line.galley { + ui.painter().galley(pos2(rect.left(), rect.top() + tops[i]), g.clone(), rgb4(theme.foreground)); + } + } + }); + } + + fn composer(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + let id = egui::Id::new(("irc-composer", &self.session)); + // Tab completes nicks instead of moving focus. + if ui.memory(|m| m.has_focus(id)) && ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Tab)) && is_room(&self.active) { + let users = self.users.get(&self.active).cloned().unwrap_or_default(); + if let Some(done) = complete(&self.input, &users) { + self.input = done; + self.move_cursor_to_end(ui, id); + } + } + let mut send = false; + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + send |= ui.add(egui::Button::new(RichText::new(format!("{} Send", icon::PAPER_PLANE_RIGHT)).color(t.accent_text)).fill(t.accent)).clicked(); + let hint = if self.active == SERVER { "/command or /msg ".to_string() } else { format!("Message {} or /command", self.active) }; + let r = ui.add(egui::TextEdit::singleline(&mut self.input).id(id).hint_text(hint).desired_width(ui.available_width())); + if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { + send = true; + self.focus_composer = true; + } + }); + if send { + self.send(s); + } + if std::mem::take(&mut self.focus_composer) { + ui.memory_mut(|m| m.request_focus(id)); + self.move_cursor_to_end(ui, id); + } + } + + fn move_cursor_to_end(&self, ui: &Ui, id: egui::Id) { + if let Some(mut st) = egui::TextEdit::load_state(ui.ctx(), id) { + let end = egui::text::CCursor::new(self.input.chars().count()); + st.cursor.set_char_range(Some(egui::text::CCursorRange::one(end))); + st.store(ui.ctx(), id); + } + } + + fn sidebar(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + let total_h = ui.available_height(); + + widgets::section(ui, "Rooms"); + let mut join = false; + ui.horizontal(|ui| { + let r = ui.add(egui::TextEdit::singleline(&mut self.join_input).hint_text("#room").desired_width(ui.available_width() - 52.0)); + join |= r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)); + join |= ui.button("Join").clicked(); + }); + if join && !self.join_input.trim().is_empty() { + let room = self.join_input.trim().to_string(); + self.join(s, &room); + self.join_input.clear(); + } + let rows_h = 22.0; + if self.joined.is_empty() { + widgets::hint(ui, "No joined rooms"); + } else { + let joined = self.joined.clone(); + let h = (joined.len() as f32 * (rows_h + 2.0)).min(total_h * 0.22); + ui.allocate_ui(vec2(ui.available_width(), h), |ui| { + let sel = joined.iter().position(|r| r.name == self.active); + let clicked = widgets::rows(ui, "irc-joined", joined.len(), rows_h, sel, |ui, i| { + let r = &joined[i]; + ui.label(&r.name); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.label(RichText::new(r.users.to_string()).color(t.text_faint).size(11.0)); + }); + let tip = ircformat::plain_text(if r.topic.is_empty() { &r.name } else { &r.topic }); + ui.interact(ui.max_rect(), ui.id().with("tip"), Sense::hover()).on_hover_text(tip); + }); + if let Some(i) = clicked { + self.set_active(&joined[i].name); + } + }); + } + + ui.add_space(6.0); + ui.horizontal(|ui| { + widgets::section(ui, "Room List"); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.small_button(format!("{} List", icon::LIST_BULLETS)).clicked() { + self.room_list.clear(); + self.listing = true; + if let Err(e) = s.svc.sessions.irc(&self.session).and_then(|c| c.list_rooms()) { + self.listing = false; + s.error(format!("Room list failed: {e}")); + } + } + }); + }); + ui.add(egui::TextEdit::singleline(&mut self.room_filter).hint_text("Filter rooms").desired_width(ui.available_width())); + let filter = self.room_filter.to_lowercase(); + let listed: Vec<&RoomInfo> = + self.room_list.iter().filter(|r| filter.is_empty() || format!("{} {}", r.name, r.topic).to_lowercase().contains(&filter)).collect(); + let list_h = total_h * 0.3; + let mut join_room = None; + if listed.is_empty() { + widgets::hint(ui, if self.listing { "Loading rooms..." } else { "No rooms loaded" }); + } else { + ui.allocate_ui(vec2(ui.available_width(), list_h), |ui| { + let clicked = widgets::rows(ui, "irc-room-list", listed.len(), rows_h, None, |ui, i| { + let r = listed[i]; + ui.label(&r.name); + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.label(RichText::new(r.users.to_string()).color(t.text_faint).size(11.0)); + ui.label(RichText::new(ircformat::plain_text(&r.topic)).color(t.text_faint).size(11.0)); + }); + ui.interact(ui.max_rect(), ui.id().with("tip"), Sense::hover()).on_hover_text(format!("{}\nClick to join", ircformat::plain_text(if r.topic.is_empty() { &r.name } else { &r.topic }))); + }); + join_room = clicked.map(|i| listed[i].name.clone()); + }); + } + if let Some(room) = join_room { + self.join(s, &room); + } + + ui.add_space(6.0); + let users = if is_room(&self.active) { self.users.get(&self.active).cloned() } else { None }; + widgets::section(ui, &format!("Users{}", users.as_ref().map(|u| format!(" ({})", u.len())).unwrap_or_default())); + match users { + _ if !is_room(&self.active) => widgets::hint(ui, "No room selected"), + Some(u) if !u.is_empty() => { + let clicked = widgets::rows(ui, "irc-users", u.len(), rows_h, None, |ui, i| { + let nick = display_nick(&u[i]); + ui.label(RichText::new(nick).color(xterm(ircformat::nick_colour(nick), crate::terminal_themes::get(&s.prefs.terminal_theme)))); + ui.interact(ui.max_rect(), ui.id().with("tip"), Sense::hover()).on_hover_text(format!("{}\nClick to mention", u[i])); + }); + if let Some(i) = clicked { + self.insert_mention(&display_nick(&u[i]).to_string()); + } + } + _ => widgets::hint(ui, "No users yet"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn ev(kind: &str, target: &str) -> irc::Event { + irc::Event { kind: kind.into(), target: target.into(), text: "hi".into(), ..Default::default() } + } + + #[test] + fn routes_like_irc_js() { + assert_eq!(route(&ev("rooms", "")), Route::Rooms); + assert_eq!(route(&ev("users", "#a")), Route::Users("#a".into())); + assert_eq!(route(&ev("room-list", "")), Route::RoomList); + assert_eq!(route(&ev("message", "#a")), Route::Line("#a".into())); + assert_eq!(route(&ev("quit", "")), Route::Line(SERVER.into())); + } + + #[test] + fn events_fill_targets_and_unread() { + let mut v = IrcView::new("s", &Profile::new(nexus_core::models::Protocol::Irc)); + v.on_event(ev("message", "#rust")); + v.on_event(ev("nick", "")); + assert_eq!(v.targets.iter().map(|t| t.name.as_str()).collect::>(), [SERVER, "#rust"]); + assert!(v.unread.contains("#rust") && !v.unread.contains(SERVER)); + assert_eq!(v.targets[0].lines.len(), 2); // "Connecting..." + nick + v.set_active("#rust"); + assert!(v.unread.is_empty()); + for _ in 0..MAX_LINES + 5 { + v.on_event(ev("message", "#rust")); + } + assert_eq!(v.targets[1].lines.len(), MAX_LINES); + } + + #[test] + fn nick_completion() { + let users = vec!["@Alice".to_string(), "+bob".to_string()]; + assert_eq!(complete("hi @al", &users).as_deref(), Some("hi @Alice ")); + assert_eq!(complete("bo", &users).as_deref(), Some("bob ")); + assert_eq!(complete("hi ", &users), None); + assert_eq!(complete("x @zz", &users), None); + assert_eq!(display_nick("~&@%+nick"), "nick"); + } + + #[test] + fn palette() { + let th = crate::terminal_themes::get("charcoal"); + assert_eq!(xterm(16, th), Color32::from_rgb(0, 0, 0)); + assert_eq!(xterm(231, th), Color32::from_rgb(255, 255, 255)); + assert_eq!(xterm(244, th), Color32::from_rgb(128, 128, 128)); } } diff --git a/rust/app/src/raw_view.rs b/rust/app/src/raw_view.rs index 33e8210..ba1e5c4 100644 --- a/rust/app/src/raw_view.rs +++ b/rust/app/src/raw_view.rs @@ -1,22 +1,519 @@ -//! RAW TCP viewer: text / hex / split views of the capture and a send bar. -//! STUB — interface fixed; implementation pending. +//! RAW TCP viewer: text / hex / split views of the capture, pause, clear, save, and a send bar. +//! Port of the web build's `raw.js`. Sent data comes back through `Event::RawData` ("send"), so +//! the echo of a sent entry needs no local bookkeeping. use crate::state::Shared; -use nexus_core::models::Profile; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::text::LayoutJob; +use egui::{Color32, FontId, RichText, Sense, TextFormat, Ui, pos2, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::clients::raw::format_hex_dump; +use nexus_core::clients::raw::to_display_encoding; +use nexus_core::models::{PayloadPreset, Profile}; +use std::collections::VecDeque; + +/// Entries kept in the viewer (the core keeps its own, larger capture history). +const MAX_ENTRIES: usize = 5000; + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Mode { + Text, + Hex, + Split, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Format { + Text, + Hex, + Escaped, +} + +impl Format { + fn label(self) -> &'static str { + match self { + Format::Text => "Text", + Format::Hex => "Hex", + Format::Escaped => "Escaped", + } + } + fn from_id(s: &str) -> Format { + match s { + "hex" => Format::Hex, + "escaped" => Format::Escaped, + _ => Format::Text, + } + } +} + +struct Entry { + seq: u64, + send: bool, + timestamp: i64, + data: Vec, +} + +/// One display row. `header` rows carry the direction/time line of a hex or split entry. +#[derive(Debug, PartialEq)] +struct Row { + seq: u64, + send: bool, + header: bool, + left: String, + /// The hex dump column in split mode. + right: String, +} pub struct RawView { pub session: String, + show_timestamps: bool, + show_direction: bool, + encoding: String, + presets: Vec, + mode: Mode, + paused: bool, + entries: VecDeque, + next_seq: u64, + rows: VecDeque, + /// Widest row in characters (for horizontal scrolling). + widest: usize, + tx_bytes: u64, + rx_bytes: u64, + format: Format, + input: String, } impl RawView { - pub fn new(session: &str, _profile: &Profile) -> RawView { - RawView { session: session.into() } + pub fn new(session: &str, profile: &Profile) -> RawView { + let raw = profile.raw.clone().unwrap_or_default(); + RawView { + session: session.into(), + show_timestamps: raw.show_timestamps, + show_direction: raw.show_direction, + encoding: raw.encoding, + presets: raw.payload_presets, + mode: Mode::Text, + paused: false, + entries: VecDeque::new(), + next_seq: 0, + rows: VecDeque::new(), + widest: 0, + tx_bytes: 0, + rx_bytes: 0, + format: Format::Text, + input: String::new(), + } } /// Data from `Event::RawData` (direction "send"/"recv", unix ms timestamp). - pub fn on_data(&mut self, _data: &[u8], _direction: &str, _timestamp: i64) {} + pub fn on_data(&mut self, data: &[u8], direction: &str, timestamp: i64) { + let send = direction == "send"; + if send { + self.tx_bytes += data.len() as u64; + } else { + self.rx_bytes += data.len() as u64; + } + let entry = Entry { seq: self.next_seq, send, timestamp, data: data.to_vec() }; + self.next_seq += 1; + if !self.paused { + let rows = self.rows_for(&entry); + self.push_rows(rows); + } + self.entries.push_back(entry); + while self.entries.len() > MAX_ENTRIES { + self.entries.pop_front(); + } + // A paused display stays frozen; it is rebuilt on resume. + if !self.paused + && let Some(first) = self.entries.front().map(|e| e.seq) + { + while self.rows.front().is_some_and(|r| r.seq < first) { + self.rows.pop_front(); + } + } + } - pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { - ui.label("RAW viewer"); + fn push_rows(&mut self, rows: Vec) { + for r in rows { + self.widest = self.widest.max(r.left.chars().count() + r.right.chars().count()); + self.rows.push_back(r); + } + } + + fn rebuild(&mut self) { + self.rows.clear(); + self.widest = 0; + let rows: Vec = self.entries.iter().flat_map(|e| self.rows_for(e)).collect(); + self.push_rows(rows); + } + + fn prefix(&self, e: &Entry) -> String { + let mut p = String::new(); + if self.show_timestamps { + p.push_str(&format!("[{}] ", format_time(e.timestamp))); + } + if self.show_direction { + p.push_str(if e.send { "[TX] " } else { "[RX] " }); + } + p + } + + fn rows_for(&self, e: &Entry) -> Vec { + let row = |header: bool, left: String, right: String| Row { seq: e.seq, send: e.send, header, left, right }; + match self.mode { + Mode::Text => { + let prefix = self.prefix(e); + let pad = " ".repeat(prefix.chars().count()); + text_lines(&to_display_encoding(&e.data, &self.encoding)) + .into_iter() + .enumerate() + .map(|(i, l)| row(false, format!("{}{l}", if i == 0 { &prefix } else { &pad }), String::new())) + .collect() + } + Mode::Hex => { + let mut out = Vec::new(); + let prefix = self.prefix(e); + if !prefix.is_empty() { + out.push(row(true, format!("{}· {} bytes", prefix, e.data.len()), String::new())); + } + out.extend(format_hex_dump(&e.data).lines().map(|l| row(false, l.to_string(), String::new()))); + out + } + Mode::Split => { + let mut head = String::new(); + if self.show_timestamps { + head.push_str(&format_time(e.timestamp)); + head.push(' '); + } + head.push_str(if e.send { "▲ TX" } else { "▼ RX" }); + let mut out = vec![row(true, format!("{head} · {} bytes", e.data.len()), String::new())]; + let text = text_lines(&to_display_encoding(&e.data, &self.encoding)); + let dump = format_hex_dump(&e.data); + let hex: Vec<&str> = dump.lines().collect(); + for i in 0..text.len().max(hex.len()) { + out.push(row(false, text.get(i).cloned().unwrap_or_default(), hex.get(i).map(|s| s.to_string()).unwrap_or_default())); + } + out + } + } + } + + fn set_mode(&mut self, mode: Mode) { + if self.mode != mode { + self.mode = mode; + self.rebuild(); + } + } + + fn send(&mut self, s: &mut Shared) { + if self.input.is_empty() { + return; + } + let m = &s.svc.sessions; + let r = match self.format { + Format::Text => m.raw_write_text(&self.session, &self.input), + Format::Hex => m.raw_write_hex(&self.session, &self.input), + Format::Escaped => m.raw_write_escaped(&self.session, &self.input), + }; + match r { + Ok(()) => self.input.clear(), + Err(e) => s.error(format!("Send failed: {e}")), + } + } + + fn save(&mut self, s: &mut Shared) { + if self.entries.is_empty() { + s.warn("Nothing to save"); + return; + } + let Some(path) = rfd::FileDialog::new().set_file_name("capture.json").add_filter("JSON", &["json"]).save_file() else { return }; + match s.svc.sessions.raw_save_capture(&self.session, &path) { + Ok(()) => s.success("Capture saved"), + Err(e) => s.error(format!("Save failed: {e}")), + } + } + + fn clear(&mut self, s: &mut Shared) { + self.entries.clear(); + self.rows.clear(); + self.widest = 0; + self.tx_bytes = 0; + self.rx_bytes = 0; + let _ = s.svc.sessions.raw_clear_history(&self.session); + } + + pub fn ui(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + egui::Frame::new().fill(t.chrome).inner_margin(egui::Margin::symmetric(8, 5)).show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.horizontal(|ui| self.toolbar(ui, s)); + }); + let send_h = 40.0; + let h = (ui.available_height() - send_h).max(40.0); + let (rect, _) = ui.allocate_exact_size(vec2(ui.available_width(), h), Sense::hover()); + ui.painter().rect_filled(rect, 0.0, t.canvas); + let mut child = ui.new_child(egui::UiBuilder::new().max_rect(rect.shrink2(vec2(8.0, 4.0)))); + self.display(&mut child); + egui::Frame::new().fill(t.chrome).inner_margin(egui::Margin::symmetric(8, 6)).show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.horizontal(|ui| self.send_bar(ui, s)); + }); + } + + fn toolbar(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + for (m, label, glyph) in [(Mode::Text, "Text", icon::TEXT_AA), (Mode::Hex, "Hex", icon::HASH), (Mode::Split, "Split", icon::COLUMNS)] { + if ui.selectable_label(self.mode == m, format!("{glyph} {label}")).clicked() { + self.set_mode(m); + } + } + ui.separator(); + let (g, label) = if self.paused { (icon::PLAY, "Resume") } else { (icon::PAUSE, "Pause") }; + if widgets::button(ui, g, label).clicked() { + self.paused = !self.paused; + if !self.paused { + self.rebuild(); + } + } + if widgets::button(ui, icon::TRASH, "Clear").clicked() { + self.clear(s); + } + if widgets::button(ui, icon::FLOPPY_DISK, "Save").clicked() { + self.save(s); + } + if self.paused { + widgets::chip(ui, "PAUSED", t.warning); + } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + let total = self.tx_bytes + self.rx_bytes; + ui.label(RichText::new(widgets::human_size(total)).font(theme::semibold(12.0))); + ui.label(RichText::new(format!("{} RX {}", icon::ARROW_DOWN, widgets::human_size(self.rx_bytes))).color(t.text_dim).size(11.5)); + ui.label(RichText::new(format!("{} TX {}", icon::ARROW_UP, widgets::human_size(self.tx_bytes))).color(t.accent).size(11.5)); + ui.label(RichText::new(format!("{} entries", widgets::group(self.entries.len() as u64))).color(t.text_faint).size(11.5)); + }); + } + + fn display(&mut self, ui: &mut Ui) { + let t = Tokens::get(ui.ctx()); + if self.rows.is_empty() { + widgets::empty(ui, icon::BROADCAST, if self.paused { "Paused" } else { "No data yet" }, "Received and sent bytes appear here"); + return; + } + let font = theme::mono(12.5); + let (cw, rh) = ui.fonts_mut(|f| (f.glyph_width(&font, '0'), f.row_height(&font))); + let split = self.mode == Mode::Split; + let avail = ui.available_width(); + // Split mode: the text column takes what the hex dump (77 chars) leaves, at least 30 chars. + let hex_w = 79.0 * cw; + let left_w = if split { (avail - hex_w - 12.0).max(30.0 * cw) } else { 0.0 }; + let width = if split { left_w + 12.0 + hex_w } else { self.widest as f32 * cw + 16.0 }; + ui.spacing_mut().item_spacing.y = 0.0; + egui::ScrollArea::both().auto_shrink([false, false]).stick_to_bottom(true).show_rows(ui, rh, self.rows.len(), |ui, range| { + for i in range { + let row = &self.rows[i]; + let (rect, _) = ui.allocate_exact_size(vec2(width.max(avail), rh), Sense::hover()); + let fg = if row.send { t.accent } else { t.byte_print }; + let p = ui.painter(); + if row.header { + p.rect_filled(rect, 0.0, t.text.gamma_multiply(0.04)); + p.text(rect.left_center(), egui::Align2::LEFT_CENTER, &row.left, font.clone(), if row.send { t.accent } else { t.text_dim }); + continue; + } + match self.mode { + Mode::Text => { + p.text(rect.left_center(), egui::Align2::LEFT_CENTER, &row.left, font.clone(), fg); + } + Mode::Hex => { + let g = ui.fonts_mut(|f| f.layout_job(hex_job(&row.left, &font, &t, fg))); + p.galley(pos2(rect.left(), rect.top()), g, fg); + } + Mode::Split => { + let clip = egui::Rect::from_min_size(rect.min, vec2(left_w, rh)).intersect(ui.clip_rect()); + p.with_clip_rect(clip).text(rect.left_center(), egui::Align2::LEFT_CENTER, &row.left, font.clone(), fg); + let x = rect.left() + left_w + 6.0; + p.line_segment([pos2(x, rect.top()), pos2(x, rect.bottom())], egui::Stroke::new(1.0, t.separator)); + if !row.right.is_empty() { + let g = ui.fonts_mut(|f| f.layout_job(hex_job(&row.right, &font, &t, fg))); + p.galley(pos2(x + 6.0, rect.top()), g, fg); + } + } + } + } + }); + } + + fn send_bar(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + egui::ComboBox::from_id_salt(("raw-format", &self.session)).width(84.0).selected_text(self.format.label()).show_ui(ui, |ui| { + for f in [Format::Text, Format::Hex, Format::Escaped] { + ui.selectable_value(&mut self.format, f, f.label()); + } + }); + // Right-hand controls are laid out first so the input takes the remaining width. + let mut send = false; + let mut insert: Option<&str> = None; + let mut focus = false; + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + send |= ui.add(egui::Button::new(RichText::new(format!("{} Send", icon::PAPER_PLANE_RIGHT)).color(t.accent_text)).fill(t.accent)).clicked(); + if !self.presets.is_empty() { + egui::ComboBox::from_id_salt(("raw-presets", &self.session)).width(110.0).selected_text("Presets…").show_ui(ui, |ui| { + for p in &self.presets { + if ui.selectable_label(false, &p.name).clicked() { + self.input = p.payload.clone(); + self.format = Format::from_id(&p.format); + focus = true; + } + } + }); + } + for (label, esc) in [("EOT", "\\x04"), ("ETX", "\\x03"), ("LF", "\\n"), ("CR", "\\r"), ("NUL", "\\x00")] { + if ui.small_button(label).on_hover_text(esc).clicked() { + insert = Some(esc); + } + } + let r = ui.add( + egui::TextEdit::singleline(&mut self.input) + .id_salt(("raw-input", &self.session)) + .font(theme::mono(12.5)) + .hint_text("Type data to send…") + .desired_width(ui.available_width()), + ); + if r.lost_focus() && ui.input(|i| i.key_pressed(egui::Key::Enter)) { + send = true; + focus = true; + } + if let Some(esc) = insert { + self.insert_byte(esc); + focus = true; + } + if focus { + r.request_focus(); + } + }); + if send { + self.send(s); + } + } + + /// Appends a quick byte in the current format (Text switches to Escaped so it isn't sent literally). + fn insert_byte(&mut self, esc: &str) { + match self.format { + Format::Hex => { + if !self.input.is_empty() && !self.input.ends_with(' ') { + self.input.push(' '); + } + self.input.push_str(&esc_to_hex(esc)); + } + Format::Text | Format::Escaped => { + self.format = Format::Escaped; + self.input.push_str(esc); + } + } + } +} + +/// "\\x04" / "\\n" → "04" / "0a". +fn esc_to_hex(esc: &str) -> String { + nexus_core::clients::raw::parse_escaped(esc).map(|b| nexus_core::clients::raw::format_hex(&b)).unwrap_or_default() +} + +/// Splits decoded text into display lines: CRLF/LF break lines, other control characters show as '·'. +fn text_lines(s: &str) -> Vec { + let s = s.strip_suffix('\n').unwrap_or(s); + s.split('\n') + .map(|l| { + l.trim_end_matches('\r') + .chars() + .map(|c| match c { + '\t' => " ".to_string(), + c if c.is_control() => "·".to_string(), + c => c.to_string(), + }) + .collect() + }) + .collect() +} + +/// A hex dump line coloured by column: faint offset, bytes, dim ASCII. +fn hex_job(line: &str, font: &FontId, t: &Tokens, fg: Color32) -> LayoutJob { + let mut job = LayoutJob::default(); + let fmt = |c: Color32| TextFormat { font_id: font.clone(), color: c, ..Default::default() }; + let (offset, rest) = line.split_at(line.len().min(10)); + let (bytes, ascii) = rest.find('|').map(|i| rest.split_at(i)).unwrap_or((rest, "")); + job.append(offset, 0.0, fmt(t.text_faint)); + job.append(bytes, 0.0, fmt(fg)); + job.append(ascii, 0.0, fmt(t.text_dim)); + job +} + +/// HH:MM:SS for today, otherwise "Mon DD HH:MM:SS" (the web build's formatTimestamp). +fn format_time(ms: i64) -> String { + use chrono::{Local, TimeZone}; + let Some(d) = Local.timestamp_millis_opt(ms).single() else { return String::new() }; + if d.date_naive() == Local::now().date_naive() { d.format("%H:%M:%S").to_string() } else { d.format("%b %d %H:%M:%S").to_string() } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn view() -> RawView { + let mut p = Profile::new(nexus_core::models::Protocol::Raw); + p.raw = Some(nexus_core::models::RawConfig { show_direction: true, ..Default::default() }); + RawView::new("s", &p) + } + + #[test] + fn text_rows_decode_utf8_and_prefix_direction() { + let mut v = view(); + v.on_data("héllo\r\nwörld\n".as_bytes(), "recv", 0); + v.on_data(b"ping\x01", "send", 0); + let rows: Vec<&str> = v.rows.iter().map(|r| r.left.as_str()).collect(); + assert_eq!(rows, ["[RX] héllo", " wörld", "[TX] ping·"]); + assert_eq!((v.rx_bytes, v.tx_bytes), (15, 5)); + } + + #[test] + fn hex_and_split_rows() { + let mut v = view(); + v.on_data(&[0x41; 20], "recv", 0); + v.set_mode(Mode::Hex); + assert_eq!(v.rows.len(), 3); // header + 2 dump lines + assert!(v.rows[1].left.starts_with("00000000 41 41")); + v.set_mode(Mode::Split); + assert!(v.rows[0].header && v.rows[0].left.contains("▼ RX")); + assert_eq!(v.rows[1].left, "A".repeat(20)); + assert!(v.rows[2].right.starts_with("00000010")); + } + + #[test] + fn pause_freezes_and_buffer_caps() { + let mut v = view(); + v.paused = true; + for _ in 0..MAX_ENTRIES + 10 { + v.on_data(b"x", "recv", 0); + } + assert!(v.rows.is_empty()); + assert_eq!(v.entries.len(), MAX_ENTRIES); + v.paused = false; + v.rebuild(); + assert_eq!(v.rows.len(), MAX_ENTRIES); + v.on_data(b"y", "recv", 0); + assert_eq!(v.rows.len(), MAX_ENTRIES); + assert_eq!(v.rows.back().unwrap().left, "[RX] y"); + } + + #[test] + fn quick_bytes() { + assert_eq!(esc_to_hex("\\x04"), "04"); + assert_eq!(esc_to_hex("\\n"), "0a"); + let mut v = view(); + v.insert_byte("\\r"); + assert!(v.format == Format::Escaped && v.input == "\\r"); + v.format = Format::Hex; + v.input = "41".into(); + v.insert_byte("\\x00"); + assert_eq!(v.input, "41 00"); } } diff --git a/rust/app/src/vnc_view.rs b/rust/app/src/vnc_view.rs index 24c7a1d..8776cb5 100644 --- a/rust/app/src/vnc_view.rs +++ b/rust/app/src/vnc_view.rs @@ -1,22 +1,399 @@ -//! VNC viewer: the remote framebuffer as a texture, with mouse and keyboard input. -//! STUB — interface fixed; implementation pending. +//! VNC viewer: the remote framebuffer as a texture, with mouse, keyboard and clipboard input. +//! Toolbar after the web build's `vnc.js` (status, Ctrl+Alt+Del, clipboard), plus refresh and a +//! scale toggle. use crate::state::Shared; +use crate::theme::{self, Tokens}; +use crate::widgets; +use egui::{Event, Key, Modifiers, Pos2, Rect, RichText, Sense, TextureHandle, Ui, vec2}; +use egui_phosphor::regular as icon; +use nexus_core::clients::vnc::{self, keysym_for}; use nexus_core::models::Profile; +use std::sync::Arc; + +const XK_SHIFT: u32 = 0xffe1; +const XK_CONTROL: u32 = 0xffe3; +const XK_ALT: u32 = 0xffe9; +const XK_SUPER: u32 = 0xffeb; +const XK_DELETE: u32 = 0xffff; +/// Wheel distance (points) per remote scroll notch for pixel-precise wheels. +const NOTCH: f32 = 40.0; pub struct VncView { pub session: String, + view_only: bool, + scale_to_fit: bool, + ready: bool, + client: Option>, + texture: Option, + fb_size: [usize; 2], + name: String, + /// Modifier keysyms currently held on the remote side. + held: Vec, + buttons: u8, + last_pos: (u16, u16), + wheel: f32, + had_focus: bool, +} + +/// Keysym for a non-printing key (arrows, F-keys, Enter…); printable keys arrive as `Event::Text`. +fn named_keysym(key: Key) -> Option { + let name = key.name(); + if name.chars().count() == 1 || key == Key::Space { + return None; + } + keysym_for(name, None) +} + +/// Keysym for a printable key pressed with Ctrl/Alt/Cmd (egui sends no text for those). +fn chord_keysym(key: Key, shift: bool) -> Option { + if key == Key::Space { + return Some(0x20); + } + let mut chars = key.symbol_or_name().chars(); + let c = chars.next()?; + if chars.next().is_some() { + return None; + } + keysym_for("", Some(if shift { c.to_ascii_uppercase() } else { c.to_ascii_lowercase() })) +} + +/// Modifier keysyms that should be held for `m`. +fn modifier_syms(m: Modifiers) -> Vec { + let mut v = vec![]; + if m.shift { + v.push(XK_SHIFT); + } + if m.ctrl { + v.push(XK_CONTROL); + } + if m.alt { + v.push(XK_ALT); + } + if m.mac_cmd { + v.push(XK_SUPER); + } + v +} + +/// Vertical wheel movement this frame in notches (positive = up). +fn wheel_notches(events: &[Event]) -> f32 { + events + .iter() + .map(|e| match e { + Event::MouseWheel { unit, delta, .. } => match unit { + egui::MouseWheelUnit::Point => delta.y / NOTCH, + egui::MouseWheelUnit::Line => delta.y, + egui::MouseWheelUnit::Page => delta.y * 5.0, + }, + _ => 0.0, + }) + .sum() +} + +/// Pointer button mask: bit 0 left, 1 middle, 2 right. +fn button_mask(left: bool, middle: bool, right: bool) -> u8 { + left as u8 | (middle as u8) << 1 | (right as u8) << 2 +} + +/// A screen position over the drawn image → framebuffer coordinates (clamped). +fn to_remote(p: Pos2, img: Rect, fb: [usize; 2]) -> (u16, u16) { + let sx = fb[0] as f32 / img.width().max(1.0); + let sy = fb[1] as f32 / img.height().max(1.0); + let x = ((p.x - img.left()) * sx).clamp(0.0, fb[0].saturating_sub(1) as f32); + let y = ((p.y - img.top()) * sy).clamp(0.0, fb[1].saturating_sub(1) as f32); + (x as u16, y as u16) } impl VncView { - pub fn new(session: &str, _profile: &Profile) -> VncView { - VncView { session: session.into() } + pub fn new(session: &str, profile: &Profile) -> VncView { + let cfg = profile.vnc.clone().unwrap_or_default(); + VncView { + session: session.into(), + view_only: cfg.view_only, + scale_to_fit: cfg.scale_to_fit, + ready: false, + client: None, + texture: None, + fb_size: [0, 0], + name: String::new(), + held: vec![], + buttons: 0, + last_pos: (u16::MAX, u16::MAX), + wheel: 0.0, + had_focus: false, + } } /// Called on `Event::VncReady` (the client is connected; fetch it via `s.svc.sessions.vnc(id)`). - pub fn on_ready(&mut self) {} + pub fn on_ready(&mut self) { + self.ready = true; + self.client = None; + } - pub fn ui(&mut self, ui: &mut egui::Ui, _s: &mut Shared) { - ui.label("VNC"); + fn key(&self, sym: u32, down: bool) { + if let Some(c) = &self.client { + let _ = c.key(sym, down); + } + } + + fn tap(&self, syms: &[u32]) { + syms.iter().for_each(|&k| self.key(k, true)); + syms.iter().rev().for_each(|&k| self.key(k, false)); + } + + fn sync_modifiers(&mut self, want: Vec) { + for k in self.held.clone() { + if !want.contains(&k) { + self.key(k, false); + } + } + for &k in &want { + if !self.held.contains(&k) { + self.key(k, true); + } + } + self.held = want; + } + + fn release_all(&mut self) { + self.sync_modifiers(vec![]); + if self.buttons != 0 + && let Some(c) = &self.client + { + let _ = c.pointer(self.last_pos.0, self.last_pos.1, 0); + } + self.buttons = 0; + } + + fn paste(&self, s: &mut Shared, text: &str) { + if let Some(c) = &self.client + && let Err(e) = c.send_clipboard(text) + { + s.error(format!("Clipboard: {e}")); + } + } + + pub fn ui(&mut self, ui: &mut Ui, s: &mut Shared) { + if self.ready && self.client.is_none() { + self.client = s.svc.sessions.vnc(&self.session).ok(); + if let Some(c) = &self.client { + self.name = c.desktop_name(); + } + } + self.upload(ui); + self.toolbar(ui, s); + + let t = Tokens::get(ui.ctx()); + let area = ui.available_rect_before_wrap(); + ui.painter().rect_filled(area, 0.0, t.canvas); + let Some(tex) = self.texture.clone().filter(|_| self.ready) else { + ui.allocate_rect(area, Sense::hover()); + let mut c = ui.new_child(egui::UiBuilder::new().max_rect(area)); + c.vertical_centered(|ui| { + ui.add_space(area.height() / 2.0 - 30.0); + ui.add(egui::Spinner::new().size(22.0)); + ui.label(RichText::new(if self.ready { "Waiting for the first frame…" } else { "Connecting…" }).color(t.text_dim)); + }); + return; + }; + let fb = self.fb_size; + let size = vec2(fb[0] as f32, fb[1] as f32); + if self.scale_to_fit { + ui.allocate_rect(area, Sense::hover()); + let scale = (area.width() / size.x).min(area.height() / size.y); + let img = Rect::from_center_size(area.center(), size * scale); + self.draw(ui, s, &tex, img); + } else { + let mut c = ui.new_child(egui::UiBuilder::new().max_rect(area)); + egui::ScrollArea::both().id_salt(("vnc", &self.session)).auto_shrink([false, false]).show(&mut c, |ui| { + let (img, _) = ui.allocate_exact_size(size, Sense::hover()); + self.draw(ui, s, &tex, img); + }); + ui.allocate_rect(area, Sense::hover()); + } + } + + /// Uploads the framebuffer when the client marked it dirty. + fn upload(&mut self, ui: &Ui) { + let Some(c) = &self.client else { return }; + let fb = c.framebuffer(); + let mut fb = fb.lock().unwrap_or_else(|e| e.into_inner()); + if !fb.dirty || fb.width == 0 || fb.height == 0 { + return; + } + let size = [fb.width as usize, fb.height as usize]; + // Alpha is always 255, so the buffer is already premultiplied. + let img = egui::ColorImage::from_rgba_premultiplied(size, &fb.rgba); + fb.dirty = false; + drop(fb); + self.fb_size = size; + match &mut self.texture { + Some(t) => t.set(img, egui::TextureOptions::LINEAR), + None => self.texture = Some(ui.ctx().load_texture(format!("vnc-{}", self.session), img, egui::TextureOptions::LINEAR)), + } + } + + fn toolbar(&mut self, ui: &mut Ui, s: &mut Shared) { + let t = Tokens::get(ui.ctx()); + egui::Frame::new().fill(t.chrome).inner_margin(egui::Margin::symmetric(8, 5)).show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.horizontal(|ui| { + let (dot, status) = match (self.ready, self.name.is_empty()) { + (false, _) => (t.warning, "Connecting…".to_string()), + (true, true) => (t.success, "Connected".to_string()), + (true, false) => (t.success, format!("Connected — {}", self.name)), + }; + ui.label(RichText::new("●").color(dot).size(10.0)); + ui.label(RichText::new(status).font(theme::semibold(12.5))); + if self.fb_size[0] > 0 { + ui.label(RichText::new(format!("{}×{}", self.fb_size[0], self.fb_size[1])).color(t.text_faint).size(11.5)); + } + if self.view_only { + widgets::chip(ui, "VIEW ONLY", t.warning).on_hover_text("Input is not sent to the remote desktop"); + } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + ui.add_enabled_ui(self.ready, |ui| { + let (g, tip) = if self.scale_to_fit { (icon::CORNERS_IN, "Scaled to fit (click for 1:1)") } else { (icon::CORNERS_OUT, "1:1 (click to scale to fit)") }; + if widgets::icon_button(ui, g, tip).clicked() { + self.scale_to_fit = !self.scale_to_fit; + } + if widgets::icon_button(ui, icon::ARROW_CLOCKWISE, "Refresh the screen").clicked() + && let Some(c) = &self.client + { + let _ = c.request_full_update(); + } + if !self.view_only { + if widgets::button(ui, icon::CLIPBOARD_TEXT, "Paste Clipboard").on_hover_text("Send the local clipboard to the remote desktop").clicked() { + match arboard::Clipboard::new().and_then(|mut c| c.get_text()) { + Ok(text) if !text.is_empty() => { + self.paste(s, &text); + s.info("Clipboard sent"); + } + _ => s.warn("The clipboard has no text"), + } + } + if widgets::button(ui, icon::KEYBOARD, "Ctrl+Alt+Del").clicked() { + self.tap(&[XK_CONTROL, XK_ALT, XK_DELETE]); + } + } + }); + }); + }); + }); + } + + fn draw(&mut self, ui: &mut Ui, s: &mut Shared, tex: &TextureHandle, img: Rect) { + ui.painter().image(tex.id(), img, Rect::from_min_max(Pos2::ZERO, Pos2::new(1.0, 1.0)), egui::Color32::WHITE); + let id = egui::Id::new(("vnc-input", &self.session)); + let resp = ui.interact(img, id, Sense::click_and_drag()); + if resp.clicked() || resp.drag_started() || resp.secondary_clicked() || resp.middle_clicked() { + resp.request_focus(); + } + let focused = resp.has_focus(); + if focused { + ui.painter().rect_stroke(img, 0.0, egui::Stroke::new(1.0, Tokens::get(ui.ctx()).accent.gamma_multiply(0.6)), egui::StrokeKind::Outside); + ui.memory_mut(|m| { + m.set_focus_lock_filter(id, egui::EventFilter { tab: true, horizontal_arrows: true, vertical_arrows: true, escape: true }) + }); + } else if self.had_focus { + self.release_all(); + } + self.had_focus = focused; + if self.view_only || self.client.is_none() { + return; + } + self.pointer(ui, &resp, img); + if focused { + self.keyboard(ui, s); + } + } + + fn pointer(&mut self, ui: &Ui, resp: &egui::Response, img: Rect) { + let Some(c) = self.client.clone() else { return }; + let (pos, left, middle, right, scroll) = ui.input(|i| { + let p = &i.pointer; + (p.latest_pos(), p.primary_down(), p.middle_down(), p.secondary_down(), wheel_notches(&i.events)) + }); + let Some(pos) = pos else { return }; + // Only while hovering the image, or while a drag that began on it continues. + if !resp.hovered() && !resp.dragged() && self.buttons == 0 { + return; + } + let buttons = if resp.hovered() || resp.dragged() || self.buttons != 0 { button_mask(left, middle, right) } else { 0 }; + let at = to_remote(pos, img, self.fb_size); + if at != self.last_pos || buttons != self.buttons { + let _ = c.pointer(at.0, at.1, buttons); + self.last_pos = at; + self.buttons = buttons; + } + if resp.hovered() && scroll != 0.0 { + self.wheel += scroll; + while self.wheel.abs() >= 1.0 { + let bit = if self.wheel > 0.0 { 1 << 3 } else { 1 << 4 }; + let _ = c.pointer(at.0, at.1, buttons | bit); + let _ = c.pointer(at.0, at.1, buttons); + self.wheel -= self.wheel.signum(); + } + } + } + + fn keyboard(&mut self, ui: &Ui, s: &mut Shared) { + let (events, mods) = ui.input(|i| (i.events.clone(), i.modifiers)); + self.sync_modifiers(modifier_syms(mods)); + for e in events { + match e { + Event::Key { key, pressed, modifiers, .. } => { + let sym = named_keysym(key).or_else(|| (modifiers.ctrl || modifiers.alt || modifiers.mac_cmd).then(|| chord_keysym(key, modifiers.shift)).flatten()); + if let Some(sym) = sym { + self.key(sym, pressed); + } + } + Event::Text(text) => { + for ch in text.chars() { + if let Some(sym) = keysym_for("", Some(ch)) { + self.tap(&[sym]); + } + } + } + // egui turns Ctrl+C/X/V into these; forward the keystroke (and the text for paste). + Event::Copy => self.tap(&[u32::from(b'c')]), + Event::Cut => self.tap(&[u32::from(b'x')]), + Event::Paste(text) => { + self.paste(s, &text); + self.tap(&[u32::from(b'v')]); + } + _ => {} + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn keys_map_to_keysyms() { + assert_eq!(named_keysym(Key::Enter), Some(0xff0d)); + assert_eq!(named_keysym(Key::ArrowLeft), Some(0xff51)); + assert_eq!(named_keysym(Key::F5), Some(0xffc2)); + assert_eq!(named_keysym(Key::A), None); + assert_eq!(named_keysym(Key::Space), None); + assert_eq!(named_keysym(Key::Comma), None); + assert_eq!(chord_keysym(Key::A, false), Some(u32::from(b'a'))); + assert_eq!(chord_keysym(Key::A, true), Some(u32::from(b'A'))); + assert_eq!(chord_keysym(Key::Num1, false), Some(u32::from(b'1'))); + assert_eq!(chord_keysym(Key::Space, false), Some(0x20)); + assert_eq!(modifier_syms(Modifiers::CTRL | Modifiers::SHIFT), vec![XK_SHIFT, XK_CONTROL]); + } + + #[test] + fn pointer_mapping() { + assert_eq!(button_mask(true, false, true), 0b101); + let img = Rect::from_min_size(Pos2::new(100.0, 50.0), vec2(400.0, 300.0)); + assert_eq!(to_remote(Pos2::new(100.0, 50.0), img, [800, 600]), (0, 0)); + assert_eq!(to_remote(Pos2::new(300.0, 200.0), img, [800, 600]), (400, 300)); + assert_eq!(to_remote(Pos2::new(900.0, 900.0), img, [800, 600]), (799, 599)); } } From 2bb12c3a5847366465574bdc4b4956049ef043bb Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:40:48 +0100 Subject: [PATCH 31/45] rust: merge UI feature modules; fix Ctrl+key on Linux/Windows (egui command_only shadowing); clippy clean; rustfmt config --- .github/workflows/ci.yml | 3 +- rust/app/src/app.rs | 115 ++++++++++------ rust/app/src/chrome.rs | 82 ++++++++--- rust/app/src/forwards.rs | 26 +++- rust/app/src/github_view.rs | 104 ++++++++------ rust/app/src/irc_view.rs | 9 +- rust/app/src/profile_editor.rs | 109 +++++++++++---- rust/app/src/quick_connect.rs | 14 +- rust/app/src/raw_view.rs | 8 +- rust/app/src/recordings.rs | 41 ++++-- rust/app/src/sftp.rs | 92 ++++++++++--- rust/app/src/sidebar.rs | 42 +++++- rust/app/src/snippets.rs | 29 +++- rust/app/src/splits.rs | 49 +++++-- rust/app/src/ssh_keys.rs | 10 +- rust/app/src/state.rs | 30 +---- rust/app/src/tabs.rs | 18 ++- rust/app/src/terminal.rs | 44 +++--- rust/app/src/terminal_themes.rs | 209 +++++++++++++++++++++++++++-- rust/app/src/theme.rs | 7 +- rust/app/src/transfer.rs | 59 ++++++-- rust/app/src/triggers.rs | 11 +- rust/app/src/views.rs | 9 -- rust/app/src/vnc_view.rs | 18 ++- rust/core/src/clients/github.rs | 83 +++--------- rust/core/src/clients/irc.rs | 72 +++------- rust/core/src/clients/local.rs | 4 +- rust/core/src/clients/mod.rs | 15 +-- rust/core/src/clients/raw.rs | 14 +- rust/core/src/clients/serial.rs | 19 +-- rust/core/src/clients/sftp.rs | 23 ++-- rust/core/src/clients/ssh.rs | 64 +++++---- rust/core/src/clients/telnet.rs | 3 +- rust/core/src/clients/vnc.rs | 60 ++++----- rust/core/src/exportimport.rs | 2 +- rust/core/src/ircformat.rs | 16 +-- rust/core/src/keyfiles.rs | 25 +--- rust/core/src/models.rs | 3 +- rust/core/src/recording.rs | 5 +- rust/core/src/secrets.rs | 15 +-- rust/core/src/service.rs | 13 +- rust/core/src/sessions.rs | 88 +++++++++--- rust/core/src/sshconfig.rs | 9 +- rust/core/src/storage.rs | 33 ++--- rust/core/src/terminalenv.rs | 6 +- rust/core/src/updater.rs | 26 +--- rust/core/src/validation.rs | 13 +- rust/core/tests/ssh_integration.rs | 48 +++++-- rust/rustfmt.toml | 2 + 49 files changed, 1140 insertions(+), 659 deletions(-) create mode 100644 rust/rustfmt.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc00f43..20a2c5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,4 +25,5 @@ jobs: sudo apt-get update sudo apt-get install -y libxkbcommon-dev libgl1-mesa-dev libx11-dev libxcursor-dev libxrandr-dev libxi-dev libwayland-dev libgtk-3-dev openssh-server - run: cargo test --locked --workspace - - run: cargo clippy --locked --workspace -- -D warnings + - run: cargo fmt --all --check + - run: cargo clippy --locked --workspace --all-targets -- -D warnings diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs index e78cf0c..c650ac5 100644 --- a/rust/app/src/app.rs +++ b/rust/app/src/app.rs @@ -41,17 +41,43 @@ pub enum Prompt { /// Modal dialogs owned by the main window. pub enum Dialog { /// A profile already has a live session. - Reuse { profile: String, existing: String }, + Reuse { + profile: String, + existing: String, + }, /// Unencrypted VNC without an SSH tunnel. - DirectVnc { profile: String }, - ConfirmClose { session: String }, - ConfirmDisconnectMany { sessions: Vec }, - Rename { session: String, name: String }, - Broadcast { picks: Vec<(String, String, bool)> }, + DirectVnc { + profile: String, + }, + ConfirmClose { + session: String, + }, + ConfirmDisconnectMany { + sessions: Vec, + }, + Rename { + session: String, + name: String, + }, + Broadcast { + picks: Vec<(String, String, bool)>, + }, /// Guided help for an auth or host-key failure. - Guided { session: String, title: String, body: String, known_hosts: bool }, - Delete { profiles: Vec }, - Input { title: String, label: String, value: String, then: InputThen }, + Guided { + session: String, + title: String, + body: String, + known_hosts: bool, + }, + Delete { + profiles: Vec, + }, + Input { + title: String, + label: String, + value: String, + then: InputThen, + }, } pub enum InputThen { @@ -205,7 +231,9 @@ impl App { /// Registers a just-opened session as a pane (and a tab unless it's a split child). pub fn add_session(&mut self, id: &str, tab: bool) { - let Some(info) = self.s.svc.sessions.info(id) else { return }; + let Some(info) = self.s.svc.sessions.info(id) else { + return; + }; let profile = self.s.svc.sessions.profile(id); let view = self.view_for(&info, profile.as_ref()); self.s.panes.insert(id.into(), Pane { info, view, recording: false, local_exited: false, connected_since: None, highlight_until: 0.0 }); @@ -224,7 +252,8 @@ impl App { }; if !skip_checks { let live = self.s.panes.values().find(|pn| { - pn.info.profile_id == id && matches!(self.s.session_state(&pn.info.id), Some(SessionState::Connected | SessionState::Connecting | SessionState::Reconnecting)) + pn.info.profile_id == id + && matches!(self.s.session_state(&pn.info.id), Some(SessionState::Connected | SessionState::Connecting | SessionState::Reconnecting)) }); if let Some(pn) = live { self.dialog = Some(Dialog::Reuse { profile: id.into(), existing: pn.info.id.clone() }); @@ -365,10 +394,10 @@ impl App { fn mark_activity(&mut self, session: &str) { let active = self.s.active.clone(); - if let Some(t) = self.s.tabs.iter_mut().find(|t| t.id == session || t.split.as_ref().is_some_and(|sp| sp.sessions().iter().any(|x| x == session))) { - if active.as_deref() != Some(&t.id) { - t.activity = true; - } + if let Some(t) = self.s.tabs.iter_mut().find(|t| t.id == session || t.split.as_ref().is_some_and(|sp| sp.sessions().iter().any(|x| x == session))) + && active.as_deref() != Some(&t.id) + { + t.activity = true; } } @@ -467,7 +496,9 @@ impl App { p.info.error = error.into(); } } - let Some(p) = self.s.pane_mut(session) else { return }; + let Some(p) = self.s.pane_mut(session) else { + return; + }; let local = p.info.protocol == Protocol::Local; match state { SessionState::Connected => { @@ -477,7 +508,9 @@ impl App { let _ = self.s.svc.sessions.write(session, format!("{cmd}\n").as_bytes()); self.run_local = None; } - let Some(p) = self.s.pane_mut(session) else { return }; + let Some(p) = self.s.pane_mut(session) else { + return; + }; p.local_exited = false; p.connected_since = Some(chrono::Local::now()); // The PTY was created before the view knew its size: send it now. @@ -520,7 +553,6 @@ impl App { self.add_session(&sid, true); self.s.info(format!("Connecting to {name}…")); } - Request::ConnectLocal => self.connect_local(), Request::ReloadProfiles => self.s.reload_profiles(), Request::ReloadPreferences => { self.s.prefs = self.s.svc.preferences(); @@ -528,26 +560,14 @@ impl App { self.theme = theme_for(&self.s.prefs.theme); theme::apply(&self.s.ctx, self.theme); } - Request::QuickConnect => self.quick.open(), - Request::Settings(t) => self.settings.open(t), - Request::SshKeys => self.keys.open(), Request::ExportEncrypted(ids) => self.transfer.export_encrypted(ids), Request::ImportEncrypted => self.transfer.import_encrypted(), - Request::ExportJson => self.transfer.export_json(), - Request::ImportJson => self.transfer.import_json(), - Request::Recordings => self.recordings.open(), - Request::Snippets => self.snippets.open(), - Request::Triggers => self.triggers.open(), Request::Sftp(id) => self.sftp.toggle(&id), - Request::PortForwards(id) => self.forwards.open(&id), Request::SendToActive(text) => match self.s.focused_session() { Some(id) if self.s.session_state(&id) == Some(SessionState::Connected) => self.send_input(&id, text.as_bytes()), _ => self.s.error("No active connected session"), }, Request::Write(id, data) => self.send_input(&id, &data), - Request::Disconnect(id) => self.close_session(&id), - Request::CommandPalette => self.palette = Some(chrome::Palette::default()), - Request::About => self.settings.open(SettingsTab::About), } } } @@ -754,7 +774,9 @@ impl App { // --- screenshot automation --------------------------------------------------------- fn screenshot(&mut self, ctx: &egui::Context) { - let Some((path, frames)) = &mut self.screenshot else { return }; + let Some((path, frames)) = &mut self.screenshot else { + return; + }; *frames += 1; if *frames < 40 { std::thread::sleep(std::time::Duration::from_millis(15)); @@ -824,26 +846,33 @@ impl eframe::App for App { if std::mem::take(&mut self.resize_sidebar) { panel = panel.exact_size(width); } - let side = panel - .min_size(180.0) - .max_size(500.0) - .resizable(true) - .frame(egui::Frame::NONE.fill(t.dock)) - .show(ui, |ui| sidebar::ui(self, ui)); + let side = panel.min_size(180.0).max_size(500.0).resizable(true).frame(egui::Frame::NONE.fill(t.dock)).show(ui, |ui| sidebar::ui(self, ui)); let w = side.response.rect.width().round() as i64; if (w - self.s.prefs.sidebar_width).abs() > 2 && !ctx.input(|i| i.pointer.any_down()) { self.s.prefs.sidebar_width = w; let _ = self.s.svc.save_preferences(&self.s.prefs); } if self.editor.is_open() { - egui::Panel::right("editor").default_size(460.0).min_size(380.0).max_size(800.0).resizable(true).frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)).show(ui, |ui| { - self.editor.ui(ui, &mut self.s); - }); + egui::Panel::right("editor") + .default_size(460.0) + .min_size(380.0) + .max_size(800.0) + .resizable(true) + .frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)) + .show(ui, |ui| { + self.editor.ui(ui, &mut self.s); + }); } else if let Some(id) = self.sftp.open_for().map(String::from) { if self.s.pane(&id).is_some() { - egui::Panel::right("sftp").default_size(380.0).min_size(280.0).max_size(800.0).resizable(true).frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)).show(ui, |ui| { - self.sftp.ui(ui, &mut self.s); - }); + egui::Panel::right("sftp") + .default_size(380.0) + .min_size(280.0) + .max_size(800.0) + .resizable(true) + .frame(egui::Frame::NONE.fill(t.dock).inner_margin(6)) + .show(ui, |ui| { + self.sftp.ui(ui, &mut self.s); + }); } else { self.sftp.close(&id); } diff --git a/rust/app/src/chrome.rs b/rust/app/src/chrome.rs index 0317ea7..ee5deef 100644 --- a/rust/app/src/chrome.rs +++ b/rust/app/src/chrome.rs @@ -194,7 +194,13 @@ pub fn title_bar(app: &mut App, ui: &mut Ui) { let resp = ui.interact(r, ui.id().with("palette-launch"), Sense::click()); ui.painter().rect(r, 6.0, if resp.hovered() { t.hover } else { t.field }, Stroke::new(1.0, t.field_border), egui::StrokeKind::Inside); ui.painter().text(r.left_center() + vec2(9.0, 0.0), Align2::LEFT_CENTER, icon::MAGNIFYING_GLASS, egui::FontId::proportional(13.0), t.text_faint); - ui.painter().with_clip_rect(r.shrink(2.0)).text(r.left_center() + vec2(28.0, 0.0), Align2::LEFT_CENTER, "Search connections and commands", egui::FontId::proportional(12.0), t.text_faint); + ui.painter().with_clip_rect(r.shrink(2.0)).text( + r.left_center() + vec2(28.0, 0.0), + Align2::LEFT_CENTER, + "Search connections and commands", + egui::FontId::proportional(12.0), + t.text_faint, + ); if w > 250.0 { ui.painter().text(r.right_center() - vec2(8.0, 0.0), Align2::RIGHT_CENTER, keys("Mod+K").replace('+', " "), theme::semibold(10.5), t.text_faint); } @@ -257,7 +263,9 @@ pub fn resize_edges(ctx: &egui::Context) { (Rect::from_min_max(r.min, pos2(r.left() + e, r.bottom())), D::West, CursorIcon::ResizeHorizontal), (Rect::from_min_max(pos2(r.right() - e, r.top()), r.max), D::East, CursorIcon::ResizeHorizontal), ]; - let Some(p) = ctx.input(|i| i.pointer.hover_pos()) else { return }; + let Some(p) = ctx.input(|i| i.pointer.hover_pos()) else { + return; + }; for (zone, dir, cursor) in zones { if zone.contains(p) { ctx.set_cursor_icon(cursor); @@ -317,7 +325,13 @@ pub fn status_bar(app: &mut App, ui: &mut Ui) { && active.as_deref() == Some(sid) && let Ok(ms) = r { - let c = if *ms < 100 { t.success } else if *ms < 250 { t.warning } else { t.danger }; + let c = if *ms < 100 { + t.success + } else if *ms < 250 { + t.warning + } else { + t.danger + }; ui.label(RichText::new(format!("{ms} ms")).size(11.5).color(c)); } if app.s.broadcast { @@ -328,7 +342,15 @@ pub fn status_bar(app: &mut App, ui: &mut Ui) { let vault = app.s.svc.secrets.vault.is_unlocked(); let keychain = app.s.svc.secrets.keychain_ok; let label = if vault { format!("{} Vault unlocked", icon::LOCK_OPEN) } else { format!("{} Vault locked", icon::LOCK) }; - if ui.add(egui::Label::new(dim(label)).sense(Sense::click())).on_hover_text(if keychain { "Saved credentials use the OS keychain; the vault is the fallback." } else { "No OS keychain available: saved credentials use the encrypted vault." }).clicked() { + if ui + .add(egui::Label::new(dim(label)).sense(Sense::click())) + .on_hover_text(if keychain { + "Saved credentials use the OS keychain; the vault is the fallback." + } else { + "No OS keychain available: saved credentials use the encrypted vault." + }) + .clicked() + { app.settings.open(SettingsTab::Security); } let total = app.s.panes.len(); @@ -337,7 +359,11 @@ pub fn status_bar(app: &mut App, ui: &mut Ui) { if let Some(u) = &app.update && u.available { - let txt = format!("{} Update available{}", icon::ARROW_CIRCLE_UP, if u.latest_version.is_empty() { String::new() } else { format!(" ({})", u.latest_version) }); + let txt = format!( + "{} Update available{}", + icon::ARROW_CIRCLE_UP, + if u.latest_version.is_empty() { String::new() } else { format!(" ({})", u.latest_version) } + ); if ui.add(egui::Label::new(RichText::new(txt).size(11.5).color(t.accent)).sense(Sense::click())).clicked() { app.settings.open(SettingsTab::About); } @@ -397,6 +423,8 @@ enum Cmd { Settings, Import, Export, + ExportJson, + ImportJson, Lock, Unlock, Keys, @@ -435,7 +463,12 @@ fn commands(app: &App) -> Vec<(Cmd, String, &'static str, &'static str, String)> let name = if p.display_name.is_empty() { &p.host } else { &p.display_name }; v.push((Cmd::Connect(p.id.clone()), format!("Connect to {name}"), "Connections", icon::PLUGS_CONNECTED, String::new())); } - for (pr, label) in [(Protocol::Ssh, "New SSH Connection"), (Protocol::Telnet, "New Telnet Connection"), (Protocol::Raw, "New RAW Connection"), (Protocol::Serial, "New Serial Connection")] { + for (pr, label) in [ + (Protocol::Ssh, "New SSH Connection"), + (Protocol::Telnet, "New Telnet Connection"), + (Protocol::Raw, "New RAW Connection"), + (Protocol::Serial, "New Serial Connection"), + ] { v.push((Cmd::NewProfile(pr), label.into(), "Actions", icon::PLUS_CIRCLE, if pr == Protocol::Ssh { keys("Mod+N") } else { String::new() })); } v.push((Cmd::Quick, "Quick Connect…".into(), "Actions", icon::LIGHTNING, keys("Mod+Shift+Q"))); @@ -456,6 +489,8 @@ fn commands(app: &App) -> Vec<(Cmd, String, &'static str, &'static str, String)> v.push((Cmd::Settings, "Settings".into(), "Application", icon::GEAR, keys("Mod+,"))); v.push((Cmd::Import, "Import Profiles".into(), "Application", icon::DOWNLOAD_SIMPLE, String::new())); v.push((Cmd::Export, "Export Profiles".into(), "Application", icon::UPLOAD_SIMPLE, String::new())); + v.push((Cmd::ImportJson, "Import Profiles (JSON)".into(), "Application", icon::FILE_ARROW_DOWN, String::new())); + v.push((Cmd::ExportJson, "Export Profiles (JSON)".into(), "Application", icon::FILE_ARROW_UP, String::new())); for k in ThemeKind::ALL { v.push((Cmd::Theme(k), format!("Theme: {}", k.label()), "Application", icon::PALETTE, String::new())); } @@ -501,6 +536,8 @@ fn run(app: &mut App, ctx: &egui::Context, c: Cmd) { Cmd::Settings => app.settings.open(SettingsTab::General), Cmd::Import => app.s.request(Request::ImportEncrypted), Cmd::Export => app.s.request(Request::ExportEncrypted(vec![])), + Cmd::ExportJson => app.transfer.export_json(), + Cmd::ImportJson => app.transfer.import_json(), Cmd::Lock => { app.s.svc.secrets.vault.lock(); app.s.info("Secret store locked"); @@ -536,14 +573,13 @@ pub fn modal_backdrop(ctx: &egui::Context) { } pub fn palette(app: &mut App, ctx: &egui::Context) { - let Some(mut pal) = app.palette.take() else { return }; + let Some(mut pal) = app.palette.take() else { + return; + }; let t = Tokens::get(ctx); - let mut matches: Vec<_> = commands(app) - .into_iter() - .filter_map(|c| fuzzy_score(&format!("{} {}", c.1, c.2), pal.query.trim()).map(|s| (s, c))) - .collect(); + let mut matches: Vec<_> = commands(app).into_iter().filter_map(|c| fuzzy_score(&format!("{} {}", c.1, c.2), pal.query.trim()).map(|s| (s, c))).collect(); if !pal.query.trim().is_empty() { - matches.sort_by(|a, b| b.0.cmp(&a.0)); + matches.sort_by_key(|m| std::cmp::Reverse(m.0)); } let matches: Vec<_> = matches.into_iter().map(|(_, c)| c).collect(); pal.sel = pal.sel.min(matches.len().saturating_sub(1)); @@ -564,7 +600,13 @@ pub fn palette(app: &mut App, ctx: &egui::Context) { ui.set_width(580.0); ui.horizontal(|ui| { ui.label(RichText::new(icon::MAGNIFYING_GLASS).size(16.0).color(t.text_faint)); - let r = ui.add(egui::TextEdit::singleline(&mut pal.query).hint_text("Connect, switch tab, or run a command…").frame(egui::Frame::NONE).desired_width(f32::INFINITY).font(egui::FontId::proportional(15.0))); + let r = ui.add( + egui::TextEdit::singleline(&mut pal.query) + .hint_text("Connect, switch tab, or run a command…") + .frame(egui::Frame::NONE) + .desired_width(f32::INFINITY) + .font(egui::FontId::proportional(15.0)), + ); r.request_focus(); if r.changed() { pal.sel = 0; @@ -622,7 +664,9 @@ pub fn modal(ctx: &egui::Context, id: &str, width: f32, body: impl FnOnce(&mut U } pub fn prompts(app: &mut App, ctx: &egui::Context) { - let Some(p) = app.prompts.front_mut() else { return }; + let Some(p) = app.prompts.front_mut() else { + return; + }; let t = Tokens::get(ctx); let esc = ctx.input(|i| i.key_pressed(Key::Escape)); let enter = ctx.input(|i| i.key_pressed(Key::Enter)); @@ -680,7 +724,9 @@ pub fn prompts(app: &mut App, ctx: &egui::Context) { // --- dialogs -------------------------------------------------------------------------------- pub fn dialogs(app: &mut App, ctx: &egui::Context) { - let Some(mut d) = app.dialog.take() else { return }; + let Some(mut d) = app.dialog.take() else { + return; + }; let t = Tokens::get(ctx); let esc = ctx.input(|i| i.key_pressed(Key::Escape)); let mut keep = !esc; @@ -853,7 +899,11 @@ pub fn dialogs(app: &mut App, ctx: &egui::Context) { let names: Vec = profiles.iter().filter_map(|id| app.s.profile(id)).map(|p| p.display_name.clone()).collect(); modal(ctx, "delete", 380.0, |ui| { ui.label(RichText::new("Delete connection?").font(theme::semibold(15.0))); - ui.label(if names.len() == 1 { format!("\"{}\" will be deleted, with its saved credentials.", names[0]) } else { format!("{} connections will be deleted.", names.len()) }); + ui.label(if names.len() == 1 { + format!("\"{}\" will be deleted, with its saved credentials.", names[0]) + } else { + format!("{} connections will be deleted.", names.len()) + }); ui.add_space(8.0); ui.horizontal(|ui| { if ui.add(egui::Button::new(RichText::new(format!("{} Delete", icon::TRASH)).color(Color32::WHITE)).fill(t.danger)).clicked() { diff --git a/rust/app/src/forwards.rs b/rust/app/src/forwards.rs index b16fa76..0554554 100644 --- a/rust/app/src/forwards.rs +++ b/rust/app/src/forwards.rs @@ -20,7 +20,14 @@ struct Form { impl Default for Form { fn default() -> Form { - Form { remote: false, bind_host: "127.0.0.1".into(), bind_port: String::new(), dest_host: "localhost".into(), dest_port: String::new(), label: String::new() } + Form { + remote: false, + bind_host: "127.0.0.1".into(), + bind_port: String::new(), + dest_host: "localhost".into(), + dest_port: String::new(), + label: String::new(), + } } } @@ -52,7 +59,9 @@ impl Forwards { s.info("Forward stopped"); } } - let Some(sid) = self.session.clone() else { return }; + let Some(sid) = self.session.clone() else { + return; + }; // A lock and a clone in the client; no network. let list = match s.svc.sessions.forwards(&sid) { Ok(l) => l, @@ -84,7 +93,9 @@ impl Forwards { ui.horizontal(|ui| { let local = fwd.direction == ForwardDirection::Local; widgets::chip(ui, if local { "L" } else { "R" }, if local { t.accent } else { t.warning }); - let or = |s: &str, d: &str| if s.is_empty() { d.to_string() } else { s.to_string() }; + let or = |s: &str, d: &str| { + if s.is_empty() { d.to_string() } else { s.to_string() } + }; let text = format!("{}:{} → {}:{}", or(&fwd.bind_host, "127.0.0.1"), fwd.bind_port, or(&fwd.dest_host, "localhost"), fwd.dest_port); ui.label(RichText::new(text).font(theme::mono(12.0))); if !fwd.label.is_empty() { @@ -126,7 +137,9 @@ impl Forwards { let f = &self.form; match (port(&f.bind_port), port(&f.dest_port)) { (Some(bind_port), Some(dest_port)) => { - let or = |s: &str, d: &str| if s.trim().is_empty() { d.to_string() } else { s.trim().to_string() }; + let or = |s: &str, d: &str| { + if s.trim().is_empty() { d.to_string() } else { s.trim().to_string() } + }; let rule = PortForward { id: new_id(), direction: if f.remote { ForwardDirection::Remote } else { ForwardDirection::Local }, @@ -138,7 +151,10 @@ impl Forwards { label: f.label.trim().into(), }; let (svc, done) = (s.svc.clone(), self.done.clone()); - s.spawn(move || svc.sessions.start_forward(&sid, rule).map_err(|e| format!("Failed to start forward: {e}").into()), move |_| done.lock().unwrap().push(true)); + s.spawn( + move || svc.sessions.start_forward(&sid, rule).map_err(|e| format!("Failed to start forward: {e}").into()), + move |_| done.lock().unwrap().push(true), + ); } _ => s.error("Bind port and destination port are required"), } diff --git a/rust/app/src/github_view.rs b/rust/app/src/github_view.rs index 583598d..493e384 100644 --- a/rust/app/src/github_view.rs +++ b/rust/app/src/github_view.rs @@ -61,12 +61,16 @@ fn grouped<'a>(repos: &'a [GitHubRepo], login: &str, filter: &str) -> Vec<(Strin /// "just now", "3 minutes ago", "1 day ago"… (the web build's relativeTime). fn relative_time(iso: &str, now: DateTime) -> String { - let Some(d) = parse_time(iso) else { return String::new() }; + let Some(d) = parse_time(iso) else { + return String::new(); + }; let secs = (now - d).num_seconds(); if secs < 60 { return "just now".into(); } - let plural = |n: i64, unit: &str| if n == 1 { format!("1 {unit} ago") } else { format!("{n} {unit}s ago") }; + let plural = |n: i64, unit: &str| { + if n == 1 { format!("1 {unit} ago") } else { format!("{n} {unit}s ago") } + }; let mins = secs / 60; let hrs = mins / 60; let days = hrs / 24; @@ -120,6 +124,7 @@ fn lang_colour(lang: &str) -> Color32 { Color32::from_rgb((hex >> 16) as u8, (hex >> 8) as u8, hex as u8) } +#[allow(clippy::large_enum_variant)] // short-lived, one per click enum Action { Zip(GitHubRepo), Visit(String), @@ -178,7 +183,9 @@ impl GithubView { fn download(&mut self, s: &Shared, repo: &GitHubRepo) { let reference = repo.default_branch.clone(); let name = if reference.is_empty() { format!("{}.zip", repo.name) } else { format!("{}-{reference}.zip", repo.name) }; - let Some(path) = rfd::FileDialog::new().set_file_name(name).add_filter("ZIP archive", &["zip"]).save_file() else { return }; + let Some(path) = rfd::FileDialog::new().set_file_name(name).add_filter("ZIP archive", &["zip"]).save_file() else { + return; + }; self.on_status(format!("Downloading {}...", repo.full_name)); let (svc, sid, inbox, ctx, full) = (s.svc.clone(), self.session.clone(), self.inbox.clone(), s.ctx.clone(), repo.full_name.clone()); std::thread::spawn(move || { @@ -221,7 +228,11 @@ impl GithubView { if widgets::icon_button(ui, icon::ARROW_CLOCKWISE, "Refresh now").clicked() { self.refresh(s); } - ui.add(egui::TextEdit::singleline(&mut self.filter).hint_text(format!("{} Filter repositories…", icon::MAGNIFYING_GLASS)).desired_width(240.0)); + ui.add( + egui::TextEdit::singleline(&mut self.filter) + .hint_text(format!("{} Filter repositories…", icon::MAGNIFYING_GLASS)) + .desired_width(240.0), + ); }); }); let c = if self.status_error { t.danger } else { t.text_dim }; @@ -270,50 +281,55 @@ impl GithubView { fn card(ui: &mut Ui, r: &GitHubRepo, now: DateTime) -> Option { let t = Tokens::get(ui.ctx()); let mut action = None; - let resp = egui::Frame::new().fill(t.card).stroke(egui::Stroke::new(1.0, t.card_border)).corner_radius(6).inner_margin(egui::Margin::symmetric(12, 9)).show(ui, |ui| { - ui.set_width(ui.available_width()); - ui.horizontal(|ui| { - ui.label(RichText::new(&r.name).font(theme::semibold(13.5)).color(t.accent)); - if r.private { - widgets::chip(ui, "Private", t.warning); - } - if r.fork { - widgets::chip(ui, "Fork", t.text_dim); - } - if r.archived { - widgets::chip(ui, "Archived", t.danger); - } - ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { - if ui.button(format!("{} Visit", icon::ARROW_SQUARE_OUT)).on_hover_text("Open repository in browser").clicked() { - action = Some(Action::Visit(r.html_url.clone())); + let resp = egui::Frame::new() + .fill(t.card) + .stroke(egui::Stroke::new(1.0, t.card_border)) + .corner_radius(6) + .inner_margin(egui::Margin::symmetric(12, 9)) + .show(ui, |ui| { + ui.set_width(ui.available_width()); + ui.horizontal(|ui| { + ui.label(RichText::new(&r.name).font(theme::semibold(13.5)).color(t.accent)); + if r.private { + widgets::chip(ui, "Private", t.warning); } - if ui.button(format!("{} ZIP", icon::FILE_ZIP)).on_hover_text("Download repository as zip").clicked() { - action = Some(Action::Zip(r.clone())); + if r.fork { + widgets::chip(ui, "Fork", t.text_dim); + } + if r.archived { + widgets::chip(ui, "Archived", t.danger); + } + ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { + if ui.button(format!("{} Visit", icon::ARROW_SQUARE_OUT)).on_hover_text("Open repository in browser").clicked() { + action = Some(Action::Visit(r.html_url.clone())); + } + if ui.button(format!("{} ZIP", icon::FILE_ZIP)).on_hover_text("Download repository as zip").clicked() { + action = Some(Action::Zip(r.clone())); + } + }); + }); + if !r.latest_committer.is_empty() && !r.latest_commit_at.is_empty() { + ui.label(RichText::new(format!("Last commit: {} by {}", local_time(&r.latest_commit_at), r.latest_committer)).color(t.text_faint).size(11.5)); + } + if !r.description.is_empty() { + ui.label(RichText::new(&r.description).color(t.text_dim)); + } + ui.horizontal(|ui| { + if !r.language.is_empty() { + let (dot, _) = ui.allocate_exact_size(vec2(10.0, 10.0), Sense::hover()); + ui.painter().circle_filled(dot.center(), 5.0, lang_colour(&r.language)); + ui.label(RichText::new(&r.language).color(t.text_dim).size(11.5)); + ui.add_space(8.0); + } + if r.stars > 0 { + ui.label(RichText::new(format!("{} {}", icon::STAR, widgets::group(r.stars as u64))).color(t.text_dim).size(11.5)); + ui.add_space(8.0); + } + if !r.pushed_at.is_empty() { + ui.label(RichText::new(relative_time(&r.pushed_at, now)).color(t.text_faint).size(11.5)).on_hover_text(local_time(&r.pushed_at)); } }); }); - if !r.latest_committer.is_empty() && !r.latest_commit_at.is_empty() { - ui.label(RichText::new(format!("Last commit: {} by {}", local_time(&r.latest_commit_at), r.latest_committer)).color(t.text_faint).size(11.5)); - } - if !r.description.is_empty() { - ui.label(RichText::new(&r.description).color(t.text_dim)); - } - ui.horizontal(|ui| { - if !r.language.is_empty() { - let (dot, _) = ui.allocate_exact_size(vec2(10.0, 10.0), Sense::hover()); - ui.painter().circle_filled(dot.center(), 5.0, lang_colour(&r.language)); - ui.label(RichText::new(&r.language).color(t.text_dim).size(11.5)); - ui.add_space(8.0); - } - if r.stars > 0 { - ui.label(RichText::new(format!("{} {}", icon::STAR, widgets::group(r.stars as u64))).color(t.text_dim).size(11.5)); - ui.add_space(8.0); - } - if !r.pushed_at.is_empty() { - ui.label(RichText::new(relative_time(&r.pushed_at, now)).color(t.text_faint).size(11.5)).on_hover_text(local_time(&r.pushed_at)); - } - }); - }); let resp = resp.response.interact(Sense::click()); if resp.double_clicked() { action = Some(Action::Visit(r.html_url.clone())); diff --git a/rust/app/src/irc_view.rs b/rust/app/src/irc_view.rs index a4c974d..9d75926 100644 --- a/rust/app/src/irc_view.rs +++ b/rust/app/src/irc_view.rs @@ -317,7 +317,9 @@ impl IrcView { fn log(&mut self, ui: &mut Ui, theme: &'static TermTheme) { let font = FontId::monospace(self.font_size); let wrap = ui.available_width().max(100.0); - let Some(target) = self.targets.iter_mut().find(|t| t.name == self.active) else { return }; + let Some(target) = self.targets.iter_mut().find(|t| t.name == self.active) else { + return; + }; // Lay out lines once per width/theme; the galley cache keeps scrolling cheap. let mut tops = Vec::with_capacity(target.lines.len()); let mut y = 0.0; @@ -453,7 +455,8 @@ impl IrcView { ui.label(RichText::new(r.users.to_string()).color(t.text_faint).size(11.0)); ui.label(RichText::new(ircformat::plain_text(&r.topic)).color(t.text_faint).size(11.0)); }); - ui.interact(ui.max_rect(), ui.id().with("tip"), Sense::hover()).on_hover_text(format!("{}\nClick to join", ircformat::plain_text(if r.topic.is_empty() { &r.name } else { &r.topic }))); + ui.interact(ui.max_rect(), ui.id().with("tip"), Sense::hover()) + .on_hover_text(format!("{}\nClick to join", ircformat::plain_text(if r.topic.is_empty() { &r.name } else { &r.topic }))); }); join_room = clicked.map(|i| listed[i].name.clone()); }); @@ -474,7 +477,7 @@ impl IrcView { ui.interact(ui.max_rect(), ui.id().with("tip"), Sense::hover()).on_hover_text(format!("{}\nClick to mention", u[i])); }); if let Some(i) = clicked { - self.insert_mention(&display_nick(&u[i]).to_string()); + self.insert_mention(display_nick(&u[i])); } } _ => widgets::hint(ui, "No users yet"), diff --git a/rust/app/src/profile_editor.rs b/rust/app/src/profile_editor.rs index 7e4d40d..c9025a5 100644 --- a/rust/app/src/profile_editor.rs +++ b/rust/app/src/profile_editor.rs @@ -208,7 +208,10 @@ impl ProfileEditor { }); ui.add_space(2.0); let errs = if self.show_errors { self.validate() } else { vec![] }; - egui::Panel::bottom("profile-editor-footer").resizable(false).frame(egui::Frame::NONE.fill(t.dock).inner_margin(egui::Margin::symmetric(0, 6))).show(ui, |ui| self.footer(ui, s, &errs)); + egui::Panel::bottom("profile-editor-footer") + .resizable(false) + .frame(egui::Frame::NONE.fill(t.dock).inner_margin(egui::Margin::symmetric(0, 6))) + .show(ui, |ui| self.footer(ui, s, &errs)); egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| { ui.add_space(4.0); self.general(ui, &errs); @@ -240,7 +243,12 @@ impl ProfileEditor { if !errs.is_empty() { let all = errs.iter().map(|e| e.message.clone()).collect::>().join("\n"); let n = errs.len(); - ui.label(RichText::new(format!("{} {n} problem{} — see the highlighted fields", icon::WARNING_CIRCLE, if n == 1 { "" } else { "s" })).color(t.danger).size(12.0)).on_hover_text(all); + ui.label( + RichText::new(format!("{} {n} problem{} — see the highlighted fields", icon::WARNING_CIRCLE, if n == 1 { "" } else { "s" })) + .color(t.danger) + .size(12.0), + ) + .on_hover_text(all); } if self.confirm_delete { ui.horizontal(|ui| { @@ -435,11 +443,14 @@ impl ProfileEditor { item(ui, |ui| { ui.horizontal(|ui| { let opts = [(ForwardDirection::Local, "Local →"), (ForwardDirection::Remote, "← Remote")]; - ComboBox::from_id_salt(("pf-dir", i)).width(84.0).selected_text(if pf.direction == ForwardDirection::Local { opts[0].1 } else { opts[1].1 }).show_ui(ui, |ui| { - for (v, l) in opts { - ui.selectable_value(&mut pf.direction, v, l); - } - }); + ComboBox::from_id_salt(("pf-dir", i)) + .width(84.0) + .selected_text(if pf.direction == ForwardDirection::Local { opts[0].1 } else { opts[1].1 }) + .show_ui(ui, |ui| { + for (v, l) in opts { + ui.selectable_value(&mut pf.direction, v, l); + } + }); ui.with_layout(Layout::right_to_left(Align::Center), |ui| { act = row_buttons(ui, i, n).or(act.take()); ui.checkbox(&mut pf.enabled, "Active").on_hover_text("Start on connect"); @@ -460,13 +471,22 @@ impl ProfileEditor { apply(&mut c.port_forwards, act); list_errs(ui, errs, "ssh.portForwards"); if ui.button(format!("{} Add Port Forward", icon::PLUS)).clicked() { - c.port_forwards.push(PortForward { id: new_id(), bind_host: "127.0.0.1".into(), bind_port: 1, dest_port: 1, enabled: true, ..Default::default() }); + c.port_forwards.push(PortForward { + id: new_id(), + bind_host: "127.0.0.1".into(), + bind_port: 1, + dest_port: 1, + enabled: true, + ..Default::default() + }); } }); section(ui, "Startup", |ui| { text(ui, "Startup Command", &mut c.startup_command, "Optional command to run after connecting"); label(ui, "Environment Variables"); - ui.add(TextEdit::multiline(env).hint_text("KEY=VALUE (one per line)").desired_rows(3).desired_width(f32::INFINITY).font(egui::TextStyle::Monospace)); + ui.add( + TextEdit::multiline(env).hint_text("KEY=VALUE (one per line)").desired_rows(3).desired_width(f32::INFINITY).font(egui::TextStyle::Monospace), + ); }); section(ui, "Login Script", |ui| login_script(ui, "ssh-login", &mut c.login_scripts)); section(ui, "SSH Agent", |ui| { @@ -488,7 +508,9 @@ impl ProfileEditor { if let Some((_, until)) = totp_flash { ui.ctx().request_repaint_after(std::time::Duration::from_secs_f64((*until - s.now).max(0.0))); } - let test = ui.add(Button::new(RichText::new(flash.as_deref().unwrap_or("Test")).font(theme::mono(12.5)))).on_hover_text("Generate a code with the saved secret"); + let test = ui + .add(Button::new(RichText::new(flash.as_deref().unwrap_or("Test")).font(theme::mono(12.5)))) + .on_hover_text("Generate a code with the saved secret"); if test.clicked() { *totp_flash = Some(match (*is_new, s.svc.totp_code(&id)) { (false, Ok(code)) => (code, s.now + 5.0), @@ -634,7 +656,24 @@ impl ProfileEditor { err(ui, errs, "vnc.quality"); }); section(ui, "SSH Tunnel", |ui| { - tunnel(ui, "vnc", Tunnel { on: &mut c.ssh_tunnel, host: &mut c.ssh_host, port: &mut c.ssh_port, user: &mut c.ssh_username, auth: &mut c.ssh_auth_method, key: &mut c.ssh_private_key, agent: &mut c.ssh_agent_auth, mode: &mut c.ssh_secret_mode }, keys, key_checks, secrets, errs); + tunnel( + ui, + "vnc", + Tunnel { + on: &mut c.ssh_tunnel, + host: &mut c.ssh_host, + port: &mut c.ssh_port, + user: &mut c.ssh_username, + auth: &mut c.ssh_auth_method, + key: &mut c.ssh_private_key, + agent: &mut c.ssh_agent_auth, + mode: &mut c.ssh_secret_mode, + }, + keys, + key_checks, + secrets, + errs, + ); }); } @@ -686,7 +725,24 @@ impl ProfileEditor { } }); section(ui, "SSH Tunnel", |ui| { - tunnel(ui, "irc", Tunnel { on: &mut c.ssh_tunnel, host: &mut c.ssh_host, port: &mut c.ssh_port, user: &mut c.ssh_username, auth: &mut c.ssh_auth_method, key: &mut c.ssh_private_key, agent: &mut c.ssh_agent_auth, mode: &mut c.ssh_secret_mode }, keys, key_checks, secrets, errs); + tunnel( + ui, + "irc", + Tunnel { + on: &mut c.ssh_tunnel, + host: &mut c.ssh_host, + port: &mut c.ssh_port, + user: &mut c.ssh_username, + auth: &mut c.ssh_auth_method, + key: &mut c.ssh_private_key, + agent: &mut c.ssh_agent_auth, + mode: &mut c.ssh_secret_mode, + }, + keys, + key_checks, + secrets, + errs, + ); }); section(ui, "Reconnection", |ui| reconnect(ui, &mut c.auto_reconnect, &mut c.max_reconnect_tries)); } @@ -696,11 +752,13 @@ impl ProfileEditor { section(ui, "Line Settings", |ui| { label(ui, "Baud Rate"); ui.horizontal(|ui| { - ComboBox::from_id_salt("serial-baud").selected_text(if BAUDS.contains(&c.baud_rate) { c.baud_rate.to_string() } else { "Custom".into() }).show_ui(ui, |ui| { - for b in BAUDS { - ui.selectable_value(&mut c.baud_rate, b, b.to_string()); - } - }); + ComboBox::from_id_salt("serial-baud") + .selected_text(if BAUDS.contains(&c.baud_rate) { c.baud_rate.to_string() } else { "Custom".into() }) + .show_ui(ui, |ui| { + for b in BAUDS { + ui.selectable_value(&mut c.baud_rate, b, b.to_string()); + } + }); ui.add(DragValue::new(&mut c.baud_rate).range(1..=20_000_000)).on_hover_text("Any rate the adapter supports"); }); err(ui, errs, "serial.baudRate"); @@ -709,7 +767,12 @@ impl ProfileEditor { combo(ui, "Parity", &mut c.parity, &[(Parity::None, "None"), (Parity::Odd, "Odd"), (Parity::Even, "Even")]); combo(ui, "Stop Bits", &mut c.stop_bits, &[(1, "1"), (2, "2")]); err(ui, errs, "serial.stopBits"); - combo(ui, "Flow Control", &mut c.flow_control, &[(FlowControl::None, "None"), (FlowControl::Xonxoff, "XON/XOFF (software)"), (FlowControl::Rtscts, "RTS/CTS (hardware)")]); + combo( + ui, + "Flow Control", + &mut c.flow_control, + &[(FlowControl::None, "None"), (FlowControl::Xonxoff, "XON/XOFF (software)"), (FlowControl::Rtscts, "RTS/CTS (hardware)")], + ); }); section(ui, "Behaviour", |ui| { line_ending(ui, &mut c.line_ending); @@ -799,11 +862,7 @@ fn prep(p: &mut Profile) { } fn parse_env(text: &str) -> BTreeMap { - text.lines() - .filter_map(|l| l.split_once('=')) - .map(|(k, v)| (k.trim().to_string(), v.trim().to_string())) - .filter(|(k, _)| !k.is_empty()) - .collect() + text.lines().filter_map(|l| l.split_once('=')).map(|(k, v)| (k.trim().to_string(), v.trim().to_string())).filter(|(k, _)| !k.is_empty()).collect() } /// "ssh.jumpHosts[2].host" → 2 @@ -1059,7 +1118,9 @@ fn login_script(ui: &mut Ui, id: &str, steps: &mut Vec) { } else { ui.add_space(56.0 + ui.spacing().item_spacing.x); } - ui.add(TextEdit::singleline(&mut st.value).hint_text(if wait { "Text to match…" } else { "Text to send…" }).desired_width(ui.available_width())); + ui.add( + TextEdit::singleline(&mut st.value).hint_text(if wait { "Text to match…" } else { "Text to send…" }).desired_width(ui.available_width()), + ); }); }); } diff --git a/rust/app/src/quick_connect.rs b/rust/app/src/quick_connect.rs index 1b80f6f..39140a3 100644 --- a/rust/app/src/quick_connect.rs +++ b/rust/app/src/quick_connect.rs @@ -24,7 +24,16 @@ pub struct QuickConnect { impl Default for QuickConnect { fn default() -> Self { - QuickConnect { open: false, focus: false, protocol: Protocol::Ssh, host: String::new(), port: 22, username: String::new(), nick: "nexus".into(), ports: vec![] } + QuickConnect { + open: false, + focus: false, + protocol: Protocol::Ssh, + host: String::new(), + port: 22, + username: String::new(), + nick: "nexus".into(), + ports: vec![], + } } } @@ -97,7 +106,8 @@ impl QuickConnect { let r = ui .horizontal(|ui| { let w = if serial { ui.available_width() - 34.0 } else { ui.available_width() }; - let r = ui.add(TextEdit::singleline(&mut self.host).hint_text(if serial { "/dev/ttyUSB0 or COM3" } else { "e.g. 192.168.1.1" }).desired_width(w)); + let r = ui + .add(TextEdit::singleline(&mut self.host).hint_text(if serial { "/dev/ttyUSB0 or COM3" } else { "e.g. 192.168.1.1" }).desired_width(w)); if serial { ui.menu_button(icon::CARET_DOWN, |ui| { if self.ports.is_empty() { diff --git a/rust/app/src/raw_view.rs b/rust/app/src/raw_view.rs index ba1e5c4..facdb83 100644 --- a/rust/app/src/raw_view.rs +++ b/rust/app/src/raw_view.rs @@ -227,7 +227,9 @@ impl RawView { s.warn("Nothing to save"); return; } - let Some(path) = rfd::FileDialog::new().set_file_name("capture.json").add_filter("JSON", &["json"]).save_file() else { return }; + let Some(path) = rfd::FileDialog::new().set_file_name("capture.json").add_filter("JSON", &["json"]).save_file() else { + return; + }; match s.svc.sessions.raw_save_capture(&self.session, &path) { Ok(()) => s.success("Capture saved"), Err(e) => s.error(format!("Save failed: {e}")), @@ -450,7 +452,9 @@ fn hex_job(line: &str, font: &FontId, t: &Tokens, fg: Color32) -> LayoutJob { /// HH:MM:SS for today, otherwise "Mon DD HH:MM:SS" (the web build's formatTimestamp). fn format_time(ms: i64) -> String { use chrono::{Local, TimeZone}; - let Some(d) = Local.timestamp_millis_opt(ms).single() else { return String::new() }; + let Some(d) = Local.timestamp_millis_opt(ms).single() else { + return String::new(); + }; if d.date_naive() == Local::now().date_naive() { d.format("%H:%M:%S").to_string() } else { d.format("%b %d %H:%M:%S").to_string() } } diff --git a/rust/app/src/recordings.rs b/rust/app/src/recordings.rs index bbc6ba5..d053333 100644 --- a/rust/app/src/recordings.rs +++ b/rust/app/src/recordings.rs @@ -15,7 +15,7 @@ const SPEEDS: [f64; 5] = [0.5, 1.0, 2.0, 5.0, 10.0]; /// m:ss, as the JS `_formatSeconds`. pub fn mmss(secs: f64) -> String { - if !(secs > 0.0) { + if secs.is_nan() || secs <= 0.0 { return "0:00".into(); } let s = secs.floor() as u64; @@ -97,7 +97,18 @@ pub struct Recordings { impl Default for Recordings { fn default() -> Self { - Recordings { open: false, list: vec![], info: None, loading: false, rx: None, age: 30, size: 1 << 30, status: String::new(), confirm_delete: None, playback: None } + Recordings { + open: false, + list: vec![], + info: None, + loading: false, + rx: None, + age: 30, + size: 1 << 30, + status: String::new(), + confirm_delete: None, + playback: None, + } } } @@ -150,7 +161,8 @@ impl Recordings { } let total = events.last().map_or(1.0, |e| e.elapsed).max(f64::EPSILON); let title = if header.title.is_empty() { title } else { header.title }; - let mut p = Playback { title, term: TermView::new(10_000, 13.0, CursorStyle::Block), events, total, pos: 0.0, next: 0, playing: false, speed: 1.0 }; + let mut p = + Playback { title, term: TermView::new(10_000, 13.0, CursorStyle::Block), events, total, pos: 0.0, next: 0, playing: false, speed: 1.0 }; p.play(); // autoplay self.playback = Some(p); } @@ -222,7 +234,11 @@ impl Recordings { ui.set_width(ui.available_width() - 64.0); ui.label(RichText::new(if r.title.is_empty() { &r.id } else { &r.title }).font(theme::semibold(13.0))); let date = r.created_at.with_timezone(&chrono::Local).format("%Y-%m-%d %H:%M"); - ui.label(RichText::new(format!("{date} · {} · {} · {}×{}", mmss(r.duration), widgets::human_size(r.size), r.width, r.height)).size(11.5).color(t.text_dim)); + ui.label( + RichText::new(format!("{date} · {} · {} · {}×{}", mmss(r.duration), widgets::human_size(r.size), r.width, r.height)) + .size(11.5) + .color(t.text_dim), + ); }); if widgets::icon_button(ui, icon::PLAY, "Play").clicked() { play = Some(i); @@ -245,7 +261,9 @@ impl Recordings { } fn delete_dialog(&mut self, ctx: &egui::Context, s: &mut Shared) { - let Some(r) = &self.confirm_delete else { return }; + let Some(r) = &self.confirm_delete else { + return; + }; let (mut yes, mut no) = (false, ctx.input(|i| i.key_pressed(Key::Escape))); let name = if r.title.is_empty() { r.id.clone() } else { r.title.clone() }; crate::chrome::modal(ctx, "rec-delete", 380.0, |ui| { @@ -309,11 +327,14 @@ impl Recordings { }); }); }); - egui::Frame::NONE.fill(egui::Color32::from_rgba_unmultiplied(theme.background[0], theme.background[1], theme.background[2], 255)).corner_radius(t.radius).show(ui, |ui| { - ui.set_min_size(ui.available_size()); - // Read-only: keyboard input and terminal replies are dropped. - let _ = p.term.ui(ui, theme, false); - }); + egui::Frame::NONE + .fill(egui::Color32::from_rgba_unmultiplied(theme.background[0], theme.background[1], theme.background[2], 255)) + .corner_radius(t.radius) + .show(ui, |ui| { + ui.set_min_size(ui.available_size()); + // Read-only: keyboard input and terminal replies are dropped. + let _ = p.term.ui(ui, theme, false); + }); }); if !open { self.playback = None; diff --git a/rust/app/src/sftp.rs b/rust/app/src/sftp.rs index c9c8c41..5afef0f 100644 --- a/rust/app/src/sftp.rs +++ b/rust/app/src/sftp.rs @@ -19,14 +19,27 @@ use std::sync::{Arc, Mutex}; /// Worker results, applied on the UI thread. enum Done { - Listed { session: String, path: String, files: Result, String> }, - Picker { session: String, result: Result<(String, Vec, bool), String> }, + Listed { + session: String, + path: String, + files: Result, String>, + }, + Picker { + session: String, + result: Result<(String, Vec, bool), String>, + }, Refresh(String), - TransferEnded { session: String, path: String }, + TransferEnded { + session: String, + path: String, + }, /// Always shown. Toast(String), /// Shown only while the panel for `session` is open (otherwise per-file toasts cover it). - Summary { session: String, text: String }, + Summary { + session: String, + text: String, + }, } #[derive(Clone)] @@ -372,10 +385,14 @@ impl Sftp { fn download(&mut self, s: &Shared, session: &str, files: Vec) { let targets: Vec<(FileEntry, PathBuf)> = if files.len() == 1 { - let Some(p) = rfd::FileDialog::new().set_file_name(&files[0].name).save_file() else { return }; + let Some(p) = rfd::FileDialog::new().set_file_name(&files[0].name).save_file() else { + return; + }; vec![(files[0].clone(), p)] } else { - let Some(dir) = rfd::FileDialog::new().set_title("Download to folder").pick_folder() else { return }; + let Some(dir) = rfd::FileDialog::new().set_title("Download to folder").pick_folder() else { + return; + }; files.into_iter().filter_map(|e| safe_sftp_upload_name(&e.name).ok().map(|n| (e, dir.join(n)))).collect() }; for (e, _) in &targets { @@ -422,7 +439,14 @@ impl Sftp { } /// Runs a single SFTP operation, then toasts `ok` and refreshes the listing. - fn op(&mut self, s: &Shared, session: &str, ok: &'static str, what: &'static str, f: impl FnOnce(&Service, &str) -> nexus_core::Result<()> + Send + 'static) { + fn op( + &mut self, + s: &Shared, + session: &str, + ok: &'static str, + what: &'static str, + f: impl FnOnce(&Service, &str) -> nexus_core::Result<()> + Send + 'static, + ) { let sid = session.to_string(); self.work(s, move |svc, out| { match f(svc, &sid) { @@ -460,7 +484,9 @@ impl Sftp { /// The dock panel. pub fn ui(&mut self, ui: &mut Ui, s: &mut Shared) { self.drain(s); - let Some(session) = self.open_for.clone() else { return }; + let Some(session) = self.open_for.clone() else { + return; + }; if !self.panels.contains_key(&session) { self.navigate(s, &session, None); } @@ -535,7 +561,9 @@ impl Sftp { }); }); - let Some(p) = self.panels.get(&session) else { return }; + let Some(p) = self.panels.get(&session) else { + return; + }; if let Some(go) = breadcrumb(ui, &p.cwd) { act = Some(Act::Nav(go)); } @@ -555,7 +583,11 @@ impl Sftp { ui.add(egui::Label::new(base_name(&x.path)).truncate()).on_hover_text(&x.path); ui.add(egui::ProgressBar::new(frac).desired_height(4.0).fill(t.accent)); let total = if x.total > 0 { widgets::human_size(x.total) } else { "?".into() }; - ui.label(RichText::new(format!("{} / {total} ({:.0}%)", widgets::human_size(x.transferred), frac * 100.0)).size(11.0).color(t.text_faint)); + ui.label( + RichText::new(format!("{} / {total} ({:.0}%)", widgets::human_size(x.transferred), frac * 100.0)) + .size(11.0) + .color(t.text_faint), + ); }); }); } @@ -595,7 +627,13 @@ impl Sftp { if p.selected.contains(&e.path) { ui.painter().rect_filled(r.expand2(vec2(6.0, 0.0)), 3.0, t.row_selected); } - ui.painter().text(r.left_center() + vec2(8.0, 0.0), egui::Align2::CENTER_CENTER, file_icon(e), egui::FontId::proportional(14.0), if e.is_dir { t.accent } else { t.text_dim }); + ui.painter().text( + r.left_center() + vec2(8.0, 0.0), + egui::Align2::CENTER_CENTER, + file_icon(e), + egui::FontId::proportional(14.0), + if e.is_dir { t.accent } else { t.text_dim }, + ); let meta_w = if e.is_dir { 0.0 } else { 72.0 }; let nr = egui::Rect::from_min_max(egui::pos2(r.min.x + 22.0, r.min.y), egui::pos2(r.max.x - meta_w - 4.0, r.max.y)); ui.scope_builder(egui::UiBuilder::new().max_rect(nr).layout(Layout::left_to_right(Align::Center)), |ui| { @@ -603,7 +641,13 @@ impl Sftp { }); if !e.is_dir { let mr = egui::Rect::from_min_max(egui::pos2(r.max.x - meta_w, r.min.y), r.max); - ui.painter().text(mr.right_center(), egui::Align2::RIGHT_CENTER, widgets::human_size(e.size), egui::FontId::proportional(11.5), t.text_faint); + ui.painter().text( + mr.right_center(), + egui::Align2::RIGHT_CENTER, + widgets::human_size(e.size), + egui::FontId::proportional(11.5), + t.text_faint, + ); if e.modified_at > 0 { ui.interact(mr, ui.id().with(("meta", i)), Sense::hover()).on_hover_text(modified(e.modified_at)); } @@ -718,14 +762,14 @@ impl Sftp { } } - - // --- dialogs -------------------------------------------------------------------------- /// Modal dialogs (file drop, directory picker), drawn every frame. pub fn dialogs(&mut self, ctx: &egui::Context, s: &mut Shared) { self.drain(s); - let Some(mut d) = self.dialog.take() else { return }; + let Some(mut d) = self.dialog.take() else { + return; + }; let t = Tokens::get(ctx); let esc = ctx.input(|i| i.key_pressed(Key::Escape)); let enter = ctx.input(|i| i.key_pressed(Key::Enter)); @@ -802,7 +846,9 @@ impl Sftp { modal(ctx, "sftp-drop", 420.0, |ui| { ui.label(RichText::new(format!("{} File Drop", icon::FILE_ARROW_UP)).font(theme::semibold(15.0))); ui.label(RichText::new(title).strong()); - ui.label(RichText::new(format!("Choose what to do with the dropped {}.", if paths.len() == 1 { "file" } else { "files" })).color(t.text_dim)); + ui.label( + RichText::new(format!("Choose what to do with the dropped {}.", if paths.len() == 1 { "file" } else { "files" })).color(t.text_dim), + ); ui.add_space(8.0); ui.horizontal(|ui| { upload = ui @@ -894,8 +940,15 @@ impl Sftp { // Keyboard: ↑↓ move (skipping unreadable folders), Enter uses, Backspace goes up. if ready { - let (down, up, back) = ctx.input_mut(|i| (i.consume_key(Modifiers::NONE, Key::ArrowDown), i.consume_key(Modifiers::NONE, Key::ArrowUp), i.key_pressed(Key::Backspace))); - let delta: isize = if down { 1 } else if up { -1 } else { 0 }; + let (down, up, back) = ctx + .input_mut(|i| (i.consume_key(Modifiers::NONE, Key::ArrowDown), i.consume_key(Modifiers::NONE, Key::ArrowUp), i.key_pressed(Key::Backspace))); + let delta: isize = if down { + 1 + } else if up { + -1 + } else { + 0 + }; if delta != 0 && !visible.is_empty() { let n = visible.len() as isize; let mut idx = pk.cursor.map_or(if delta > 0 { -1 } else { n }, |c| c as isize); @@ -1003,7 +1056,8 @@ impl Sftp { ui.separator(); ui.horizontal(|ui| { let suffix = if sel.is_none() && !pk.cwd_writable { " (read only)" } else { "" }; - ui.add(egui::Label::new(RichText::new(format!("{target}{suffix}")).font(theme::mono(11.5)).color(t.text_dim)).truncate()).on_hover_text(&target); + ui.add(egui::Label::new(RichText::new(format!("{target}{suffix}")).font(theme::mono(11.5)).color(t.text_dim)).truncate()) + .on_hover_text(&target); let status = match (sel.is_some(), writable) { (_, true) => "", (true, false) => "No write permission", diff --git a/rust/app/src/sidebar.rs b/rust/app/src/sidebar.rs index 9e97533..2997b17 100644 --- a/rust/app/src/sidebar.rs +++ b/rust/app/src/sidebar.rs @@ -74,7 +74,7 @@ fn list(app: &mut App, ui: &mut Ui) { fuzzy_score(&fields, &q).map(|s| (s, p.clone())) }) .collect(); - hits.sort_by(|a, b| b.0.cmp(&a.0)); + hits.sort_by_key(|h| std::cmp::Reverse(h.0)); if hits.is_empty() { widgets::empty(ui, icon::MAGNIFYING_GLASS, "No matches", "Try a host, name, tag or protocol."); } @@ -91,7 +91,7 @@ fn list(app: &mut App, ui: &mut Ui) { section(app, ui, "★ Favourites", "favourites", &favs); } let mut recent: Vec<&Profile> = profiles.iter().filter(|p| p.last_connected.is_some()).collect(); - recent.sort_by(|a, b| b.last_connected.cmp(&a.last_connected)); + recent.sort_by_key(|p| std::cmp::Reverse(p.last_connected)); recent.truncate(RECENT); if !recent.is_empty() { section(app, ui, "Recent", "recent", &recent); @@ -169,7 +169,13 @@ fn local_row(app: &mut App, ui: &mut Ui) { if resp.hovered() { ui.painter().rect_filled(r, 6.0, t.hover); } - ui.painter().text(r.left_center() + vec2(10.0, 0.0), Align2::LEFT_CENTER, icon::TERMINAL_WINDOW, egui::FontId::proportional(15.0), crate::views::protocol_color(nexus_core::models::Protocol::Local)); + ui.painter().text( + r.left_center() + vec2(10.0, 0.0), + Align2::LEFT_CENTER, + icon::TERMINAL_WINDOW, + egui::FontId::proportional(15.0), + crate::views::protocol_color(nexus_core::models::Protocol::Local), + ); ui.painter().text(r.left_center() + vec2(32.0, 0.0), Align2::LEFT_CENTER, "Local Machine", egui::FontId::proportional(13.0), t.text); if resp.on_hover_text("Open a terminal on this computer").clicked() { app.connect_local(); @@ -204,7 +210,14 @@ fn row(app: &mut App, ui: &mut Ui, p: &Profile, section: &str) { if p.favourite { ui.painter().text(r.right_center() - vec2(36.0, 0.0), Align2::RIGHT_CENTER, "★", egui::FontId::proportional(11.0), t.warning); } - let tip = format!("{}\n{} {}:{}{}", name, protocol_badge(p.protocol), p.host, p.port, if p.description.is_empty() { String::new() } else { format!("\n{}", p.description) }); + let tip = format!( + "{}\n{} {}:{}{}", + name, + protocol_badge(p.protocol), + p.host, + p.port, + if p.description.is_empty() { String::new() } else { format!("\n{}", p.description) } + ); let resp = resp.on_hover_text(tip); let multi = ui.input(|i| i.modifiers.command); if resp.double_clicked() { @@ -281,16 +294,31 @@ fn bulk_bar(app: &mut App, ui: &mut Ui) { } ui.menu_button(format!("{} Tags", icon::TAG), |ui| { if ui.button("Add tags…").clicked() { - app.dialog = Some(Dialog::Input { title: "Add tags".into(), label: "Comma-separated tags to add".into(), value: String::new(), then: InputThen::Tags { profiles: ids.clone(), replace: false } }); + app.dialog = Some(Dialog::Input { + title: "Add tags".into(), + label: "Comma-separated tags to add".into(), + value: String::new(), + then: InputThen::Tags { profiles: ids.clone(), replace: false }, + }); ui.close(); } if ui.button("Replace tags…").clicked() { - app.dialog = Some(Dialog::Input { title: "Replace tags".into(), label: "Comma-separated tags".into(), value: String::new(), then: InputThen::Tags { profiles: ids.clone(), replace: true } }); + app.dialog = Some(Dialog::Input { + title: "Replace tags".into(), + label: "Comma-separated tags".into(), + value: String::new(), + then: InputThen::Tags { profiles: ids.clone(), replace: true }, + }); ui.close(); } }); if ui.small_button(format!("{} Move group", icon::FOLDER)).clicked() { - app.dialog = Some(Dialog::Input { title: "Move to group".into(), label: "Group name (empty for Ungrouped)".into(), value: String::new(), then: InputThen::MoveGroup { profiles: ids.clone() } }); + app.dialog = Some(Dialog::Input { + title: "Move to group".into(), + label: "Group name (empty for Ungrouped)".into(), + value: String::new(), + then: InputThen::MoveGroup { profiles: ids.clone() }, + }); } if ui.small_button(format!("{} Delete", icon::TRASH)).clicked() { app.dialog = Some(Dialog::Delete { profiles: ids.clone() }); diff --git a/rust/app/src/snippets.rs b/rust/app/src/snippets.rs index 44cfadf..598caea 100644 --- a/rust/app/src/snippets.rs +++ b/rust/app/src/snippets.rs @@ -71,7 +71,15 @@ pub struct Snippets { impl Default for Snippets { fn default() -> Self { - Snippets { open: false, list: vec![], search: String::new(), pane: Pane::Empty("Select a snippet or create a new one"), confirm_delete: None, vars: None, loaded: false } + Snippets { + open: false, + list: vec![], + search: String::new(), + pane: Pane::Empty("Select a snippet or create a new one"), + confirm_delete: None, + vars: None, + loaded: false, + } } } @@ -263,7 +271,9 @@ impl Snippets { /// Resolves variables then types the command (plus newline) into the focused session. fn send(&mut self, s: &mut Shared, command: &str) { - let Some(id) = s.focused_session() else { return s.error("No active session") }; + let Some(id) = s.focused_session() else { + return s.error("No active session"); + }; if s.session_state(&id) != Some(SessionState::Connected) { return s.error("Session is not connected"); } @@ -306,7 +316,9 @@ impl Snippets { } fn delete_dialog(&mut self, ctx: &egui::Context, s: &mut Shared) { - let Some(id) = self.confirm_delete.clone() else { return }; + let Some(id) = self.confirm_delete.clone() else { + return; + }; let name = self.list.iter().find(|x| x.id == id).map(|x| x.name.clone()).unwrap_or_default(); let (mut yes, mut no) = (false, ctx.input(|i| i.key_pressed(Key::Escape))); crate::chrome::modal(ctx, "snippet-delete", 360.0, |ui| { @@ -335,7 +347,16 @@ impl Snippets { fn blank() -> Snippet { let now = nexus_core::models::now(); - Snippet { id: String::new(), name: String::new(), command: String::new(), description: String::new(), folder: String::new(), tags: vec![], created_at: now, updated_at: now } + Snippet { + id: String::new(), + name: String::new(), + command: String::new(), + description: String::new(), + folder: String::new(), + tags: vec![], + created_at: now, + updated_at: now, + } } fn send_text(s: &mut Shared, text: String) { diff --git a/rust/app/src/splits.rs b/rust/app/src/splits.rs index 124d921..a8d3766 100644 --- a/rust/app/src/splits.rs +++ b/rust/app/src/splits.rs @@ -52,7 +52,13 @@ impl Split { fn at(&mut self, path: &[bool]) -> &mut Split { match (self, path.split_first()) { - (Split::Node { a, b, .. }, Some((side, rest))) => if *side { b.at(rest) } else { a.at(rest) }, + (Split::Node { a, b, .. }, Some((side, rest))) => { + if *side { + b.at(rest) + } else { + a.at(rest) + } + } (s, _) => s, } } @@ -124,20 +130,32 @@ fn divide(r: Rect, dir: Dir, ratio: f32) -> (Rect, Rect, Rect) { match dir { Dir::Right => { let x = r.left() + (r.width() - GAP) * ratio; - (Rect::from_min_max(r.min, pos2(x, r.bottom())), Rect::from_min_max(pos2(x + GAP, r.top()), r.max), Rect::from_min_max(pos2(x, r.top()), pos2(x + GAP, r.bottom()))) + ( + Rect::from_min_max(r.min, pos2(x, r.bottom())), + Rect::from_min_max(pos2(x + GAP, r.top()), r.max), + Rect::from_min_max(pos2(x, r.top()), pos2(x + GAP, r.bottom())), + ) } Dir::Down => { let y = r.top() + (r.height() - GAP) * ratio; - (Rect::from_min_max(r.min, pos2(r.right(), y)), Rect::from_min_max(pos2(r.left(), y + GAP), r.max), Rect::from_min_max(pos2(r.left(), y), pos2(r.right(), y + GAP))) + ( + Rect::from_min_max(r.min, pos2(r.right(), y)), + Rect::from_min_max(pos2(r.left(), y + GAP), r.max), + Rect::from_min_max(pos2(r.left(), y), pos2(r.right(), y + GAP)), + ) } } } /// Splits the focused pane of the active tab. pub fn split_focused(app: &mut App, dir: Dir) { - let Some(tab_id) = app.s.active.clone() else { return }; + let Some(tab_id) = app.s.active.clone() else { + return; + }; let focused = app.s.focused_session().unwrap_or(tab_id.clone()); - let Some(tab) = app.s.tabs.iter_mut().find(|t| t.id == tab_id) else { return }; + let Some(tab) = app.s.tabs.iter_mut().find(|t| t.id == tab_id) else { + return; + }; match &mut tab.split { None => tab.split = Some(Split::Node { dir, ratio: 0.5, a: Box::new(Split::Leaf(Some(tab_id.clone()))), b: Box::new(Split::Leaf(None)) }), Some(sp) => { @@ -152,7 +170,9 @@ pub fn split_focused(app: &mut App, dir: Dir) { /// removed too: the next pane takes over the tab. pub fn remove_session(s: &mut Shared, session: &str) { for i in 0..s.tabs.len() { - let Some(sp) = &mut s.tabs[i].split else { continue }; + let Some(sp) = &mut s.tabs[i].split else { + continue; + }; let mut path = vec![]; if !sp.find(session, &mut path) { continue; @@ -202,7 +222,8 @@ pub fn render(app: &mut App, ui: &mut Ui, tab_id: &str) { }; let min = (MIN_PANE / len).min(0.45); let ratio = ((pos - start) / len).clamp(min, 1.0 - min); - if let Some(Split::Node { ratio: r, .. }) = app.s.tabs.iter_mut().find(|tb| tb.id == tab_id).and_then(|tb| tb.split.as_mut()).map(|sp| sp.at(&path)) { + if let Some(Split::Node { ratio: r, .. }) = app.s.tabs.iter_mut().find(|tb| tb.id == tab_id).and_then(|tb| tb.split.as_mut()).map(|sp| sp.at(&path)) + { *r = ratio; } } @@ -213,9 +234,19 @@ pub fn render(app: &mut App, ui: &mut Ui, tab_id: &str) { let body = Rect::from_min_max(pos2(rect.left(), head.bottom()), rect.max); let focused = session.is_some() && app.s.focused == session; ui.painter().rect_filled(head, 0.0, if focused { t.card } else { t.tab_strip }); - let title = session.as_deref().and_then(|s| app.s.pane(s)).map(|p| (p.info.tab_name.clone(), protocol_color(p.info.protocol))).unwrap_or(("New pane".into(), t.text_faint)); + let title = session + .as_deref() + .and_then(|s| app.s.pane(s)) + .map(|p| (p.info.tab_name.clone(), protocol_color(p.info.protocol))) + .unwrap_or(("New pane".into(), t.text_faint)); ui.painter().circle_filled(head.left_center() + vec2(10.0, 0.0), 3.5, title.1); - ui.painter().with_clip_rect(head.shrink2(vec2(24.0, 0.0))).text(head.left_center() + vec2(20.0, 0.0), Align2::LEFT_CENTER, title.0, egui::FontId::proportional(11.5), if focused { t.text } else { t.text_dim }); + ui.painter().with_clip_rect(head.shrink2(vec2(24.0, 0.0))).text( + head.left_center() + vec2(20.0, 0.0), + Align2::LEFT_CENTER, + title.0, + egui::FontId::proportional(11.5), + if focused { t.text } else { t.text_dim }, + ); let xr = Rect::from_center_size(head.right_center() - vec2(12.0, 0.0), vec2(16.0, 16.0)); if widgets::icon_button(&mut ui.new_child(egui::UiBuilder::new().max_rect(xr)), icon::X, "Close pane").clicked() { close = Some((path.clone(), session.clone())); diff --git a/rust/app/src/ssh_keys.rs b/rust/app/src/ssh_keys.rs index 4d526de..6230966 100644 --- a/rust/app/src/ssh_keys.rs +++ b/rust/app/src/ssh_keys.rs @@ -90,7 +90,8 @@ impl SshKeys { } }); }); - ui.add(egui::Label::new(RichText::new(&k.public_key).font(theme::mono(11.0)).color(t.text_dim)).truncate()).on_hover_text(&k.public_key); + ui.add(egui::Label::new(RichText::new(&k.public_key).font(theme::mono(11.0)).color(t.text_dim)).truncate()) + .on_hover_text(&k.public_key); }); ui.add_space(2.0); } @@ -111,7 +112,8 @@ impl SshKeys { s.error("Passphrases do not match"); } else { let (svc, kind, bits, pass) = (s.svc.clone(), f.kind, f.bits, f.pass.clone()); - self.job = Some(Job::spawn(ctx, move || svc.generate_ssh_key(&name, kind, bits, &pass).map(|_| name).map_err(|e| e.to_string()))); + self.job = + Some(Job::spawn(ctx, move || svc.generate_ssh_key(&name, kind, bits, &pass).map(|_| name).map_err(|e| e.to_string()))); } } if self.job.is_none() && ui.button("Cancel").clicked() { @@ -125,7 +127,9 @@ impl SshKeys { } fn delete_dialog(&mut self, ctx: &egui::Context, s: &mut Shared) { - let Some(name) = self.confirm_delete.clone() else { return }; + let Some(name) = self.confirm_delete.clone() else { + return; + }; let t = Tokens::get(ctx); let mut done = false; let r = egui::Modal::new(egui::Id::new("delete-key")).show(ctx, |ui| { diff --git a/rust/app/src/state.rs b/rust/app/src/state.rs index 21af468..b218276 100644 --- a/rust/app/src/state.rs +++ b/rust/app/src/state.rs @@ -8,36 +8,27 @@ use std::collections::HashSet; use std::sync::Arc; /// Things a feature asks the main window to do (handled once per frame, in order). +#[allow(clippy::large_enum_variant)] // a per-frame queue; boxing buys nothing pub enum Request { /// Open the profile editor for a new profile (optionally pre-set protocol / folder). - NewProfile { protocol: Option, folder: String }, + NewProfile { + protocol: Option, + folder: String, + }, EditProfile(String), Connect(String), ConnectQuick(Profile), - ConnectLocal, /// Reload `Shared::profiles` from the database. ReloadProfiles, /// Re-read and apply preferences (theme, terminal theme, fonts…). ReloadPreferences, - QuickConnect, - Settings(SettingsTab), - SshKeys, ExportEncrypted(Vec), ImportEncrypted, - ExportJson, - ImportJson, - Recordings, - Snippets, - Triggers, Sftp(String), - PortForwards(String), /// Type text into the active session (snippets). SendToActive(String), /// Bytes to a specific session. Write(String, Vec), - Disconnect(String), - CommandPalette, - About, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] @@ -174,11 +165,6 @@ impl Shared { self.panes.get_mut(id) } - /// The tab that shows `session` (its own, or the one it's split into). - pub fn tab_of(&self, session: &str) -> Option<&Tab> { - self.tabs.iter().find(|t| t.id == session || t.split.as_ref().is_some_and(|sp| sp.sessions().iter().any(|x| x == session))) - } - /// The session keyboard actions apply to: the focused pane, else the active tab. pub fn focused_session(&self) -> Option { self.focused.clone().or_else(|| self.active.clone()) @@ -208,9 +194,5 @@ impl Shared { /// Formats a command-key shortcut for menus: "⌘K" on macOS, "Ctrl+K" elsewhere. pub fn keys(s: &str) -> String { - if cfg!(target_os = "macos") { - s.replace("Mod+Shift+", "⇧⌘").replace("Mod+", "⌘") - } else { - s.replace("Mod+", "Ctrl+") - } + if cfg!(target_os = "macos") { s.replace("Mod+Shift+", "⇧⌘").replace("Mod+", "⌘") } else { s.replace("Mod+", "Ctrl+") } } diff --git a/rust/app/src/tabs.rs b/rust/app/src/tabs.rs index c714461..2269960 100644 --- a/rust/app/src/tabs.rs +++ b/rust/app/src/tabs.rs @@ -141,7 +141,13 @@ fn strip(app: &mut App, ui: &mut Ui) { if sftp { let fr = Rect::from_center_size(pos2(body.right() - 33.0, body.center().y), vec2(16.0, 16.0)); let fresp = ui.interact(fr, ui.id().with(("sftp", id)), Sense::click()); - let c = if app.sftp.open_for() == Some(id.as_str()) { t.accent } else if fresp.hovered() { t.text } else { t.text_faint }; + let c = if app.sftp.open_for() == Some(id.as_str()) { + t.accent + } else if fresp.hovered() { + t.text + } else { + t.text_faint + }; ui.painter().text(fr.center(), Align2::CENTER_CENTER, icon::FOLDER_SIMPLE, egui::FontId::proportional(12.0), c); if fresp.on_hover_text("SFTP browser").clicked() { app.s.request(Request::Sftp(id.clone())); @@ -211,7 +217,11 @@ fn strip(app: &mut App, ui: &mut Ui) { if widgets::icon_button(&mut right, icon::PLUS, "New connection").clicked() { app.s.request(Request::NewProfile { protocol: None, folder: String::new() }); } - let b = widgets::icon_button(&mut right, icon::BROADCAST, if app.s.broadcast { "Broadcast ON — click to stop, right-click for targets" } else { "Broadcast input (right-click to pick targets)" }); + let b = widgets::icon_button( + &mut right, + icon::BROADCAST, + if app.s.broadcast { "Broadcast ON — click to stop, right-click for targets" } else { "Broadcast input (right-click to pick targets)" }, + ); if app.s.broadcast { right.painter().rect_stroke(b.rect, 4.0, Stroke::new(1.5, t.warning), egui::StrokeKind::Inside); } @@ -281,7 +291,9 @@ fn tab_action(app: &mut App, id: &str, a: &str) { /// Draws one session into `rect`. pub fn pane(app: &mut App, ui: &mut Ui, id: &str, rect: Rect) { - let Some(mut pane) = app.s.panes.remove(id) else { return }; + let Some(mut pane) = app.s.panes.remove(id) else { + return; + }; let t = Tokens::get(ui.ctx()); let theme_id = app.s.prefs.terminal_theme.clone(); let focused_tab = app.s.focused.as_deref() == Some(id) || (app.s.focused.is_none() && app.s.active.as_deref() == Some(id)); diff --git a/rust/app/src/terminal.rs b/rust/app/src/terminal.rs index d705318..e9fee00 100644 --- a/rust/app/src/terminal.rs +++ b/rust/app/src/terminal.rs @@ -143,14 +143,6 @@ impl TermView { self.focus_requested = true; } - pub fn alt_screen(&self) -> bool { - self.term.mode().contains(TermMode::ALT_SCREEN) - } - - pub fn has_selection(&self) -> bool { - self.term.selection.as_ref().is_some_and(|s| !s.is_empty()) - } - pub fn selection_text(&self) -> Option { self.term.selection_to_string().filter(|s| !s.is_empty()) } @@ -284,13 +276,13 @@ impl TermView { let focused = ui.memory(|m| m.has_focus(id)); out.focused = focused; if focused { - ui.memory_mut(|m| { - m.set_focus_lock_filter(id, egui::EventFilter { tab: true, horizontal_arrows: true, vertical_arrows: true, escape: true }) - }); + ui.memory_mut(|m| m.set_focus_lock_filter(id, egui::EventFilter { tab: true, horizontal_arrows: true, vertical_arrows: true, escape: true })); self.keyboard(ui, &mut out); let cur = self.term.grid().cursor.point; let cursor_rect = Rect::from_min_size(origin + vec2(cur.column.0 as f32 * cw, cur.line.0 as f32 * ch), vec2(cw, ch)); - ui.output_mut(|o| o.ime = Some(egui::output::IMEOutput { purpose: egui::IMEPurpose::Terminal, rect, cursor_rect, should_interrupt_composition: false })); + ui.output_mut(|o| { + o.ime = Some(egui::output::IMEOutput { purpose: egui::IMEPurpose::Terminal, rect, cursor_rect, should_interrupt_composition: false }) + }); } self.mouse(ui, &resp, origin, cw, ch, &mut out); @@ -475,7 +467,9 @@ impl TermView { continue; } if *pressed { - if modifiers.command && let Some(url) = self.url_at(origin, cw, ch, *pos) { + if modifiers.command + && let Some(url) = self.url_at(origin, cw, ch, *pos) + { ui.ctx().open_url(egui::OpenUrl::new_tab(url)); continue; } @@ -560,11 +554,8 @@ impl TermView { let selection = self.term.selection.as_ref().and_then(|s| s.to_range(&self.term)); let sel_bg = rgb(theme.selection); self.refresh_find(); - let finds: Vec<(i32, usize, usize, bool)> = self - .find - .as_ref() - .map(|f| f.matches.iter().enumerate().map(|(i, &(l, a, b))| (l, a, b, i == f.current)).collect()) - .unwrap_or_default(); + let finds: Vec<(i32, usize, usize, bool)> = + self.find.as_ref().map(|f| f.matches.iter().enumerate().map(|(i, &(l, a, b))| (l, a, b, i == f.current)).collect()).unwrap_or_default(); let ncols = self.cols as usize; for vr in 0..self.rows as i32 { let line = Line(vr - offset); @@ -818,7 +809,7 @@ pub fn key_bytes(key: Key, m: Modifiers, mode: TermMode) -> Option> { Key::F10 => tilde(21), Key::F11 => tilde(23), Key::F12 => tilde(24), - _ if m.ctrl && !m.command_only() => { + _ if m.ctrl && !mac_cmd_only(m) => { let b = match key { Key::Space | Key::Num2 => 0, Key::OpenBracket | Key::Num3 => 0x1b, @@ -840,15 +831,9 @@ pub fn key_bytes(key: Key, m: Modifiers, mode: TermMode) -> Option> { }) } -trait CommandOnly { - fn command_only(&self) -> bool; -} - -impl CommandOnly for Modifiers { - /// On macOS, Cmd is the app's modifier; Ctrl still produces control codes. - fn command_only(&self) -> bool { - cfg!(target_os = "macos") && self.mac_cmd && !self.ctrl - } +/// On macOS Cmd is the app's modifier while Ctrl still produces control codes. +fn mac_cmd_only(m: Modifiers) -> bool { + cfg!(target_os = "macos") && m.mac_cmd && !m.ctrl } #[cfg(test)] @@ -862,6 +847,9 @@ mod tests { assert_eq!(key_bytes(Key::ArrowUp, none, TermMode::APP_CURSOR).unwrap(), b"\x1bOA"); assert_eq!(key_bytes(Key::ArrowLeft, Modifiers::CTRL, TermMode::empty()).unwrap(), b"\x1b[1;5D"); assert_eq!(key_bytes(Key::C, Modifiers::CTRL, TermMode::empty()).unwrap(), [3]); + // egui reports Ctrl as ctrl + command on Linux/Windows; it must still send a control code. + let ctrl_cmd = Modifiers { ctrl: true, command: true, ..Default::default() }; + assert_eq!(key_bytes(Key::D, ctrl_cmd, TermMode::empty()).unwrap(), [4]); assert_eq!(key_bytes(Key::F5, none, TermMode::empty()).unwrap(), b"\x1b[15~"); assert_eq!(key_bytes(Key::Backspace, none, TermMode::empty()).unwrap(), [0x7f]); assert!(key_bytes(Key::A, none, TermMode::empty()).is_none()); diff --git a/rust/app/src/terminal_themes.rs b/rust/app/src/terminal_themes.rs index f14367a..1c9d274 100644 --- a/rust/app/src/terminal_themes.rs +++ b/rust/app/src/terminal_themes.rs @@ -21,7 +21,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [78, 205, 196, 255], cursor_accent: [28, 28, 28, 255], selection: [78, 205, 196, 76], - ansi: [[28, 28, 28, 255], [239, 83, 80, 255], [102, 187, 106, 255], [255, 167, 38, 255], [66, 165, 245, 255], [171, 71, 188, 255], [78, 205, 196, 255], [228, 228, 228, 255], [85, 85, 85, 255], [255, 112, 67, 255], [129, 199, 132, 255], [255, 204, 2, 255], [100, 181, 246, 255], [206, 147, 216, 255], [128, 222, 234, 255], [255, 255, 255, 255]], + ansi: [ + [28, 28, 28, 255], + [239, 83, 80, 255], + [102, 187, 106, 255], + [255, 167, 38, 255], + [66, 165, 245, 255], + [171, 71, 188, 255], + [78, 205, 196, 255], + [228, 228, 228, 255], + [85, 85, 85, 255], + [255, 112, 67, 255], + [129, 199, 132, 255], + [255, 204, 2, 255], + [100, 181, 246, 255], + [206, 147, 216, 255], + [128, 222, 234, 255], + [255, 255, 255, 255], + ], }, TermTheme { id: "solarized-dark", @@ -31,7 +48,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [147, 161, 161, 255], cursor_accent: [0, 43, 54, 255], selection: [147, 161, 161, 76], - ansi: [[7, 54, 66, 255], [220, 50, 47, 255], [133, 153, 0, 255], [181, 137, 0, 255], [38, 139, 210, 255], [211, 54, 130, 255], [42, 161, 152, 255], [238, 232, 213, 255], [88, 110, 117, 255], [203, 75, 22, 255], [88, 110, 117, 255], [101, 123, 131, 255], [131, 148, 150, 255], [108, 113, 196, 255], [147, 161, 161, 255], [253, 246, 227, 255]], + ansi: [ + [7, 54, 66, 255], + [220, 50, 47, 255], + [133, 153, 0, 255], + [181, 137, 0, 255], + [38, 139, 210, 255], + [211, 54, 130, 255], + [42, 161, 152, 255], + [238, 232, 213, 255], + [88, 110, 117, 255], + [203, 75, 22, 255], + [88, 110, 117, 255], + [101, 123, 131, 255], + [131, 148, 150, 255], + [108, 113, 196, 255], + [147, 161, 161, 255], + [253, 246, 227, 255], + ], }, TermTheme { id: "monokai", @@ -41,7 +75,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [248, 248, 240, 255], cursor_accent: [39, 40, 34, 255], selection: [73, 72, 62, 153], - ansi: [[39, 40, 34, 255], [249, 38, 114, 255], [166, 226, 46, 255], [244, 191, 117, 255], [102, 217, 239, 255], [174, 129, 255, 255], [161, 239, 228, 255], [248, 248, 242, 255], [117, 113, 94, 255], [249, 38, 114, 255], [166, 226, 46, 255], [244, 191, 117, 255], [102, 217, 239, 255], [174, 129, 255, 255], [161, 239, 228, 255], [249, 248, 245, 255]], + ansi: [ + [39, 40, 34, 255], + [249, 38, 114, 255], + [166, 226, 46, 255], + [244, 191, 117, 255], + [102, 217, 239, 255], + [174, 129, 255, 255], + [161, 239, 228, 255], + [248, 248, 242, 255], + [117, 113, 94, 255], + [249, 38, 114, 255], + [166, 226, 46, 255], + [244, 191, 117, 255], + [102, 217, 239, 255], + [174, 129, 255, 255], + [161, 239, 228, 255], + [249, 248, 245, 255], + ], }, TermTheme { id: "nord", @@ -51,7 +102,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [216, 222, 233, 255], cursor_accent: [46, 52, 64, 255], selection: [136, 192, 208, 76], - ansi: [[59, 66, 82, 255], [191, 97, 106, 255], [163, 190, 140, 255], [235, 203, 139, 255], [129, 161, 193, 255], [180, 142, 173, 255], [136, 192, 208, 255], [229, 233, 240, 255], [76, 86, 106, 255], [191, 97, 106, 255], [163, 190, 140, 255], [235, 203, 139, 255], [129, 161, 193, 255], [180, 142, 173, 255], [143, 188, 187, 255], [236, 239, 244, 255]], + ansi: [ + [59, 66, 82, 255], + [191, 97, 106, 255], + [163, 190, 140, 255], + [235, 203, 139, 255], + [129, 161, 193, 255], + [180, 142, 173, 255], + [136, 192, 208, 255], + [229, 233, 240, 255], + [76, 86, 106, 255], + [191, 97, 106, 255], + [163, 190, 140, 255], + [235, 203, 139, 255], + [129, 161, 193, 255], + [180, 142, 173, 255], + [143, 188, 187, 255], + [236, 239, 244, 255], + ], }, TermTheme { id: "dracula", @@ -61,7 +129,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [248, 248, 242, 255], cursor_accent: [40, 42, 54, 255], selection: [68, 71, 90, 153], - ansi: [[33, 34, 44, 255], [255, 85, 85, 255], [80, 250, 123, 255], [241, 250, 140, 255], [189, 147, 249, 255], [255, 121, 198, 255], [139, 233, 253, 255], [248, 248, 242, 255], [98, 114, 164, 255], [255, 110, 110, 255], [105, 255, 148, 255], [255, 255, 165, 255], [214, 172, 255, 255], [255, 146, 223, 255], [164, 255, 255, 255], [255, 255, 255, 255]], + ansi: [ + [33, 34, 44, 255], + [255, 85, 85, 255], + [80, 250, 123, 255], + [241, 250, 140, 255], + [189, 147, 249, 255], + [255, 121, 198, 255], + [139, 233, 253, 255], + [248, 248, 242, 255], + [98, 114, 164, 255], + [255, 110, 110, 255], + [105, 255, 148, 255], + [255, 255, 165, 255], + [214, 172, 255, 255], + [255, 146, 223, 255], + [164, 255, 255, 255], + [255, 255, 255, 255], + ], }, TermTheme { id: "one-dark", @@ -71,7 +156,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [82, 139, 255, 255], cursor_accent: [40, 44, 52, 255], selection: [82, 139, 255, 76], - ansi: [[40, 44, 52, 255], [224, 108, 117, 255], [152, 195, 121, 255], [229, 192, 123, 255], [97, 175, 239, 255], [198, 120, 221, 255], [86, 182, 194, 255], [171, 178, 191, 255], [92, 99, 112, 255], [224, 108, 117, 255], [152, 195, 121, 255], [229, 192, 123, 255], [97, 175, 239, 255], [198, 120, 221, 255], [86, 182, 194, 255], [255, 255, 255, 255]], + ansi: [ + [40, 44, 52, 255], + [224, 108, 117, 255], + [152, 195, 121, 255], + [229, 192, 123, 255], + [97, 175, 239, 255], + [198, 120, 221, 255], + [86, 182, 194, 255], + [171, 178, 191, 255], + [92, 99, 112, 255], + [224, 108, 117, 255], + [152, 195, 121, 255], + [229, 192, 123, 255], + [97, 175, 239, 255], + [198, 120, 221, 255], + [86, 182, 194, 255], + [255, 255, 255, 255], + ], }, TermTheme { id: "gruvbox-dark", @@ -81,7 +183,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [235, 219, 178, 255], cursor_accent: [40, 40, 40, 255], selection: [214, 93, 14, 76], - ansi: [[40, 40, 40, 255], [204, 36, 29, 255], [152, 151, 26, 255], [215, 153, 33, 255], [69, 133, 136, 255], [177, 98, 134, 255], [104, 157, 106, 255], [168, 153, 132, 255], [146, 131, 116, 255], [251, 73, 52, 255], [184, 187, 38, 255], [250, 189, 47, 255], [131, 165, 152, 255], [211, 134, 155, 255], [142, 192, 124, 255], [235, 219, 178, 255]], + ansi: [ + [40, 40, 40, 255], + [204, 36, 29, 255], + [152, 151, 26, 255], + [215, 153, 33, 255], + [69, 133, 136, 255], + [177, 98, 134, 255], + [104, 157, 106, 255], + [168, 153, 132, 255], + [146, 131, 116, 255], + [251, 73, 52, 255], + [184, 187, 38, 255], + [250, 189, 47, 255], + [131, 165, 152, 255], + [211, 134, 155, 255], + [142, 192, 124, 255], + [235, 219, 178, 255], + ], }, TermTheme { id: "tokyo-night", @@ -91,7 +210,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [192, 202, 245, 255], cursor_accent: [26, 27, 38, 255], selection: [41, 46, 66, 230], - ansi: [[21, 22, 30, 255], [247, 118, 142, 255], [158, 206, 106, 255], [224, 175, 104, 255], [122, 162, 247, 255], [187, 154, 247, 255], [125, 207, 255, 255], [169, 177, 214, 255], [65, 72, 104, 255], [247, 118, 142, 255], [158, 206, 106, 255], [224, 175, 104, 255], [122, 162, 247, 255], [187, 154, 247, 255], [125, 207, 255, 255], [192, 202, 245, 255]], + ansi: [ + [21, 22, 30, 255], + [247, 118, 142, 255], + [158, 206, 106, 255], + [224, 175, 104, 255], + [122, 162, 247, 255], + [187, 154, 247, 255], + [125, 207, 255, 255], + [169, 177, 214, 255], + [65, 72, 104, 255], + [247, 118, 142, 255], + [158, 206, 106, 255], + [224, 175, 104, 255], + [122, 162, 247, 255], + [187, 154, 247, 255], + [125, 207, 255, 255], + [192, 202, 245, 255], + ], }, TermTheme { id: "catppuccin-mocha", @@ -101,7 +237,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [245, 224, 220, 255], cursor_accent: [30, 30, 46, 255], selection: [88, 91, 112, 128], - ansi: [[69, 71, 90, 255], [243, 139, 168, 255], [166, 227, 161, 255], [249, 226, 175, 255], [137, 180, 250, 255], [245, 194, 231, 255], [148, 226, 213, 255], [186, 194, 222, 255], [88, 91, 112, 255], [243, 139, 168, 255], [166, 227, 161, 255], [249, 226, 175, 255], [137, 180, 250, 255], [245, 194, 231, 255], [148, 226, 213, 255], [166, 173, 200, 255]], + ansi: [ + [69, 71, 90, 255], + [243, 139, 168, 255], + [166, 227, 161, 255], + [249, 226, 175, 255], + [137, 180, 250, 255], + [245, 194, 231, 255], + [148, 226, 213, 255], + [186, 194, 222, 255], + [88, 91, 112, 255], + [243, 139, 168, 255], + [166, 227, 161, 255], + [249, 226, 175, 255], + [137, 180, 250, 255], + [245, 194, 231, 255], + [148, 226, 213, 255], + [166, 173, 200, 255], + ], }, TermTheme { id: "rose-pine", @@ -111,7 +264,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [82, 79, 103, 255], cursor_accent: [224, 222, 244, 255], selection: [110, 106, 134, 102], - ansi: [[38, 35, 58, 255], [235, 111, 146, 255], [49, 116, 143, 255], [246, 193, 119, 255], [156, 207, 216, 255], [196, 167, 231, 255], [235, 188, 186, 255], [224, 222, 244, 255], [110, 106, 134, 255], [235, 111, 146, 255], [49, 116, 143, 255], [246, 193, 119, 255], [156, 207, 216, 255], [196, 167, 231, 255], [235, 188, 186, 255], [224, 222, 244, 255]], + ansi: [ + [38, 35, 58, 255], + [235, 111, 146, 255], + [49, 116, 143, 255], + [246, 193, 119, 255], + [156, 207, 216, 255], + [196, 167, 231, 255], + [235, 188, 186, 255], + [224, 222, 244, 255], + [110, 106, 134, 255], + [235, 111, 146, 255], + [49, 116, 143, 255], + [246, 193, 119, 255], + [156, 207, 216, 255], + [196, 167, 231, 255], + [235, 188, 186, 255], + [224, 222, 244, 255], + ], }, TermTheme { id: "solarized-light", @@ -121,7 +291,24 @@ pub const THEMES: &[TermTheme] = &[ cursor: [88, 110, 117, 255], cursor_accent: [253, 246, 227, 255], selection: [7, 54, 66, 51], - ansi: [[7, 54, 66, 255], [220, 50, 47, 255], [133, 153, 0, 255], [181, 137, 0, 255], [38, 139, 210, 255], [211, 54, 130, 255], [42, 161, 152, 255], [238, 232, 213, 255], [0, 43, 54, 255], [203, 75, 22, 255], [88, 110, 117, 255], [101, 123, 131, 255], [131, 148, 150, 255], [108, 113, 196, 255], [147, 161, 161, 255], [253, 246, 227, 255]], + ansi: [ + [7, 54, 66, 255], + [220, 50, 47, 255], + [133, 153, 0, 255], + [181, 137, 0, 255], + [38, 139, 210, 255], + [211, 54, 130, 255], + [42, 161, 152, 255], + [238, 232, 213, 255], + [0, 43, 54, 255], + [203, 75, 22, 255], + [88, 110, 117, 255], + [101, 123, 131, 255], + [131, 148, 150, 255], + [108, 113, 196, 255], + [147, 161, 161, 255], + [253, 246, 227, 255], + ], }, ]; diff --git a/rust/app/src/theme.rs b/rust/app/src/theme.rs index 48a9e5e..761cb23 100644 --- a/rust/app/src/theme.rs +++ b/rust/app/src/theme.rs @@ -212,7 +212,12 @@ pub fn install_fonts(ctx: &egui::Context) { /// The first wide-coverage (CJK) system font found on this machine. fn system_fallback_font() -> Option> { let candidates: &[&str] = if cfg!(target_os = "macos") { - &["/System/Library/Fonts/PingFang.ttc", "/System/Library/Fonts/Hiragino Sans GB.ttc", "/Library/Fonts/Arial Unicode.ttf", "/System/Library/Fonts/Supplemental/Arial Unicode.ttf"] + &[ + "/System/Library/Fonts/PingFang.ttc", + "/System/Library/Fonts/Hiragino Sans GB.ttc", + "/Library/Fonts/Arial Unicode.ttf", + "/System/Library/Fonts/Supplemental/Arial Unicode.ttf", + ] } else if cfg!(windows) { &["C:\\Windows\\Fonts\\msyh.ttc", "C:\\Windows\\Fonts\\YuGothM.ttc", "C:\\Windows\\Fonts\\msgothic.ttc", "C:\\Windows\\Fonts\\malgun.ttf"] } else { diff --git a/rust/app/src/transfer.rs b/rust/app/src/transfer.rs index 896529d..024d45b 100644 --- a/rust/app/src/transfer.rs +++ b/rust/app/src/transfer.rs @@ -14,10 +14,25 @@ const STRATEGIES: [(&str, &str); 3] = [("skip", "Skip existing"), ("overwrite", enum Mode { /// `selected` is filled from `preselect` (or every profile) on the first frame. - Export { preselect: Option>, selected: HashSet, pw: String, confirm: String }, - Import { path: PathBuf, pw: String, strategy: &'static str, status: String }, - ExportJson { include_secrets: bool }, - ImportJson { path: PathBuf, strategy: &'static str }, + Export { + preselect: Option>, + selected: HashSet, + pw: String, + confirm: String, + }, + Import { + path: PathBuf, + pw: String, + strategy: &'static str, + status: String, + }, + ExportJson { + include_secrets: bool, + }, + ImportJson { + path: PathBuf, + strategy: &'static str, + }, } enum Done { @@ -38,7 +53,9 @@ impl Transfer { } pub fn import_encrypted(&mut self) { - if let Some(path) = rfd::FileDialog::new().set_title("Import Profiles").add_filter("Nexus Export", &[EXTENSION, "link"]).add_filter("All files", &["*"]).pick_file() { + if let Some(path) = + rfd::FileDialog::new().set_title("Import Profiles").add_filter("Nexus Export", &[EXTENSION, "link"]).add_filter("All files", &["*"]).pick_file() + { self.mode = Some(Mode::Import { path, pw: String::new(), strategy: "skip", status: String::new() }); } } @@ -112,7 +129,11 @@ impl Transfer { let name = if p.display_name.is_empty() { &p.host } else { &p.display_name }; let label = format!("{name} ({})", p.protocol.id()); if ui.checkbox(&mut on, label).changed() { - if on { selected.insert(p.id.clone()) } else { selected.remove(&p.id) }; + if on { + selected.insert(p.id.clone()) + } else { + selected.remove(&p.id) + }; } } }); @@ -131,11 +152,18 @@ impl Transfer { s.error("Passwords do not match."); } else if selected.is_empty() { s.error("Please select at least one profile."); - } else if let Some(path) = rfd::FileDialog::new().set_title("Export Profiles").set_file_name(format!("nexus-profiles.{EXTENSION}")).add_filter("Nexus Export", &[EXTENSION]).save_file() { + } else if let Some(path) = rfd::FileDialog::new() + .set_title("Export Profiles") + .set_file_name(format!("nexus-profiles.{EXTENSION}")) + .add_filter("Nexus Export", &[EXTENSION]) + .save_file() + { // Keep the sidebar's order. let ids: Vec = s.profiles.iter().filter(|p| selected.contains(&p.id)).map(|p| p.id.clone()).collect(); let (svc, pw) = (s.svc.clone(), pw.clone()); - start = Some(Box::new(move || svc.export_encrypted(&ids, &pw, &path).map(|p| Done::Exported(ids.len(), p)).map_err(|e| format!("Export failed: {e}")))); + start = Some(Box::new(move || { + svc.export_encrypted(&ids, &pw, &path).map(|p| Done::Exported(ids.len(), p)).map_err(|e| format!("Export failed: {e}")) + })); } } } @@ -169,10 +197,18 @@ impl Transfer { ui.label(format!("Exports all {} profile(s) as plain JSON.", s.profiles.len())); ui.checkbox(include_secrets, "Include secrets (proxy passwords, TOTP secrets)"); if *include_secrets { - ui.label(RichText::new(format!("{} The file will contain secrets in plain text. Prefer the encrypted export.", icon::WARNING)).color(t.warning).size(12.0)); + ui.label( + RichText::new(format!("{} The file will contain secrets in plain text. Prefer the encrypted export.", icon::WARNING)) + .color(t.warning) + .size(12.0), + ); } if buttons(ui, false, icon::FILE_ARROW_UP, "Export", &mut close) - && let Some(path) = rfd::FileDialog::new().set_title("Export Profiles (JSON)").set_file_name("nexus-profiles.json").add_filter("JSON", &["json"]).save_file() + && let Some(path) = rfd::FileDialog::new() + .set_title("Export Profiles (JSON)") + .set_file_name("nexus-profiles.json") + .add_filter("JSON", &["json"]) + .save_file() { match s.svc.export_json(*include_secrets).map_err(|e| e.to_string()).and_then(|j| std::fs::write(&path, j).map_err(|e| e.to_string())) { Ok(()) => { @@ -189,7 +225,8 @@ impl Transfer { file_line(ui, path); strategy_combo(ui, strategy); if buttons(ui, false, icon::FILE_ARROW_DOWN, "Import", &mut close) { - match std::fs::read_to_string(&path).map_err(|e| e.to_string()).and_then(|j| s.svc.import_json(&j, strategy).map_err(|e| e.to_string())) { + match std::fs::read_to_string(&path).map_err(|e| e.to_string()).and_then(|j| s.svc.import_json(&j, strategy).map_err(|e| e.to_string())) + { Ok(n) => { s.success(format!("Imported {n} profile(s)")); s.request(Request::ReloadProfiles); diff --git a/rust/app/src/triggers.rs b/rust/app/src/triggers.rs index 864f404..eeac228 100644 --- a/rust/app/src/triggers.rs +++ b/rust/app/src/triggers.rs @@ -117,7 +117,10 @@ impl Triggers { } let lines = complete_lines(self.buffers.entry(session.to_owned()).or_default(), data); let mut highlight = false; - let label = s.pane(session).map(|p| if p.info.tab_name.is_empty() { p.info.profile_name.clone() } else { p.info.tab_name.clone() }).unwrap_or_else(|| session.to_owned()); + let label = s + .pane(session) + .map(|p| if p.info.tab_name.is_empty() { p.info.profile_name.clone() } else { p.info.tab_name.clone() }) + .unwrap_or_else(|| session.to_owned()); let mut fired: Vec<(String, String)> = vec![]; for line in &lines { let clean = strip_ansi(line); @@ -191,7 +194,11 @@ impl Triggers { ui.vertical(|ui| { ui.label(RichText::new(if r.name.is_empty() { &r.pattern } else { &r.name }).font(theme::semibold(13.0))); let action = ACTIONS.iter().find(|a| a.0 == r.action).map_or(r.action.as_str(), |a| a.1); - let meta = format!("{} · {action} · {}", if r.is_regex { "Regex" } else { "Text" }, if r.case_sensitive { "Case-sensitive" } else { "Case-insensitive" }); + let meta = format!( + "{} · {action} · {}", + if r.is_regex { "Regex" } else { "Text" }, + if r.case_sensitive { "Case-sensitive" } else { "Case-insensitive" } + ); ui.label(RichText::new(meta).size(11.5).color(t.text_dim)); }); ui.label(RichText::new(&r.pattern).font(theme::mono(12.0)).color(if r.is_regex && r.re.is_none() { t.danger } else { t.text })); diff --git a/rust/app/src/views.rs b/rust/app/src/views.rs index 57c46b4..5b718c2 100644 --- a/rust/app/src/views.rs +++ b/rust/app/src/views.rs @@ -14,15 +14,6 @@ pub enum View { Vnc(Box), } -impl View { - pub fn terminal(&mut self) -> Option<&mut TermView> { - match self { - View::Terminal(t) => Some(t), - _ => None, - } - } -} - /// Protocol badge colour (the web build's --protocol-* tokens). pub fn protocol_color(p: nexus_core::models::Protocol) -> egui::Color32 { use nexus_core::models::Protocol::*; diff --git a/rust/app/src/vnc_view.rs b/rust/app/src/vnc_view.rs index 8776cb5..e8a911f 100644 --- a/rust/app/src/vnc_view.rs +++ b/rust/app/src/vnc_view.rs @@ -254,7 +254,11 @@ impl VncView { } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.add_enabled_ui(self.ready, |ui| { - let (g, tip) = if self.scale_to_fit { (icon::CORNERS_IN, "Scaled to fit (click for 1:1)") } else { (icon::CORNERS_OUT, "1:1 (click to scale to fit)") }; + let (g, tip) = if self.scale_to_fit { + (icon::CORNERS_IN, "Scaled to fit (click for 1:1)") + } else { + (icon::CORNERS_OUT, "1:1 (click to scale to fit)") + }; if widgets::icon_button(ui, g, tip).clicked() { self.scale_to_fit = !self.scale_to_fit; } @@ -264,7 +268,10 @@ impl VncView { let _ = c.request_full_update(); } if !self.view_only { - if widgets::button(ui, icon::CLIPBOARD_TEXT, "Paste Clipboard").on_hover_text("Send the local clipboard to the remote desktop").clicked() { + if widgets::button(ui, icon::CLIPBOARD_TEXT, "Paste Clipboard") + .on_hover_text("Send the local clipboard to the remote desktop") + .clicked() + { match arboard::Clipboard::new().and_then(|mut c| c.get_text()) { Ok(text) if !text.is_empty() => { self.paste(s, &text); @@ -293,9 +300,7 @@ impl VncView { let focused = resp.has_focus(); if focused { ui.painter().rect_stroke(img, 0.0, egui::Stroke::new(1.0, Tokens::get(ui.ctx()).accent.gamma_multiply(0.6)), egui::StrokeKind::Outside); - ui.memory_mut(|m| { - m.set_focus_lock_filter(id, egui::EventFilter { tab: true, horizontal_arrows: true, vertical_arrows: true, escape: true }) - }); + ui.memory_mut(|m| m.set_focus_lock_filter(id, egui::EventFilter { tab: true, horizontal_arrows: true, vertical_arrows: true, escape: true })); } else if self.had_focus { self.release_all(); } @@ -344,7 +349,8 @@ impl VncView { for e in events { match e { Event::Key { key, pressed, modifiers, .. } => { - let sym = named_keysym(key).or_else(|| (modifiers.ctrl || modifiers.alt || modifiers.mac_cmd).then(|| chord_keysym(key, modifiers.shift)).flatten()); + let sym = named_keysym(key) + .or_else(|| (modifiers.ctrl || modifiers.alt || modifiers.mac_cmd).then(|| chord_keysym(key, modifiers.shift)).flatten()); if let Some(sym) = sym { self.key(sym, pressed); } diff --git a/rust/core/src/clients/github.rs b/rust/core/src/clients/github.rs index 462e7a5..4c41275 100644 --- a/rust/core/src/clients/github.rs +++ b/rust/core/src/clients/github.rs @@ -70,11 +70,7 @@ struct ApiPerson { impl Client { /// `base_url` is the API root, e.g. `https://api.github.com` or `https://ghe.example.com/api/v3`. pub fn new(base_url: &str, token: &str) -> Client { - let agent = ureq::Agent::config_builder() - .timeout_global(Some(Duration::from_secs(30))) - .http_status_as_error(false) - .build() - .into(); + let agent = ureq::Agent::config_builder().timeout_global(Some(Duration::from_secs(30))).http_status_as_error(false).build().into(); Client { base_url: base_url.trim_end_matches('/').to_string(), token: token.to_string(), agent } } @@ -89,15 +85,12 @@ impl Client { /// Confirms the token works and returns the authenticated login. pub fn validate_token(&self) -> crate::Result { - let mut resp = self - .get(&format!("{}/user", self.base_url)) - .map_err(|e| format!("contacting GitHub API: {e}"))?; + let mut resp = self.get(&format!("{}/user", self.base_url)).map_err(|e| format!("contacting GitHub API: {e}"))?; let status = resp.status().as_u16(); if status != 200 { return Err(format!("token validation failed (HTTP {status})").into()); } - let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES) - .map_err(|e| format!("reading user response: {e}"))?; + let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES).map_err(|e| format!("reading user response: {e}"))?; #[derive(Deserialize)] struct User { #[serde(default)] @@ -114,35 +107,21 @@ impl Client { /// Like `fetch_repos`, reporting commit-enrichment progress as `(done, total)`; it is called /// from worker threads. - pub fn fetch_repos_with_progress( - &self, - progress: impl Fn(usize, usize) + Sync, - ) -> crate::Result> { + pub fn fetch_repos_with_progress(&self, progress: impl Fn(usize, usize) + Sync) -> crate::Result> { let mut all: Vec = Vec::new(); let mut page = 1; loop { - let url = format!( - "{}/user/repos?per_page=100&page={page}&sort=pushed&direction=desc", - self.base_url - ); + let url = format!("{}/user/repos?per_page=100&page={page}&sort=pushed&direction=desc", self.base_url); let mut resp = self.get(&url).map_err(|e| format!("fetching repos page {page}: {e}"))?; - let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES) - .map_err(|e| format!("reading response body: {e}"))?; + let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES).map_err(|e| format!("reading response body: {e}"))?; let status = resp.status().as_u16(); if status == 401 || status == 403 { - return Err( - format!("authentication failed (HTTP {status}) — check your access token").into() - ); + return Err(format!("authentication failed (HTTP {status}) — check your access token").into()); } if status != 200 { - return Err(format!( - "GitHub API returned HTTP {status}: {}", - String::from_utf8_lossy(&body) - ) - .into()); + return Err(format!("GitHub API returned HTTP {status}: {}", String::from_utf8_lossy(&body)).into()); } - let batch: Vec = - serde_json::from_slice(&body).map_err(|e| format!("decoding repos: {e}"))?; + let batch: Vec = serde_json::from_slice(&body).map_err(|e| format!("decoding repos: {e}"))?; if batch.is_empty() { break; } @@ -161,7 +140,7 @@ impl Client { let updated = parse_time(r.updated_at.as_deref()).map_err(|e| format!("decoding repos: {e}"))?; parsed.push((r, pushed, updated)); } - parsed.sort_by(|a, b| b.1.cmp(&a.1)); + parsed.sort_by_key(|p| std::cmp::Reverse(p.1)); let mut result: Vec = parsed .into_iter() @@ -234,14 +213,9 @@ impl Client { return Err(format!("authentication failed (HTTP {status})").into()); } if status != 200 { - return Err(format!( - "GitHub API returned HTTP {status}: {}", - read_error_body(resp.body_mut().as_reader()) - ) - .into()); + return Err(format!("GitHub API returned HTTP {status}: {}", read_error_body(resp.body_mut().as_reader())).into()); } - let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES) - .map_err(|e| format!("reading commit response: {e}"))?; + let body = read_limited(resp.body_mut().as_reader(), MAX_JSON_BODY_BYTES).map_err(|e| format!("reading commit response: {e}"))?; #[derive(Deserialize)] struct Payload { @@ -284,25 +258,14 @@ impl Client { return Err(format!("authentication failed (HTTP {status}) — check your access token").into()); } if status != 200 { - return Err(format!( - "GitHub API returned HTTP {status}: {}", - read_error_body(resp.body_mut().as_reader()) - ) - .into()); + return Err(format!("GitHub API returned HTTP {status}: {}", read_error_body(resp.body_mut().as_reader())).into()); } - let length = resp - .headers() - .get("content-length") - .and_then(|v| v.to_str().ok()) - .and_then(|v| v.trim().parse::().ok()); + let length = resp.headers().get("content-length").and_then(|v| v.to_str().ok()).and_then(|v| v.trim().parse::().ok()); if let Some(length) = length.filter(|&l| l > MAX_ARCHIVE_BYTES) { - return Err( - format!("archive is too large: {length} bytes exceeds {MAX_ARCHIVE_BYTES} bytes").into() - ); + return Err(format!("archive is too large: {length} bytes exceeds {MAX_ARCHIVE_BYTES} bytes").into()); } let mut file = std::fs::File::create(dest_path).map_err(|e| format!("creating file: {e}"))?; - copy_limited_archive(&mut file, resp.body_mut().as_reader(), MAX_ARCHIVE_BYTES) - .map_err(|e| format!("writing archive: {e}"))?; + copy_limited_archive(&mut file, resp.body_mut().as_reader(), MAX_ARCHIVE_BYTES).map_err(|e| format!("writing archive: {e}"))?; Ok(()) } @@ -446,9 +409,7 @@ mod tests { let log = seen.clone(); std::thread::spawn(move || { for req in server.incoming_requests() { - let header = |name: &'static str| { - req.headers().iter().find(|h| h.field.equiv(name)).map(|h| h.value.to_string()) - }; + let header = |name: &'static str| req.headers().iter().find(|h| h.field.equiv(name)).map(|h| h.value.to_string()); log.lock().unwrap().push((req.url().to_string(), header("Authorization"), header("User-Agent"))); let (status, body) = handler(req.url()); let _ = req.respond(tiny_http::Response::from_string(body).with_status_code(status)); @@ -475,7 +436,7 @@ mod tests { serde_json::json!({ "full_name": format!("me/r{n}"), "name": format!("r{n}"), "description": null, "html_url": format!("https://example/me/r{n}"), "default_branch": if n == 0 { "" } else { "main" }, - "private": n % 2 == 0, "fork": false, "archived": false, "language": null, + "private": n.is_multiple_of(2), "fork": false, "archived": false, "language": null, "stargazers_count": n, "pushed_at": pushed, "updated_at": "2024-01-01T00:00:00Z", "owner": {"login": "me"} }) @@ -485,9 +446,7 @@ mod tests { fn fetch_repos_paginates_sorts_and_enriches() { let (base, seen) = serve(|url| { if url.starts_with("/user/repos") && url.contains("page=1&") { - let repos: Vec<_> = (0..100) - .map(|n| repo_json(n, &format!("2024-02-{:02}T00:00:00Z", 1 + n % 28))) - .collect(); + let repos: Vec<_> = (0..100).map(|n| repo_json(n, &format!("2024-02-{:02}T00:00:00Z", 1 + n % 28))).collect(); (200, serde_json::to_string(&repos).unwrap()) } else if url.starts_with("/user/repos") { (200, serde_json::to_string(&[repo_json(100, "2025-06-01T12:00:00Z")]).unwrap()) @@ -500,9 +459,7 @@ mod tests { } }); let calls = Mutex::new(Vec::new()); - let repos = Client::new(&base, "t") - .fetch_repos_with_progress(|done, total| calls.lock().unwrap().push((done, total))) - .unwrap(); + let repos = Client::new(&base, "t").fetch_repos_with_progress(|done, total| calls.lock().unwrap().push((done, total))).unwrap(); assert_eq!(repos.len(), 101); assert_eq!(repos[0].full_name, "me/r100"); assert_eq!(repos[0].pushed_at, "2025-06-01T12:00:00Z"); diff --git a/rust/core/src/clients/irc.rs b/rust/core/src/clients/irc.rs index 3fdf209..833ac4b 100644 --- a/rust/core/src/clients/irc.rs +++ b/rust/core/src/clients/irc.rs @@ -478,14 +478,14 @@ impl Client { st.connected = false; r }; - if let Err(e) = result { - if !was_closing { - let msg = e.to_string(); - self.emit(Event { kind: "error".into(), text: msg.clone(), ..Default::default() }); - (self.cfg.on_error)(msg.clone()); - if let Some(ready) = ready { - let _ = ready.send(Err(msg)); - } + if let Err(e) = result + && !was_closing + { + let msg = e.to_string(); + self.emit(Event { kind: "error".into(), text: msg.clone(), ..Default::default() }); + (self.cfg.on_error)(msg.clone()); + if let Some(ready) = ready { + let _ = ready.send(Err(msg)); } } if was_connected { @@ -498,8 +498,7 @@ impl Client { Some(connector) => connector()?, None => { let addr = format!("{}:{}", self.cfg.server, self.cfg.port); - let tcp = dial(&self.cfg.server, self.cfg.port, self.cfg.connect_timeout) - .map_err(|e| format!("dial tcp {addr}: {e}"))?; + let tcp = dial(&self.cfg.server, self.cfg.port, self.cfg.connect_timeout).map_err(|e| format!("dial tcp {addr}: {e}"))?; tcp.set_read_timeout(Some(READ_POLL))?; tcp.set_write_timeout(Some(self.cfg.connect_timeout))?; let _ = tcp.set_nodelay(true); @@ -571,10 +570,7 @@ impl Client { if ping_sent && self.st().last_pong.elapsed() > PING_DELAY + PING_TIMEOUT { return Err("timed out waiting for a requested PING response".into()); } - let id = std::time::SystemTime::now() - .duration_since(std::time::UNIX_EPOCH) - .unwrap_or_default() - .as_nanos(); + let id = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap_or_default().as_nanos(); write(&mut stream, &encode("PING", &[&id.to_string()]))?; last_ping = Instant::now(); ping_sent = true; @@ -637,11 +633,7 @@ impl Client { } "322" => { if m.params.len() >= 4 { - let room = RoomInfo { - name: m.param(1).into(), - users: m.param(2).parse().unwrap_or(0), - topic: m.param(3).into(), - }; + let room = RoomInfo { name: m.param(1).into(), users: m.param(2).parse().unwrap_or(0), topic: m.param(3).into() }; let mut st = self.st(); if st.list_rooms.len() < MAX_ROOM_LIST_ENTRIES { st.list_rooms.push(room); @@ -668,11 +660,7 @@ impl Client { st.ready.take() }; let host = if self.cfg.server.contains(':') { format!("[{}]", self.cfg.server) } else { self.cfg.server.clone() }; - self.emit(Event { - kind: "system".into(), - text: format!("Connected to {host}:{}", self.cfg.port), - ..Default::default() - }); + self.emit(Event { kind: "system".into(), text: format!("Connected to {host}:{}", self.cfg.port), ..Default::default() }); self.identify_with_nickserv(); if !self.cfg.auto_join_rooms.is_empty() { let _ = self.send("JOIN", &[&self.cfg.auto_join_rooms.join(",")]); @@ -747,11 +735,7 @@ impl Client { let from = m.source.clone(); if !from.is_empty() { let mut st = self.st(); - let ch = st.channels.entry(fold(&room)).or_insert_with(|| Channel { - name: room.clone(), - topic: String::new(), - users: Vec::new(), - }); + let ch = st.channels.entry(fold(&room)).or_insert_with(|| Channel { name: room.clone(), topic: String::new(), users: Vec::new() }); add_user(ch, &from); } if self.is_me(&from) { @@ -856,14 +840,7 @@ impl Client { if let Some(ch) = self.st().channels.get_mut(&fold(room)) { ch.topic = topic.to_string(); } - self.emit(Event { - kind: "topic".into(), - target: room.into(), - from: m.source.clone(), - text: topic.into(), - topic: topic.into(), - ..Default::default() - }); + self.emit(Event { kind: "topic".into(), target: room.into(), from: m.source.clone(), text: topic.into(), topic: topic.into(), ..Default::default() }); self.emit_rooms(); } @@ -1060,10 +1037,7 @@ mod tests { fn remember_room_membership_tracks_confirmed_channels() { let updates = Arc::new(Mutex::new(Vec::new())); let log = updates.clone(); - let client = Client::new(Config { - on_rooms_changed: Arc::new(move |rooms| log.lock().unwrap().push(rooms.join(","))), - ..Default::default() - }); + let client = Client::new(Config { on_rooms_changed: Arc::new(move |rooms| log.lock().unwrap().push(rooms.join(","))), ..Default::default() }); client.remember_room_membership("#One", true); client.remember_room_membership("#two", true); client.remember_room_membership("#one", true); @@ -1095,11 +1069,7 @@ mod tests { fn event_serialises_like_go() { let e = Event { kind: "users".into(), target: "#a".into(), timestamp: 5, ..Default::default() }; assert_eq!(serde_json::to_string(&e).unwrap(), r##"{"type":"users","target":"#a","timestamp":5}"##); - let e = Event { - kind: "rooms".into(), - rooms: vec![RoomInfo { name: "#a".into(), topic: String::new(), users: 0 }], - ..Default::default() - }; + let e = Event { kind: "rooms".into(), rooms: vec![RoomInfo { name: "#a".into(), topic: String::new(), users: 0 }], ..Default::default() }; assert_eq!(serde_json::to_string(&e).unwrap(), r##"{"type":"rooms","timestamp":0,"rooms":[{"name":"#a"}]}"##); } @@ -1236,10 +1206,7 @@ mod tests { srv.expect("NAMES #a"); client.handle_input("#a", "/mode #a +i").unwrap(); srv.expect("MODE #a +i"); - assert_eq!( - client.handle_input(SERVER_TARGET, "hi").unwrap_err().to_string(), - "select a room or use /msg " - ); + assert_eq!(client.handle_input(SERVER_TARGET, "hi").unwrap_err().to_string(), "select a room or use /msg "); assert_eq!(client.handle_input("#a", "/msg bob").unwrap_err().to_string(), "/msg requires a target and message"); client.handle_input("", "/help").unwrap(); assert!(wait_for(&rx, |e| e.kind == "system").text.starts_with("Commands: /join")); @@ -1269,10 +1236,7 @@ mod tests { fn remote_close_reports_error_and_close() { let errors = Arc::new(Mutex::new(Vec::new())); let log = errors.clone(); - let (client, listener, rx, closes) = harness(Config { - on_error: Arc::new(move |e| log.lock().unwrap().push(e)), - ..Default::default() - }); + let (client, listener, rx, closes) = harness(Config { on_error: Arc::new(move |e| log.lock().unwrap().push(e)), ..Default::default() }); let c = client.clone(); let connecting = std::thread::spawn(move || c.connect()); let mut srv = accept(&listener); diff --git a/rust/core/src/clients/local.rs b/rust/core/src/clients/local.rs index eadbfed..74ae572 100644 --- a/rust/core/src/clients/local.rs +++ b/rust/core/src/clients/local.rs @@ -9,9 +9,7 @@ use std::sync::{Arc, Mutex}; pub fn default_shell() -> String { if cfg!(windows) { - let ps = std::env::var_os("SystemRoot") - .map(|r| PathBuf::from(r).join(r"System32\WindowsPowerShell\v1.0\powershell.exe")) - .filter(|p| p.exists()); + let ps = std::env::var_os("SystemRoot").map(|r| PathBuf::from(r).join(r"System32\WindowsPowerShell\v1.0\powershell.exe")).filter(|p| p.exists()); return ps.map_or_else(|| "cmd.exe".into(), |p| p.to_string_lossy().into()); } std::env::var("SHELL").ok().filter(|s| !s.is_empty()).unwrap_or_else(|| "/bin/sh".into()) diff --git a/rust/core/src/clients/mod.rs b/rust/core/src/clients/mod.rs index 1fac69d..837e41f 100644 --- a/rust/core/src/clients/mod.rs +++ b/rust/core/src/clients/mod.rs @@ -17,9 +17,7 @@ use std::sync::{Arc, OnceLock}; /// The shared tokio runtime for async protocol stacks (russh). Blocking clients use threads. pub fn rt() -> &'static tokio::runtime::Runtime { static RT: OnceLock = OnceLock::new(); - RT.get_or_init(|| { - tokio::runtime::Builder::new_multi_thread().worker_threads(2).enable_all().thread_name("nexus-net").build().expect("tokio runtime") - }) + RT.get_or_init(|| tokio::runtime::Builder::new_multi_thread().worker_threads(2).enable_all().thread_name("nexus-net").build().expect("tokio runtime")) } pub type DataFn = Arc; @@ -81,11 +79,7 @@ impl TcpConn { TcpConn { stream: std::sync::Mutex::new(None), on_error, on_close } } - pub(crate) fn connect( - self: &Arc, - stream: std::net::TcpStream, - mut on_read: impl FnMut(&[u8]) + Send + 'static, - ) -> crate::Result<()> { + pub(crate) fn connect(self: &Arc, stream: std::net::TcpStream, mut on_read: impl FnMut(&[u8]) + Send + 'static) -> crate::Result<()> { let mut reader = stream.try_clone()?; { let mut guard = self.stream.lock().unwrap(); @@ -148,10 +142,7 @@ pub(crate) fn is_latin1(encoding: &str) -> bool { /// UTF-8 → ISO 8859-1. Like Go's charmap encoder, input that isn't valid UTF-8 or holds a /// character above U+00FF is returned unchanged. pub(crate) fn utf8_to_latin1(data: &[u8]) -> Vec { - std::str::from_utf8(data) - .ok() - .and_then(|s| s.chars().map(|c| u8::try_from(c).ok()).collect()) - .unwrap_or_else(|| data.to_vec()) + std::str::from_utf8(data).ok().and_then(|s| s.chars().map(|c| u8::try_from(c).ok()).collect()).unwrap_or_else(|| data.to_vec()) } /// ISO 8859-1 → UTF-8 (every byte maps to the code point of the same value). diff --git a/rust/core/src/clients/raw.rs b/rust/core/src/clients/raw.rs index a003afa..f289c5d 100644 --- a/rust/core/src/clients/raw.rs +++ b/rust/core/src/clients/raw.rs @@ -262,10 +262,7 @@ pub fn parse_escaped(s: &str) -> crate::Result> { /// "latin1" / "latin-1", otherwise UTF-8 with invalid bytes replaced by U+FFFD. pub fn to_display_encoding(data: &[u8], encoding: &str) -> String { match encoding.trim().to_ascii_lowercase().as_str() { - "ascii" => data - .iter() - .map(|&b| if (0x20..=0x7e).contains(&b) || matches!(b, b'\n' | b'\r' | b'\t') { b as char } else { '.' }) - .collect(), + "ascii" => data.iter().map(|&b| if (0x20..=0x7e).contains(&b) || matches!(b, b'\n' | b'\r' | b'\t') { b as char } else { '.' }).collect(), e if super::is_latin1(e) => super::latin1_to_utf8(data), _ => String::from_utf8_lossy(data).into_owned(), } @@ -285,13 +282,8 @@ mod tests { #[test] fn parse_hex_string_cases() { - let ok: [(&str, &[u8]); 5] = [ - ("48656c6c6f", b"Hello"), - ("48 65 6c 6c 6f", b"Hello"), - ("0x48 0x65 0x6c 0x6c 0x6f", b"Hello"), - ("0000", &[0, 0]), - ("ff ff", &[0xff, 0xff]), - ]; + let ok: [(&str, &[u8]); 5] = + [("48656c6c6f", b"Hello"), ("48 65 6c 6c 6f", b"Hello"), ("0x48 0x65 0x6c 0x6c 0x6f", b"Hello"), ("0000", &[0, 0]), ("ff ff", &[0xff, 0xff])]; for (input, want) in ok { assert_eq!(parse_hex_string(input).unwrap(), want, "{input:?}"); } diff --git a/rust/core/src/clients/serial.rs b/rust/core/src/clients/serial.rs index 977f925..bee042f 100644 --- a/rust/core/src/clients/serial.rs +++ b/rust/core/src/clients/serial.rs @@ -38,12 +38,7 @@ pub fn list_ports() -> Vec { impl SerialClient { pub fn new(profile: &Profile, cb: Callbacks) -> SerialClient { SerialClient { - inner: Arc::new(Inner { - path: profile.host.trim().to_string(), - cfg: profile.serial.clone().unwrap_or_default(), - cb, - port: Mutex::new(None), - }), + inner: Arc::new(Inner { path: profile.host.trim().to_string(), cfg: profile.serial.clone().unwrap_or_default(), cb, port: Mutex::new(None) }), } } @@ -292,9 +287,15 @@ mod tests { let (etx, erx) = mpsc::channel(); let (ctx, crx) = mpsc::channel(); let cb = Callbacks { - on_data: Arc::new(move |d| { let _ = dtx.send(d.to_vec()); }), - on_error: Arc::new(move |e| { let _ = etx.send(e); }), - on_close: Arc::new(move || { let _ = ctx.send(()); }), + on_data: Arc::new(move |d| { + let _ = dtx.send(d.to_vec()); + }), + on_error: Arc::new(move |e| { + let _ = etx.send(e); + }), + on_close: Arc::new(move || { + let _ = ctx.send(()); + }), }; let profile = Profile { host: pty.path.clone(), serial: Some(cfg), ..Profile::new(models::Protocol::Serial) }; (SerialClient::new(&profile, cb), drx, erx, crx) diff --git a/rust/core/src/clients/sftp.rs b/rust/core/src/clients/sftp.rs index 5b856c9..886313a 100644 --- a/rust/core/src/clients/sftp.rs +++ b/rust/core/src/clients/sftp.rs @@ -31,19 +31,18 @@ pub struct Progress { } fn mode_string(m: &russh_sftp::protocol::FileAttributes) -> String { - let kind = if m.is_dir() { 'd' } else if m.is_symlink() { 'L' } else { '-' }; + let kind = if m.is_dir() { + 'd' + } else if m.is_symlink() { + 'L' + } else { + '-' + }; format!("{kind}{}", m.permissions()) } fn entry(name: String, path: String, m: &russh_sftp::protocol::FileAttributes) -> FileEntry { - FileEntry { - name, - path, - size: m.size.unwrap_or(0), - is_dir: m.is_dir(), - mode: mode_string(m), - modified_at: m.mtime.map_or(0, |t| t as i64 * 1000), - } + FileEntry { name, path, size: m.size.unwrap_or(0), is_dir: m.is_dir(), mode: mode_string(m), modified_at: m.mtime.map_or(0, |t| t as i64 * 1000) } } /// `path.Join` for remote (always '/') paths. @@ -60,10 +59,8 @@ pub fn join(dir: &str, name: &str) -> String { /// Directories first, then case-insensitive name order. pub fn list(s: &Arc, path: &str) -> Result> { let rd = rt().block_on(s.read_dir(path)).map_err(|e| format!("sftpclient: failed to list {path}: {e}"))?; - let mut out: Vec = rd - .filter(|e| e.file_name() != "." && e.file_name() != "..") - .map(|e| entry(e.file_name(), join(path, &e.file_name()), &e.metadata())) - .collect(); + let mut out: Vec = + rd.filter(|e| e.file_name() != "." && e.file_name() != "..").map(|e| entry(e.file_name(), join(path, &e.file_name()), &e.metadata())).collect(); out.sort_by(|a, b| b.is_dir.cmp(&a.is_dir).then_with(|| a.name.to_lowercase().cmp(&b.name.to_lowercase()))); Ok(out) } diff --git a/rust/core/src/clients/ssh.rs b/rust/core/src/clients/ssh.rs index 839ccb6..da05f8a 100644 --- a/rust/core/src/clients/ssh.rs +++ b/rust/core/src/clients/ssh.rs @@ -3,8 +3,8 @@ //! port forwards, agent forwarding and SFTP. A port of internal/sshclient + sftpclient. use super::{Callbacks, rt}; -use crate::models::{ForwardDirection, JumpHost, PortForward, Profile, SshConfig}; use crate::Result; +use crate::models::{ForwardDirection, JumpHost, PortForward, Profile, SshConfig}; use base64::Engine; use russh::client::{self, Handle, Msg}; use russh::keys::{HashAlg, PrivateKeyWithHashAlg, PublicKey}; @@ -160,12 +160,9 @@ async fn connect_agent() -> Result { fn is_totp_prompt(q: &str) -> bool { let q = q.to_lowercase(); - [ - "verification code", "otp", "one-time", "one time", "two-factor", "two factor", "2fa", "totp", "authenticator", - "security code", "6-digit", "6 digit", - ] - .iter() - .any(|k| q.contains(k)) + ["verification code", "otp", "one-time", "one time", "two-factor", "two factor", "2fa", "totp", "authenticator", "security code", "6-digit", "6 digit"] + .iter() + .any(|k| q.contains(k)) } /// RFC 6238 code (SHA-1, 6 digits, 30 s) for a base32 secret, normalised as pquerna/otp does. @@ -448,7 +445,9 @@ impl SshClient { let host = host.to_string(); let listener = std::net::TcpListener::bind("127.0.0.1:0")?; let addr = listener.local_addr()?; - let ch = rt().block_on(h.channel_open_direct_tcpip(host.clone(), port as u32, "127.0.0.1", 0)).map_err(|e| format!("SSH tunnel to {host}:{port} failed: {e}"))?; + let ch = rt() + .block_on(h.channel_open_direct_tcpip(host.clone(), port as u32, "127.0.0.1", 0)) + .map_err(|e| format!("SSH tunnel to {host}:{port} failed: {e}"))?; let client = std::net::TcpStream::connect(addr)?; let (server, _) = listener.accept()?; server.set_nonblocking(true)?; @@ -488,7 +487,11 @@ impl SshClient { let methods = if sc.auth_methods.is_empty() { SshConfig::defaults().auth_methods } else { sc.auth_methods.clone() }; let key = if methods.iter().any(|m| m == "publickey") && !sc.private_key_path.is_empty() { let (path, pp) = (sc.private_key_path.clone(), passphrase.clone()); - Some(blocking(move || load_private_key(&path, pp.as_ref())).await.map_err(|e| format!("failed to build auth methods: failed to parse private key {}: {e}", sc.private_key_path))?) + Some( + blocking(move || load_private_key(&path, pp.as_ref())) + .await + .map_err(|e| format!("failed to build auth methods: failed to parse private key {}: {e}", sc.private_key_path))?, + ) } else { None }; @@ -498,17 +501,16 @@ impl SshClient { self.handshake(tcp, &p.host, port, &check).await? } else { let (prev, h) = self.via_jumps(&sc, &check, passphrase.clone(), timeout).await?; - let ch = prev.channel_open_direct_tcpip(p.host.clone(), port as u32, "127.0.0.1", 0).await.map_err(|e| format!("failed to dial target {}:{port} through jump hosts: {e}", p.host))?; + let ch = prev + .channel_open_direct_tcpip(p.host.clone(), port as u32, "127.0.0.1", 0) + .await + .map_err(|e| format!("failed to dial target {}:{port} through jump hosts: {e}", p.host))?; self.hops.lock().unwrap().extend(h); self.hops.lock().unwrap().push(prev); self.handshake(ch.into_stream(), &p.host, port, &check).await? }; - authenticate( - &mut handle, - Auth { user: &p.username, methods, agent: sc.agent_auth, key, password, totp_secret: sc.totp_secret.clone() }, - ) - .await?; + authenticate(&mut handle, Auth { user: &p.username, methods, agent: sc.agent_auth, key, password, totp_secret: sc.totp_secret.clone() }).await?; let handle = Arc::new(handle); if !shell { *self.handle.lock().unwrap() = Some(handle); @@ -565,7 +567,13 @@ impl SshClient { } /// Authenticates through each jump host in turn; returns the last hop and the ones before it. - async fn via_jumps(&self, sc: &SshConfig, check: &HostKeyCheck, passphrase: Option, timeout: Duration) -> Result<(Arc>, Vec>>)> { + async fn via_jumps( + &self, + sc: &SshConfig, + check: &HostKeyCheck, + passphrase: Option, + timeout: Duration, + ) -> Result<(Arc>, Vec>>)> { let mut chain: Vec>> = vec![]; for (i, j) in sc.jump_hosts.iter().enumerate() { let JumpHost { host, port, username, private_key_path, .. } = j; @@ -585,13 +593,23 @@ impl SshClient { self.handshake(tcp, host, jport, check).await? } Some(prev) => { - let ch = prev.channel_open_direct_tcpip(host.clone(), jport as u32, "127.0.0.1", 0).await.map_err(|e| format!("failed to dial through jump host {i} ({host}:{jport}): {e}"))?; + let ch = prev + .channel_open_direct_tcpip(host.clone(), jport as u32, "127.0.0.1", 0) + .await + .map_err(|e| format!("failed to dial through jump host {i} ({host}:{jport}): {e}"))?; self.handshake(ch.into_stream(), host, jport, check).await? } }; authenticate( &mut h, - Auth { user: username, methods: vec!["publickey".into()], agent: key.is_none() || sc.agent_auth, key, password: None, totp_secret: String::new() }, + Auth { + user: username, + methods: vec!["publickey".into()], + agent: key.is_none() || sc.agent_auth, + key, + password: None, + totp_secret: String::new(), + }, ) .await .map_err(|e| format!("failed to establish SSH through jump host {i}: {e}"))?; @@ -678,9 +696,8 @@ impl SshClient { ForwardDirection::Local => { let bind = if rule.bind_host.is_empty() { "127.0.0.1".to_string() } else { rule.bind_host.clone() }; let addr = format!("{bind}:{}", rule.bind_port); - let listener = rt() - .block_on(tokio::net::TcpListener::bind(addr.clone())) - .map_err(|e| format!("local forward: listen on {addr} failed: {e}"))?; + let listener = + rt().block_on(tokio::net::TcpListener::bind(addr.clone())).map_err(|e| format!("local forward: listen on {addr} failed: {e}"))?; let (dhost, dport) = (rule.dest_host.clone(), rule.dest_port as u32); let t2 = tasks.clone(); let task = rt().spawn(async move { @@ -755,9 +772,8 @@ impl SshClient { let h = self.handle()?; let ch = h.channel_open_session().await?; ch.request_subsystem(true, "sftp").await?; - let s = Arc::new( - russh_sftp::client::SftpSession::new(ch.into_stream()).await.map_err(|e| format!("sftpclient: failed to start SFTP subsystem: {e}"))?, - ); + let s = + Arc::new(russh_sftp::client::SftpSession::new(ch.into_stream()).await.map_err(|e| format!("sftpclient: failed to start SFTP subsystem: {e}"))?); *slot = Some(s.clone()); Ok(s) }) diff --git a/rust/core/src/clients/telnet.rs b/rust/core/src/clients/telnet.rs index 5ef5cac..998b079 100644 --- a/rust/core/src/clients/telnet.rs +++ b/rust/core/src/clients/telnet.rs @@ -367,8 +367,7 @@ mod tests { let (mut srv, _) = listener.accept().unwrap(); srv.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); - srv.write_all(&[IAC, DO, OPT_TERM_TYPE, IAC, DO, OPT_NAWS, IAC, WILL, OPT_ECHO, IAC, WILL, OPT_SGA, IAC, DO, 99]) - .unwrap(); + srv.write_all(&[IAC, DO, OPT_TERM_TYPE, IAC, DO, OPT_NAWS, IAC, WILL, OPT_ECHO, IAC, WILL, OPT_SGA, IAC, DO, 99]).unwrap(); let mut want = vec![IAC, WILL, OPT_TERM_TYPE, IAC, WILL, OPT_NAWS, IAC, SB, OPT_NAWS, 0, 80, 0, 24, IAC, SE]; want.extend_from_slice(&[IAC, DO, OPT_ECHO, IAC, DO, OPT_SGA, IAC, WONT, 99]); assert_eq!(read_n(&mut srv, want.len()), want); diff --git a/rust/core/src/clients/vnc.rs b/rust/core/src/clients/vnc.rs index 1efa569..9233542 100644 --- a/rust/core/src/clients/vnc.rs +++ b/rust/core/src/clients/vnc.rs @@ -76,7 +76,7 @@ pub struct Framebuffer { impl Framebuffer { fn new(width: u16, height: u16) -> Framebuffer { let mut rgba = vec![0u8; width as usize * height as usize * 4]; - rgba.chunks_exact_mut(4).for_each(|p| p[3] = 255); + rgba.as_chunks_mut::<4>().0.iter_mut().for_each(|p| p[3] = 255); Framebuffer { width, height, rgba, dirty: true } } @@ -92,7 +92,7 @@ impl Framebuffer { let (w, stride) = (w as usize, self.width as usize); for row in 0..h as usize { let dst = ((y as usize + row) * stride + x as usize) * 4; - for (d, s) in self.rgba[dst..dst + w * 4].chunks_exact_mut(4).zip(px[row * w * bpp..].chunks_exact(bpp)) { + for (d, s) in self.rgba[dst..dst + w * 4].as_chunks_mut::<4>().0.iter_mut().zip(px[row * w * bpp..].chunks_exact(bpp)) { d[..3].copy_from_slice(&s[..3]); d[3] = 255; } @@ -103,7 +103,7 @@ impl Framebuffer { let stride = self.width as usize; for row in 0..h as usize { let dst = ((y as usize + row) * stride + x as usize) * 4; - for d in self.rgba[dst..dst + w as usize * 4].chunks_exact_mut(4) { + for d in self.rgba[dst..dst + w as usize * 4].as_chunks_mut::<4>().0 { d[..3].copy_from_slice(&rgb[..3]); d[3] = 255; } @@ -241,13 +241,13 @@ impl Client { st.connected = false; r }; - if let Err(e) = result { - if !was_closing { - let msg = e.to_string(); - (self.cfg.on_error)(msg.clone()); - if let Some(ready) = ready { - let _ = ready.send(Err(msg)); - } + if let Err(e) = result + && !was_closing + { + let msg = e.to_string(); + (self.cfg.on_error)(msg.clone()); + if let Some(ready) = ready { + let _ = ready.send(Err(msg)); } } if was_connected { @@ -334,7 +334,13 @@ impl Client { None => return Err(format!("not a VNC server (got {:?})", ver.trim_end()).into()), }; // 3.889 is Apple's 3.8; anything between 3.3 and 3.7 is treated as 3.3, as RFB specifies. - let minor = if minor >= 8 { 8 } else if minor == 7 { 7 } else { 3 }; + let minor = if minor >= 8 { + 8 + } else if minor == 7 { + 7 + } else { + 3 + }; r.get_mut().send(format!("RFB 003.00{minor}\n").as_bytes())?; let security = if minor == 3 { @@ -368,15 +374,13 @@ impl Client { } t => return Err(unsupported_security(&[t as u8]).into()), } - if minor == 8 || security == 2 { - if read_u32(r)? != 0 { - let reason = if minor == 8 { read_string(r)? } else { String::new() }; - return Err(if reason.is_empty() { - "VNC authentication failed (wrong password?)".into() - } else { - format!("VNC authentication failed: {reason}").into() - }); - } + if (minor == 8 || security == 2) && read_u32(r)? != 0 { + let reason = if minor == 8 { read_string(r)? } else { String::new() }; + return Err(if reason.is_empty() { + "VNC authentication failed (wrong password?)".into() + } else { + format!("VNC authentication failed: {reason}").into() + }); } r.get_mut().send(&[self.cfg.shared as u8])?; @@ -414,11 +418,11 @@ pub fn vnc_auth_response(password: &str, challenge: &[u8; 16]) -> [u8; 16] { } let cipher = des::Des::new(&key.into()); let mut out = *challenge; - for chunk in out.chunks_exact_mut(8) { - let mut block: [u8; 8] = chunk.try_into().unwrap(); + for chunk in out.as_chunks_mut::<8>().0 { + let mut block: [u8; 8] = *chunk; let b = (&mut block).into(); cipher.encrypt_block(b); - chunk.copy_from_slice(&block); + *chunk = block; } out } @@ -502,11 +506,7 @@ struct Decoders { impl Default for Decoders { fn default() -> Decoders { - Decoders { - zrle: Decompress::new(true), - tight: std::array::from_fn(|_| Decompress::new(true)), - buf: Vec::new(), - } + Decoders { zrle: Decompress::new(true), tight: std::array::from_fn(|_| Decompress::new(true)), buf: Vec::new() } } } @@ -726,9 +726,7 @@ fn tight(r: &mut impl Read, fb: &mut Framebuffer, dec: &mut Decoders, x: u16, y: 9 => { let len = compact_len(r)?; let jpeg = read_n(r, len)?; - let img = image::load_from_memory_with_format(&jpeg, image::ImageFormat::Jpeg) - .map_err(|e| format!("Tight JPEG: {e}"))? - .to_rgb8(); + let img = image::load_from_memory_with_format(&jpeg, image::ImageFormat::Jpeg).map_err(|e| format!("Tight JPEG: {e}"))?.to_rgb8(); if img.width() as usize != wu || img.height() as usize != hu { return Err("Tight JPEG size doesn't match its rectangle".into()); } diff --git a/rust/core/src/exportimport.rs b/rust/core/src/exportimport.rs index b640a50..774f09b 100644 --- a/rust/core/src/exportimport.rs +++ b/rust/core/src/exportimport.rs @@ -2,9 +2,9 @@ //! over a JSON bundle, with the vault's Argon2id parameters. `.link` files from Alchementary //! Link are the same format. +use crate::Result; use crate::models::Profile; use crate::secrets::{derive_key, random_bytes}; -use crate::Result; use aes_gcm::aead::{Aead, KeyInit}; use aes_gcm::{Aes256Gcm, Nonce}; use serde::{Deserialize, Serialize}; diff --git a/rust/core/src/ircformat.rs b/rust/core/src/ircformat.rs index 115888e..681bd82 100644 --- a/rust/core/src/ircformat.rs +++ b/rust/core/src/ircformat.rs @@ -409,10 +409,7 @@ mod tests { fn hex_colours() { assert_eq!( format_text("\x04ff8800orange\x04 default"), - vec![ - span("orange", Style { fg_rgb: Some([255, 136, 0]), ..Style::default() }), - span(" default", Style::default()) - ] + vec![span("orange", Style { fg_rgb: Some([255, 136, 0]), ..Style::default() }), span(" default", Style::default())] ); let s = format_text("\x0400ff00,0000FFx"); assert_eq!(s[0].style, Style { fg_rgb: Some([0, 255, 0]), bg_rgb: Some([0, 0, 255]), ..Style::default() }); @@ -444,10 +441,8 @@ mod tests { #[test] fn event_lines() { - let message = format_event( - &Event { kind: "message".into(), from: "alice".into(), text: "hello \x02world\x02".into(), ..Default::default() }, - "12:34:56", - ); + let message = + format_event(&Event { kind: "message".into(), from: "alice".into(), text: "hello \x02world\x02".into(), ..Default::default() }, "12:34:56"); assert_eq!(message[0], span("[12:34:56]", Style { dim: true, ..Style::fg(GREY) })); assert!(message.iter().any(|s| s.text == "world" && s.style.bold)); let nick = message.iter().find(|s| s.text == "alice").unwrap(); @@ -458,10 +453,7 @@ mod tests { let action = format_event(&Event { kind: "action".into(), from: "bob".into(), text: "waves".into(), ..Default::default() }, "01:02:03"); assert!(action.iter().any(|s| s.text.ends_with("waves") && s.style.italic && s.style.fg == Some(MAGENTA))); - let kick = format_event( - &Event { kind: "kick".into(), from: "op".into(), command: "eve".into(), text: "spam".into(), ..Default::default() }, - "t", - ); + let kick = format_event(&Event { kind: "kick".into(), from: "op".into(), command: "eve".into(), text: "spam".into(), ..Default::default() }, "t"); assert_eq!(kick.iter().map(|s| s.text.as_str()).collect::(), "[t] <-- eve was kicked by op (spam)"); let system = format_event(&Event { kind: "system".into(), text: "hi".into(), ..Default::default() }, "t"); assert_eq!(system.last().unwrap(), &span("hi", Style::fg(CYAN))); diff --git a/rust/core/src/keyfiles.rs b/rust/core/src/keyfiles.rs index fbe191f..9735b15 100644 --- a/rust/core/src/keyfiles.rs +++ b/rust/core/src/keyfiles.rs @@ -5,25 +5,11 @@ use crate::Result; use std::io::Read; use std::path::{Path, PathBuf}; -const PRIVATE_KEY_MARKERS: [&str; 6] = [ - "BEGIN OPENSSH PRIVATE KEY", - "BEGIN RSA PRIVATE KEY", - "BEGIN DSA PRIVATE KEY", - "BEGIN EC PRIVATE KEY", - "BEGIN ED25519 PRIVATE KEY", - "BEGIN PRIVATE KEY", -]; +const PRIVATE_KEY_MARKERS: [&str; 6] = + ["BEGIN OPENSSH PRIVATE KEY", "BEGIN RSA PRIVATE KEY", "BEGIN DSA PRIVATE KEY", "BEGIN EC PRIVATE KEY", "BEGIN ED25519 PRIVATE KEY", "BEGIN PRIVATE KEY"]; -const IGNORED: [&str; 8] = [ - "authorized_keys", - "authorized_keys2", - "config", - "known_hosts", - "known_hosts.old", - "ssh_config", - "ssh_known_hosts", - "ssh_known_hosts2", -]; +const IGNORED: [&str; 8] = + ["authorized_keys", "authorized_keys2", "config", "known_hosts", "known_hosts.old", "ssh_config", "ssh_known_hosts", "ssh_known_hosts2"]; /// Whether the first 512 bytes look like a private key. Unreadable or empty paths are `false` /// (this is also Go's `FileIsPrivateKey`). @@ -33,8 +19,7 @@ pub fn is_likely_private_key(path: &Path) -> bool { if f.take(512).read_to_end(&mut head).is_err() || head.is_empty() { return false; } - head.starts_with(b"openssh-key-v1") - || PRIVATE_KEY_MARKERS.iter().any(|m| head.windows(m.len()).any(|w| w == m.as_bytes())) + head.starts_with(b"openssh-key-v1") || PRIVATE_KEY_MARKERS.iter().any(|m| head.windows(m.len()).any(|w| w == m.as_bytes())) } /// Candidate private keys in `~/.ssh`, sorted. A missing home or `.ssh` gives an empty list. diff --git a/rust/core/src/models.rs b/rust/core/src/models.rs index a51a790..1892c4e 100644 --- a/rust/core/src/models.rs +++ b/rust/core/src/models.rs @@ -52,8 +52,7 @@ pub enum Protocol { } impl Protocol { - pub const SAVED: [Protocol; 7] = - [Protocol::Ssh, Protocol::Telnet, Protocol::Raw, Protocol::Github, Protocol::Vnc, Protocol::Irc, Protocol::Serial]; + pub const SAVED: [Protocol; 7] = [Protocol::Ssh, Protocol::Telnet, Protocol::Raw, Protocol::Github, Protocol::Vnc, Protocol::Irc, Protocol::Serial]; pub fn id(self) -> &'static str { match self { Protocol::Ssh => "ssh", diff --git a/rust/core/src/recording.rs b/rust/core/src/recording.rs index 29cc485..905869a 100644 --- a/rust/core/src/recording.rs +++ b/rust/core/src/recording.rs @@ -340,10 +340,7 @@ impl Store { } } let id = filename.trim_end_matches(".cast").to_string(); - let created_at = chrono::DateTime::from_timestamp(hdr.timestamp, 0) - .ok_or("recording: invalid timestamp")? - .with_timezone(&chrono::Local) - .fixed_offset(); + let created_at = chrono::DateTime::from_timestamp(hdr.timestamp, 0).ok_or("recording: invalid timestamp")?.with_timezone(&chrono::Local).fixed_offset(); Ok(RecordingMeta { session_id: id.clone(), // recording ID matches session ID by default id, diff --git a/rust/core/src/secrets.rs b/rust/core/src/secrets.rs index fea0c80..5bbde05 100644 --- a/rust/core/src/secrets.rs +++ b/rust/core/src/secrets.rs @@ -43,9 +43,7 @@ pub fn open(key: &[u8; 32], data: &[u8]) -> Result> { if data.len() < 12 { return Err("ciphertext too short".into()); } - Aes256Gcm::new(key.into()) - .decrypt(Nonce::from_slice(&data[..12]), &data[12..]) - .map_err(|_| "decryption failed".into()) + Aes256Gcm::new(key.into()).decrypt(Nonce::from_slice(&data[..12]), &data[12..]).map_err(|_| "decryption failed".into()) } #[derive(Serialize, Deserialize)] @@ -93,10 +91,7 @@ impl Vault { return Err("secrets: vault already initialised".into()); } let salt = random_bytes::<32>(); - let u = Unlocked { - key: derive_key(password, &salt)?, - vault: VaultFile { version: 1, salt: B64.encode(salt), secrets: BTreeMap::new() }, - }; + let u = Unlocked { key: derive_key(password, &salt)?, vault: VaultFile { version: 1, salt: B64.encode(salt), secrets: BTreeMap::new() } }; std::fs::create_dir_all(&self.dir)?; self.save(&u)?; *self.state.lock().unwrap() = Some(u); @@ -185,11 +180,7 @@ fn off_runtime(f: impl FnOnce() -> T + Send + 'static) -> T { fn entry(service: &str, key: &str) -> keyring::Result { // go-keyring's Windows target is "service:user"; keyring's default differs. - if cfg!(windows) { - keyring::Entry::new_with_target(&format!("{service}:{key}"), service, key) - } else { - keyring::Entry::new(service, key) - } + if cfg!(windows) { keyring::Entry::new_with_target(&format!("{service}:{key}"), service, key) } else { keyring::Entry::new(service, key) } } /// The OS keychain. Every call can fail (no secret service, locked keyring); callers fall back diff --git a/rust/core/src/service.rs b/rust/core/src/service.rs index 89df85f..71f73c1 100644 --- a/rust/core/src/service.rs +++ b/rust/core/src/service.rs @@ -229,7 +229,12 @@ impl Service { } } } - self.store.add_audit("export", "", "", &format!("Exported {} profile(s) {}", ps.len(), if include_secrets { "with secrets" } else { "without secrets" })); + self.store.add_audit( + "export", + "", + "", + &format!("Exported {} profile(s) {}", ps.len(), if include_secrets { "with secrets" } else { "without secrets" }), + ); Ok(serde_json::to_string_pretty(&ps)?) } @@ -238,7 +243,11 @@ impl Service { return Err(format!("import data is too large (max {MAX_IMPORT_JSON_BYTES} bytes)").into()); } let ps: Vec> = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {e}"))?; - let ps = ps.into_iter().enumerate().map(|(i, p)| p.ok_or_else(|| format!("profile import entry {i} is null"))).collect::, _>>()?; + let ps = ps + .into_iter() + .enumerate() + .map(|(i, p)| p.ok_or_else(|| format!("profile import entry {i} is null"))) + .collect::, _>>()?; self.import(ps, strategy) } diff --git a/rust/core/src/sessions.rs b/rust/core/src/sessions.rs index 79eb11b..ded6d33 100644 --- a/rust/core/src/sessions.rs +++ b/rust/core/src/sessions.rs @@ -2,14 +2,14 @@ //! internal/sessions — same lifecycle, reconnect backoff, login scripts, recording hooks and //! credential resolution — reporting to the UI through `Event`s. -use crate::clients::{self, Callbacks, github, irc, raw, serial, telnet}; +use crate::Result; use crate::clients::local::LocalShell; use crate::clients::ssh::{HostKeyCheck, HostKeyInfo, SecretFn, SshClient}; +use crate::clients::{self, Callbacks, github, irc, raw, serial, telnet}; use crate::models::*; use crate::recording::{self, Recorder}; use crate::secrets::Secrets; use crate::storage::Store; -use crate::Result; use std::collections::HashMap; use std::path::PathBuf; use std::sync::atomic::{AtomicBool, Ordering}; @@ -214,7 +214,9 @@ impl Manager { let id2 = id.clone(); std::thread::spawn(move || { let r = me.connect_protocol(&id2, epoch, &profile, prompt); - me.finish_connect(&id2, epoch, r, |me| me.store.add_audit("connect", &profile.id, &id2, &format!("Connected via {} to {}", profile.protocol.id(), profile.host))); + me.finish_connect(&id2, epoch, r, |me| { + me.store.add_audit("connect", &profile.id, &id2, &format!("Connected via {} to {}", profile.protocol.id(), profile.host)) + }); }); id } @@ -266,9 +268,8 @@ impl Manager { Protocol::Raw => { let cb = self.terminal_callbacks(id, epoch); let (me, sid) = (self.clone(), id.to_string()); - let on_data: raw::RawDataFn = Arc::new(move |d, dir, ts| { - me.emit(Event::RawData { session: sid.clone(), data: d.to_vec(), direction: dir.into(), timestamp: ts }) - }); + let on_data: raw::RawDataFn = + Arc::new(move |d, dir, ts| me.emit(Event::RawData { session: sid.clone(), data: d.to_vec(), direction: dir.into(), timestamp: ts })); let c = Arc::new(raw::RawClient::new(p, on_data, cb.on_error, cb.on_close)); c.connect()?; Ok(Client::Raw(c)) @@ -465,7 +466,20 @@ impl Manager { /// An SSH connection used only to tunnel another protocol (IRC, VNC). #[allow(clippy::too_many_arguments)] - fn tunnel(self: &Arc, id: &str, profile_id: &str, host: &str, port: i64, user: &str, method: &str, key: &str, agent: bool, mode: SecretMode, kind: &str, prompt: Prompt) -> Result> { + fn tunnel( + self: &Arc, + id: &str, + profile_id: &str, + host: &str, + port: i64, + user: &str, + method: &str, + key: &str, + agent: bool, + mode: SecretMode, + kind: &str, + prompt: Prompt, + ) -> Result> { let mut tp = Profile::new(Protocol::Ssh); tp.host = host.into(); tp.port = if port == 0 { 22 } else { port }; @@ -493,10 +507,7 @@ impl Manager { Err(e) => { let msg = e.to_string().to_lowercase(); let auth = ["token validation failed", "authentication failed", "http 401", "http 403"].iter().any(|k| msg.contains(k)); - let retry = auth - .then(|| prompt("GitHub token did not work. Please enter a valid token:").ok()) - .flatten() - .filter(|t| !t.is_empty()); + let retry = auth.then(|| prompt("GitHub token did not work. Please enter a valid token:").ok()).flatten().filter(|t| !t.is_empty()); let Some(t) = retry else { return err(format!("GitHub authentication failed: {e}")) }; if gh.secret_mode == SecretMode::Saved { self.secrets.set(&format!("profile:{}:github-token", p.id), &t); @@ -562,7 +573,19 @@ impl Manager { rooms = crate::storage::dedup_rooms(&[&rooms, &last]); } let tunnel = if ic.ssh_tunnel { - Some(self.tunnel(id, &p.id, &ic.ssh_host, ic.ssh_port, &ic.ssh_username, &ic.ssh_auth_method, &ic.ssh_private_key, ic.ssh_agent_auth, ic.ssh_secret_mode, "irc", prompt)?) + Some(self.tunnel( + id, + &p.id, + &ic.ssh_host, + ic.ssh_port, + &ic.ssh_username, + &ic.ssh_auth_method, + &ic.ssh_private_key, + ic.ssh_agent_auth, + ic.ssh_secret_mode, + "irc", + prompt, + )?) } else { None }; @@ -629,14 +652,35 @@ impl Manager { let host = if vc.vnc_host.is_empty() { p.host.clone() } else { vc.vnc_host.clone() }; let port = if vc.vnc_port == 0 { 5900 } else { vc.vnc_port } as u16; let tunnel = if vc.ssh_tunnel { - Some(self.tunnel(id, &p.id, &vc.ssh_host, vc.ssh_port, &vc.ssh_username, &vc.ssh_auth_method, &vc.ssh_private_key, vc.ssh_agent_auth, vc.ssh_secret_mode, "vnc", prompt.clone())?) + Some(self.tunnel( + id, + &p.id, + &vc.ssh_host, + vc.ssh_port, + &vc.ssh_username, + &vc.ssh_auth_method, + &vc.ssh_private_key, + vc.ssh_agent_auth, + vc.ssh_secret_mode, + "vnc", + prompt.clone(), + )?) } else { None }; let password = self.secret_fn(&p.id, "vnc-password", vc.secret_mode, prompt); let cb = self.terminal_callbacks(id, epoch); let (me, sid) = (self.clone(), id.to_string()); - let c = connect_vnc_client(&host, port, &vc, p.connect_timeout, password, tunnel.clone(), cb, Arc::new(move || me.emit(Event::VncFrame { session: sid.clone() }))); + let c = connect_vnc_client( + &host, + port, + &vc, + p.connect_timeout, + password, + tunnel.clone(), + cb, + Arc::new(move || me.emit(Event::VncFrame { session: sid.clone() })), + ); match c { Ok(c) => { self.emit(Event::VncReady { session: id.into() }); @@ -811,7 +855,12 @@ impl Manager { return; }; let delay = (1u64 << (attempt - 1).min(5)).min(30); - self.emit(Event::State { session: id.into(), state: SessionState::Reconnecting, error: String::new(), retry: Some(Retry { attempt, max, delay_secs: delay }) }); + self.emit(Event::State { + session: id.into(), + state: SessionState::Reconnecting, + error: String::new(), + retry: Some(Retry { attempt, max, delay_secs: delay }), + }); let (me, id) = (self.clone(), id.to_string()); std::thread::spawn(move || { let deadline = std::time::Instant::now() + Duration::from_secs(delay); @@ -903,7 +952,10 @@ impl Manager { "wait" => { let timeout = Duration::from_secs(if step.timeout <= 0 { 10 } else { step.timeout as u64 }); if !wait_for_output(&login, &step.value, timeout) { - me.emit(Event::Debug { session: id.clone(), message: format!("login script step {} timed out waiting for {:?}", i + 1, step.value) }); + me.emit(Event::Debug { + session: id.clone(), + message: format!("login script step {} timed out waiting for {:?}", i + 1, step.value), + }); break; } } @@ -1105,7 +1157,9 @@ impl Manager { if exists { return err(format!("sessions: session {id:?} is already being recorded")); } - let r = Arc::new(Recorder::new(self.recordings.dir(), id, &title, cols as i64, rows as i64).map_err(|e| format!("sessions: failed to start recording: {e}"))?); + let r = Arc::new( + Recorder::new(self.recordings.dir(), id, &title, cols as i64, rows as i64).map_err(|e| format!("sessions: failed to start recording: {e}"))?, + ); self.with(id, |s| s.recorder = Some(r))?; self.emit(Event::RecordingStarted { session: id.into() }); Ok(()) diff --git a/rust/core/src/sshconfig.rs b/rust/core/src/sshconfig.rs index f3cdcfb..3fda6ff 100644 --- a/rust/core/src/sshconfig.rs +++ b/rust/core/src/sshconfig.rs @@ -29,11 +29,7 @@ pub fn parse_file(path: &Path) -> Result> { return Err(format!("sshconfig: {p} is a directory").into()); } if meta.len() > MAX_CONFIG_FILE_BYTES { - return Err(format!( - "sshconfig: config file is too large: {} bytes exceeds {MAX_CONFIG_FILE_BYTES} bytes", - meta.len() - ) - .into()); + return Err(format!("sshconfig: config file is too large: {} bytes exceeds {MAX_CONFIG_FILE_BYTES} bytes", meta.len()).into()); } let raw = std::fs::read(path).map_err(|e| format!("sshconfig: failed to open {p}: {e}"))?; parse(&String::from_utf8_lossy(&raw)) @@ -138,8 +134,7 @@ fn parse_proxy_jump(value: &str) -> Option { if value.is_empty() || value == "none" { return None; } - let mut jh = - JumpHost { port: 22, auth_method: "publickey".into(), secret_mode: SecretMode::AskEveryTime, ..Default::default() }; + let mut jh = JumpHost { port: 22, auth_method: "publickey".into(), secret_mode: SecretMode::AskEveryTime, ..Default::default() }; if let Some((user, rest)) = value.split_once('@') { jh.username = user.into(); value = rest; diff --git a/rust/core/src/storage.rs b/rust/core/src/storage.rs index e9ae6f1..21a37f5 100644 --- a/rust/core/src/storage.rs +++ b/rust/core/src/storage.rs @@ -1,8 +1,8 @@ //! SQLite persistence: profiles, known hosts, preferences, audit log, snippets and IRC channel //! state. The schema and migration files are the Go app's, so existing databases open unchanged. -use crate::models::*; use crate::Result; +use crate::models::*; use chrono::{DateTime, SecondsFormat}; use rusqlite::{Connection, OptionalExtension, Row, params}; use std::path::Path; @@ -228,10 +228,7 @@ impl Store { } pub fn set_favourite(&self, id: &str, fav: bool) -> Result<()> { - let n = self.db.lock().unwrap().execute( - "UPDATE profiles SET favourite = ?, updated_at = ? WHERE id = ?", - params![fav as i64, fmt_time(&now()), id], - )?; + let n = self.db.lock().unwrap().execute("UPDATE profiles SET favourite = ?, updated_at = ? WHERE id = ?", params![fav as i64, fmt_time(&now()), id])?; if n == 0 { return Err(format!("storage: profile {id:?} not found").into()); } @@ -240,10 +237,7 @@ impl Store { pub fn update_last_connected(&self, id: &str) -> Result<()> { let now = fmt_time(&now()); - self.db - .lock() - .unwrap() - .execute("UPDATE profiles SET last_connected = ?, updated_at = ? WHERE id = ?", params![now, now, id])?; + self.db.lock().unwrap().execute("UPDATE profiles SET last_connected = ?, updated_at = ? WHERE id = ?", params![now, now, id])?; Ok(()) } @@ -337,9 +331,7 @@ impl Store { pub fn list_audit(&self, limit: i64) -> Result> { let db = self.db.lock().unwrap(); - let mut st = db.prepare( - "SELECT id, timestamp, action, profile_id, session_id, details FROM audit_log ORDER BY timestamp DESC LIMIT ?", - )?; + let mut st = db.prepare("SELECT id, timestamp, action, profile_id, session_id, details FROM audit_log ORDER BY timestamp DESC LIMIT ?")?; let rows = st .query_map([limit], |r| { Ok(AuditEntry { @@ -357,8 +349,7 @@ impl Store { pub fn irc_last_rooms(&self, profile_id: &str) -> Result> { let db = self.db.lock().unwrap(); - let mut st = - db.prepare("SELECT room_name FROM irc_channel_state WHERE profile_id = ? ORDER BY position, room_name")?; + let mut st = db.prepare("SELECT room_name FROM irc_channel_state WHERE profile_id = ? ORDER BY position, room_name")?; let rows = st.query_map([profile_id], |r| r.get(0))?.collect::>>()?; Ok(rows) } @@ -384,14 +375,8 @@ impl Store { } pub fn preferences(&self) -> AppPreferences { - let v: Option = self - .db - .lock() - .unwrap() - .query_row("SELECT value FROM preferences WHERE key = '_all'", [], |r| r.get(0)) - .optional() - .ok() - .flatten(); + let v: Option = + self.db.lock().unwrap().query_row("SELECT value FROM preferences WHERE key = '_all'", [], |r| r.get(0)).optional().ok().flatten(); let mut p = v.and_then(|v| serde_json::from_str::(&v).ok()).unwrap_or_default(); p.apply_defaults(); p @@ -407,9 +392,7 @@ impl Store { pub fn list_snippets(&self) -> Result> { let db = self.db.lock().unwrap(); - let mut st = db.prepare( - "SELECT id, name, command, description, folder, tags, created_at, updated_at FROM snippets ORDER BY name ASC", - )?; + let mut st = db.prepare("SELECT id, name, command, description, folder, tags, created_at, updated_at FROM snippets ORDER BY name ASC")?; let rows = st.query_map([], scan_snippet)?.collect::>>()?; Ok(rows) } diff --git a/rust/core/src/terminalenv.rs b/rust/core/src/terminalenv.rs index b43ee4e..e75203a 100644 --- a/rust/core/src/terminalenv.rs +++ b/rust/core/src/terminalenv.rs @@ -16,11 +16,7 @@ pub fn local_shell_env(env: Vec) -> Vec { /// Variables to request for an SSH session. A locale set by the profile is kept as is; otherwise /// a UTF-8 `LC_CTYPE` is added. Keys are trimmed and empty keys dropped. Iteration order is sorted. pub fn ssh_env_vars(profile_env: &BTreeMap) -> BTreeMap { - let mut out: BTreeMap = profile_env - .iter() - .map(|(k, v)| (k.trim().to_string(), v.clone())) - .filter(|(k, _)| !k.is_empty()) - .collect(); + let mut out: BTreeMap = profile_env.iter().map(|(k, v)| (k.trim().to_string(), v.clone())).filter(|(k, _)| !k.is_empty()).collect(); if ![LC_ALL, LC_CTYPE, LANG].iter().any(|k| out.contains_key(*k)) { out.insert(LC_CTYPE.into(), default_utf8_locale(std::env::consts::OS).into()); } diff --git a/rust/core/src/updater.rs b/rust/core/src/updater.rs index 4e64099..99e3d7f 100644 --- a/rust/core/src/updater.rs +++ b/rust/core/src/updater.rs @@ -70,17 +70,8 @@ pub fn expand_update_url(template: &str) -> String { /// GET with the updater headers; the body is read (bounded) only on 200. fn fetch(url: &str, accept: &str) -> Result<(u16, Vec)> { - let agent: ureq::Agent = ureq::Agent::config_builder() - .timeout_global(Some(REQUEST_TIMEOUT)) - .http_status_as_error(false) - .build() - .into(); - let resp = agent - .get(url) - .header("Accept", accept) - .header("User-Agent", USER_AGENT) - .call() - .map_err(|e| format!("updater: request failed: {e}"))?; + let agent: ureq::Agent = ureq::Agent::config_builder().timeout_global(Some(REQUEST_TIMEOUT)).http_status_as_error(false).build().into(); + let resp = agent.get(url).header("Accept", accept).header("User-Agent", USER_AGENT).call().map_err(|e| format!("updater: request failed: {e}"))?; let status = resp.status().as_u16(); if status != 200 { return Ok((status, vec![])); @@ -130,8 +121,7 @@ pub fn check_github(current_version: &str) -> Result { if status != 200 { return Err(format!("updater: GitHub API returned {status}").into()); } - let release: Release = - serde_json::from_slice(&body).map_err(|e| format!("updater: failed to decode response: {e}"))?; + let release: Release = serde_json::from_slice(&body).map_err(|e| format!("updater: failed to decode response: {e}"))?; let latest = normalise_version(&release.tag_name); if release.draft || release.prerelease { return Ok(UpdateInfo { current_version: current, latest_version: latest, ..Default::default() }); @@ -189,10 +179,7 @@ fn is_newer(latest: &str, current: &str) -> bool { /// Go's `fmt.Sscanf(s, "%d")`: optional sign then digits; anything else is 0. fn leading_int(s: Option<&&str>) -> i64 { let s = s.map_or("", |s| s.trim_start()); - let end = s - .char_indices() - .find(|&(i, c)| !(c.is_ascii_digit() || (i == 0 && (c == '+' || c == '-')))) - .map_or(s.len(), |(i, _)| i); + let end = s.char_indices().find(|&(i, c)| !(c.is_ascii_digit() || (i == 0 && (c == '+' || c == '-')))).map_or(s.len(), |(i, _)| i); s[..end].parse().unwrap_or(0) } @@ -228,10 +215,7 @@ mod tests { } req.extend(&buf[..n]); } - let head = format!( - "HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", - body.len() - ); + let head = format!("HTTP/1.1 200 OK\r\nContent-Type: {content_type}\r\nContent-Length: {}\r\nConnection: close\r\n\r\n", body.len()); let _ = s.write_all(head.as_bytes()); let _ = s.write_all(&body); }); diff --git a/rust/core/src/validation.rs b/rust/core/src/validation.rs index f5866f1..7edec6c 100644 --- a/rust/core/src/validation.rs +++ b/rust/core/src/validation.rs @@ -380,8 +380,7 @@ pub fn validate_serial_config(cfg: &SerialConfig) -> Result<(), ValidationErrors fn is_valid_nick(nick: &str) -> bool { let b = nick.as_bytes(); let Some((&first, rest)) = b.split_first() else { return false }; - ((b'A'..=b'}').contains(&first) || first == b'?') - && rest.iter().all(|c| (b'A'..=b'}').contains(c) || c.is_ascii_digit() || *c == b'-') + ((b'A'..=b'}').contains(&first) || first == b'?') && rest.iter().all(|c| (b'A'..=b'}').contains(c) || c.is_ascii_digit() || *c == b'-') } /// girc's IsValidChannel. @@ -526,14 +525,8 @@ mod tests { assert!(validate_vnc_config("", &named).is_ok()); assert!(validate_vnc_config("desktop.local", &base).is_ok()); assert!(validate_vnc_config("", &base).is_err()); - let tunnel = VncConfig { - ssh_tunnel: true, - ssh_host: "jump.local".into(), - ssh_port: 22, - ssh_username: "admin".into(), - ssh_auth_method: "token".into(), - ..base - }; + let tunnel = + VncConfig { ssh_tunnel: true, ssh_host: "jump.local".into(), ssh_port: 22, ssh_username: "admin".into(), ssh_auth_method: "token".into(), ..base }; assert!(validate_vnc_config("desktop.local", &tunnel).is_err()); } diff --git a/rust/core/tests/ssh_integration.rs b/rust/core/tests/ssh_integration.rs index 7736d28..b489762 100644 --- a/rust/core/tests/ssh_integration.rs +++ b/rust/core/tests/ssh_integration.rs @@ -40,9 +40,8 @@ fn free_port() -> u16 { fn start(name: &str) -> Option { let sshd = which("sshd")?; let keygen = which("ssh-keygen")?; - let sftp_server = ["/usr/lib/openssh/sftp-server", "/usr/libexec/openssh/sftp-server", "/usr/libexec/sftp-server"] - .iter() - .find(|p| Path::new(p).exists())?; + let sftp_server = + ["/usr/lib/openssh/sftp-server", "/usr/libexec/openssh/sftp-server", "/usr/libexec/sftp-server"].iter().find(|p| Path::new(p).exists())?; let dir = std::env::temp_dir().join(format!("nexus-sshd-{name}-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); @@ -95,10 +94,13 @@ fn callbacks() -> (Callbacks, Seen) { fn accept_all() -> (HostKeyCheck, Arc>>) { let fps: Arc>> = Arc::default(); let f2 = fps.clone(); - (Arc::new(move |i| { - f2.lock().unwrap().push(format!("{} {}", i.key_type, i.fingerprint)); - Ok(()) - }), fps) + ( + Arc::new(move |i| { + f2.lock().unwrap().push(format!("{} {}", i.key_type, i.fingerprint)); + Ok(()) + }), + fps, + ) } fn wait_for(seen: &Seen, needle: &str) -> bool { @@ -151,7 +153,9 @@ fn echo_through(port: u16) -> String { #[test] fn shell_exec_forwards_sftp() { - let Some(s) = start("main") else { return eprintln!("sshd not available; skipping") }; + let Some(s) = start("main") else { + return eprintln!("sshd not available; skipping"); + }; let (cb, seen) = callbacks(); let (check, fps) = accept_all(); let c = SshClient::new(&profile(&s), cb); @@ -166,11 +170,28 @@ fn shell_exec_forwards_sftp() { let echo = echo_server(); let lport = free_port(); - let rule = PortForward { id: "l".into(), direction: ForwardDirection::Local, bind_port: lport as i64, dest_host: "127.0.0.1".into(), dest_port: echo as i64, enabled: true, ..Default::default() }; + let rule = PortForward { + id: "l".into(), + direction: ForwardDirection::Local, + bind_port: lport as i64, + dest_host: "127.0.0.1".into(), + dest_port: echo as i64, + enabled: true, + ..Default::default() + }; c.start_forward(rule).unwrap(); assert_eq!(echo_through(lport), "ping"); let rport = free_port(); - let rule = PortForward { id: "r".into(), direction: ForwardDirection::Remote, bind_host: "127.0.0.1".into(), bind_port: rport as i64, dest_host: "127.0.0.1".into(), dest_port: echo as i64, enabled: true, ..Default::default() }; + let rule = PortForward { + id: "r".into(), + direction: ForwardDirection::Remote, + bind_host: "127.0.0.1".into(), + bind_port: rport as i64, + dest_host: "127.0.0.1".into(), + dest_port: echo as i64, + enabled: true, + ..Default::default() + }; c.start_forward(rule).unwrap(); assert_eq!(echo_through(rport), "ping"); assert_eq!(c.active_forwards().len(), 2); @@ -226,7 +247,8 @@ fn jump_host_chain() { let Some(s) = start("jump") else { return }; let mut p = profile(&s); let key = p.ssh.as_ref().unwrap().private_key_path.clone(); - p.ssh.as_mut().unwrap().jump_hosts = vec![JumpHost { host: "127.0.0.1".into(), port: s.port as i64, username: p.username.clone(), private_key_path: key, ..Default::default() }]; + p.ssh.as_mut().unwrap().jump_hosts = + vec![JumpHost { host: "127.0.0.1".into(), port: s.port as i64, username: p.username.clone(), private_key_path: key, ..Default::default() }]; let (check, fps) = accept_all(); let c = SshClient::new(&p, Callbacks::noop()); c.connect(check, None, None).unwrap(); @@ -238,7 +260,9 @@ fn jump_host_chain() { #[test] fn agent_auth_and_forwarding() { let Some(mut s) = start("agent") else { return }; - let (Some(agent), Some(add)) = (which("ssh-agent"), which("ssh-add")) else { return }; + let (Some(agent), Some(add)) = (which("ssh-agent"), which("ssh-add")) else { + return; + }; let sock = s.dir.join("agent.sock"); s.agent = Some(Command::new(agent).arg("-D").arg("-a").arg(&sock).spawn().unwrap()); let start = Instant::now(); diff --git a/rust/rustfmt.toml b/rust/rustfmt.toml new file mode 100644 index 0000000..40d36bf --- /dev/null +++ b/rust/rustfmt.toml @@ -0,0 +1,2 @@ +max_width = 160 +use_small_heuristics = "Max" From cc42a9086dcfd5b218a2c73ab8c084f3bf31872c Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:42:40 +0100 Subject: [PATCH 32/45] rust: --connect startup flag; palette launcher hint fits narrow windows --- rust/app/src/app.rs | 8 ++++++++ rust/app/src/chrome.rs | 13 +++++-------- rust/app/src/main.rs | 3 ++- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs index c650ac5..ae15f0f 100644 --- a/rust/app/src/app.rs +++ b/rust/app/src/app.rs @@ -23,6 +23,8 @@ pub struct Options { /// Open a window at startup: editor, quick, settings[:tab], keys, snippets, triggers, /// recordings, palette, export, import (screenshot checks). pub open: Option, + /// Connect a saved profile (by name or id) at startup. + pub connect: Option, } /// Errors from worker threads, drained into toasts each frame. @@ -158,6 +160,12 @@ impl App { run_local: opts.run_local.map(|c| (c, None)), resize_sidebar: false, }; + if let Some(c) = opts.connect.as_deref() { + match app.s.profiles.iter().find(|p| p.id == c || p.display_name == c).map(|p| p.id.clone()) { + Some(id) => app.connect_profile(&id, true), + None => eprintln!("--connect: no profile named {c:?}"), + } + } if let Some(w) = opts.open.as_deref() { app.open_window(w); } diff --git a/rust/app/src/chrome.rs b/rust/app/src/chrome.rs index ee5deef..8de6f81 100644 --- a/rust/app/src/chrome.rs +++ b/rust/app/src/chrome.rs @@ -194,14 +194,11 @@ pub fn title_bar(app: &mut App, ui: &mut Ui) { let resp = ui.interact(r, ui.id().with("palette-launch"), Sense::click()); ui.painter().rect(r, 6.0, if resp.hovered() { t.hover } else { t.field }, Stroke::new(1.0, t.field_border), egui::StrokeKind::Inside); ui.painter().text(r.left_center() + vec2(9.0, 0.0), Align2::LEFT_CENTER, icon::MAGNIFYING_GLASS, egui::FontId::proportional(13.0), t.text_faint); - ui.painter().with_clip_rect(r.shrink(2.0)).text( - r.left_center() + vec2(28.0, 0.0), - Align2::LEFT_CENTER, - "Search connections and commands", - egui::FontId::proportional(12.0), - t.text_faint, - ); - if w > 250.0 { + let show_keys = w > 250.0; + let clip = Rect::from_min_max(r.min, pos2(r.right() - if show_keys { 52.0 } else { 4.0 }, r.bottom())); + let hint = if w > 290.0 { "Search connections and commands" } else { "Search" }; + ui.painter().with_clip_rect(clip).text(r.left_center() + vec2(28.0, 0.0), Align2::LEFT_CENTER, hint, egui::FontId::proportional(12.0), t.text_faint); + if show_keys { ui.painter().text(r.right_center() - vec2(8.0, 0.0), Align2::RIGHT_CENTER, keys("Mod+K").replace('+', " "), theme::semibold(10.5), t.text_faint); } if resp.clicked() { diff --git a/rust/app/src/main.rs b/rust/app/src/main.rs index 4519310..0062485 100644 --- a/rust/app/src/main.rs +++ b/rust/app/src/main.rs @@ -33,7 +33,7 @@ use nexus_core::service::Service; use std::sync::Arc; fn main() -> eframe::Result { - let mut opts = app::Options { screenshot: None, theme: None, run_local: None, open: None }; + let mut opts = app::Options { screenshot: None, theme: None, run_local: None, open: None, connect: None }; let mut size = [1360.0, 860.0]; let mut data_dir = None; let mut args = std::env::args().skip(1); @@ -42,6 +42,7 @@ fn main() -> eframe::Result { "--theme" => opts.theme = args.next(), "--run-local" => opts.run_local = args.next(), "--open" => opts.open = args.next(), + "--connect" => opts.connect = args.next(), "--screenshot" => opts.screenshot = args.next().map(Into::into), // Use another data directory (testing, screenshots, portable installs). "--data-dir" => data_dir = args.next(), From e1707e4751993248f25b73fbd5dcb5b05ede8dfd Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:43:35 +0100 Subject: [PATCH 33/45] rust: VNC remote clipboard to the local clipboard --- rust/app/src/app.rs | 1 + rust/core/src/sessions.rs | 114 +++++++++++++++++++++++++++++++------- 2 files changed, 96 insertions(+), 19 deletions(-) diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs index ae15f0f..95ee1b9 100644 --- a/rust/app/src/app.rs +++ b/rust/app/src/app.rs @@ -490,6 +490,7 @@ impl App { } } Event::VncFrame { .. } => {} + Event::VncClipboard { text, .. } => self.s.ctx.copy_text(text), } } diff --git a/rust/core/src/sessions.rs b/rust/core/src/sessions.rs index ded6d33..464c0fe 100644 --- a/rust/core/src/sessions.rs +++ b/rust/core/src/sessions.rs @@ -19,25 +19,96 @@ use std::time::Duration; /// Everything the UI hears about. Prompts carry their reply channel; dropping it cancels. pub enum Event { - Data { session: String, data: Vec }, - State { session: String, state: SessionState, error: String, retry: Option }, - Closed { session: String }, - RawData { session: String, data: Vec, direction: String, timestamp: i64 }, - Debug { session: String, message: String }, - GithubStatus { session: String, status: String }, - GithubRepos { session: String, login: String, repos: Vec }, - GithubError { session: String, error: String }, - Irc { session: String, event: irc::Event }, - ForwardStarted { session: String, forward: PortForward }, - ForwardStopped { session: String, forward_id: String }, - ForwardError { session: String, forward_id: String, label: String, error: String }, - RecordingStarted { session: String }, - RecordingStopped { session: String, duration: f64, events: usize }, - SftpProgress { session: String, progress: clients::sftp::Progress }, - SecretPrompt { session: String, title: String, message: String, reply: mpsc::Sender }, - HostKeyVerify { session: String, host: String, key_type: String, fingerprint: String, reply: mpsc::Sender }, - VncReady { session: String }, - VncFrame { session: String }, + Data { + session: String, + data: Vec, + }, + State { + session: String, + state: SessionState, + error: String, + retry: Option, + }, + Closed { + session: String, + }, + RawData { + session: String, + data: Vec, + direction: String, + timestamp: i64, + }, + Debug { + session: String, + message: String, + }, + GithubStatus { + session: String, + status: String, + }, + GithubRepos { + session: String, + login: String, + repos: Vec, + }, + GithubError { + session: String, + error: String, + }, + Irc { + session: String, + event: irc::Event, + }, + ForwardStarted { + session: String, + forward: PortForward, + }, + ForwardStopped { + session: String, + forward_id: String, + }, + ForwardError { + session: String, + forward_id: String, + label: String, + error: String, + }, + RecordingStarted { + session: String, + }, + RecordingStopped { + session: String, + duration: f64, + events: usize, + }, + SftpProgress { + session: String, + progress: clients::sftp::Progress, + }, + SecretPrompt { + session: String, + title: String, + message: String, + reply: mpsc::Sender, + }, + HostKeyVerify { + session: String, + host: String, + key_type: String, + fingerprint: String, + reply: mpsc::Sender, + }, + VncReady { + session: String, + }, + VncFrame { + session: String, + }, + /// The remote desktop's clipboard changed. + VncClipboard { + session: String, + text: String, + }, } #[derive(Clone, Copy, Debug)] @@ -671,6 +742,8 @@ impl Manager { let password = self.secret_fn(&p.id, "vnc-password", vc.secret_mode, prompt); let cb = self.terminal_callbacks(id, epoch); let (me, sid) = (self.clone(), id.to_string()); + let (me2, sid2) = (self.clone(), id.to_string()); + let clip: clients::vnc::ClipboardFn = Arc::new(move |text| me2.emit(Event::VncClipboard { session: sid2.clone(), text })); let c = connect_vnc_client( &host, port, @@ -680,6 +753,7 @@ impl Manager { tunnel.clone(), cb, Arc::new(move || me.emit(Event::VncFrame { session: sid.clone() })), + clip, ); match c { Ok(c) => { @@ -1192,6 +1266,7 @@ fn connect_vnc_client( tunnel: Option>, cb: Callbacks, on_frame: Arc, + on_clipboard: clients::vnc::ClipboardFn, ) -> Result> { let connector: Option = tunnel.map(|t| { let host = host.to_string(); @@ -1211,6 +1286,7 @@ fn connect_vnc_client( connector, connect_timeout: Duration::from_secs(timeout.max(1) as u64), on_frame, + on_clipboard, on_error: cb.on_error, on_close: cb.on_close, ..Default::default() From 1c16079584e22702c26ec896c4b752f30a117263 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:43:55 +0100 Subject: [PATCH 34/45] Remove the Go backend, JS frontend, Wails config and old build scripts; release.sh now wraps rust/release.sh --- .gitignore | 56 +- Makefile | 89 - build-all.sh | 262 -- build.ps1 | 139 - build.sh | 267 -- build/alchementary-link-ssh-icon.svg | 137 - build/appicon.png | Bin 76258 -> 0 bytes build/darwin/AppIcon.icns | Bin 314885 -> 0 bytes build/darwin/AppIcon.iconset/icon_128x128.png | Bin 9511 -> 0 bytes .../AppIcon.iconset/icon_128x128@2x.png | Bin 22157 -> 0 bytes build/darwin/AppIcon.iconset/icon_16x16.png | Bin 1236 -> 0 bytes .../darwin/AppIcon.iconset/icon_16x16@2x.png | Bin 1994 -> 0 bytes build/darwin/AppIcon.iconset/icon_256x256.png | Bin 22157 -> 0 bytes .../AppIcon.iconset/icon_256x256@2x.png | Bin 54200 -> 0 bytes build/darwin/AppIcon.iconset/icon_32x32.png | Bin 1994 -> 0 bytes .../darwin/AppIcon.iconset/icon_32x32@2x.png | Bin 4089 -> 0 bytes build/darwin/AppIcon.iconset/icon_512x512.png | Bin 54200 -> 0 bytes .../AppIcon.iconset/icon_512x512@2x.png | Bin 76258 -> 0 bytes build/darwin/Info.plist | 69 - build/linux/icons/alchementary-link-128.png | Bin 9511 -> 0 bytes build/linux/icons/alchementary-link-16.png | Bin 1236 -> 0 bytes build/linux/icons/alchementary-link-256.png | Bin 22157 -> 0 bytes build/linux/icons/alchementary-link-32.png | Bin 1994 -> 0 bytes build/linux/icons/alchementary-link-48.png | Bin 2981 -> 0 bytes build/linux/icons/alchementary-link-512.png | Bin 54200 -> 0 bytes build/linux/icons/alchementary-link-64.png | Bin 4089 -> 0 bytes build/releases/.gitkeep | 0 build/windows/icon.ico | Bin 372526 -> 0 bytes build/windows/info.json | 15 - build/windows/wails.exe.manifest | 15 - cmd/genfixtures/main.go | 89 - cmd/vscode-bridge/main.go | 357 --- cmd/vscode-bridge/main_test.go | 66 - frontend/embed.go | 13 - frontend/index.html | 352 --- frontend/package-lock.json | 547 ---- frontend/package.json | 25 - frontend/package.json.md5 | 1 - frontend/scripts/build.js | 233 -- frontend/scripts/bundle-novnc.js | 43 - frontend/scripts/copy-assets.js | 87 - frontend/scripts/novnc-entry.js | 7 - frontend/scripts/test-irc.js | 74 - frontend/scripts/test-regressions.js | 575 ---- frontend/scripts/test-session-lifecycle.js | 305 -- frontend/src/css/base.css | 187 -- frontend/src/css/components.css | 1230 -------- frontend/src/css/forms.css | 745 ----- frontend/src/css/github.css | 304 -- frontend/src/css/irc.css | 289 -- frontend/src/css/layout.css | 241 -- frontend/src/css/leviathan.css | 752 ----- frontend/src/css/lib/xterm.css | 285 -- frontend/src/css/modals.css | 888 ------ frontend/src/css/recordings.css | 201 -- frontend/src/css/sftp.css | 514 --- frontend/src/css/sidebar.css | 566 ---- frontend/src/css/splitpane.css | 223 -- frontend/src/css/tabs.css | 496 --- frontend/src/css/terminal.css | 479 --- frontend/src/css/tokens.css | 159 - frontend/src/css/vnc.css | 78 - frontend/src/js/app.js | 1958 ------------ frontend/src/js/commands.js | 446 --- frontend/src/js/github.js | 497 --- frontend/src/js/irc.js | 795 ----- frontend/src/js/leviathan.js | 1441 --------- frontend/src/js/lib/xterm-addon-fit.min.js | 2 - frontend/src/js/lib/xterm-addon-search.min.js | 2 - .../src/js/lib/xterm-addon-web-links.min.js | 2 - frontend/src/js/lib/xterm.min.js | 2 - frontend/src/js/notifications.js | 101 - frontend/src/js/profiles.js | 2769 ----------------- frontend/src/js/raw.js | 625 ---- frontend/src/js/recordings.js | 701 ----- frontend/src/js/secrets.js | 373 --- frontend/src/js/settings.js | 1276 -------- frontend/src/js/sftp.js | 1247 -------- frontend/src/js/sidebar.js | 939 ------ frontend/src/js/snippets.js | 454 --- frontend/src/js/splitpane.js | 797 ----- frontend/src/js/sshkeys.js | 337 -- frontend/src/js/state.js | 240 -- frontend/src/js/tabs.js | 933 ------ frontend/src/js/terminal.js | 1613 ---------- frontend/src/js/themes.js | 347 --- frontend/src/js/triggers.js | 381 --- frontend/src/js/utils.js | 402 --- frontend/src/js/vnc.js | 331 -- frontend/src/js/wails-service.js | 28 - frontend/wailsjs/go/api/Service.d.ts | 261 -- frontend/wailsjs/go/api/Service.js | 515 --- frontend/wailsjs/go/models.ts | 691 ---- frontend/wailsjs/runtime/package.json | 24 - frontend/wailsjs/runtime/runtime.d.ts | 330 -- frontend/wailsjs/runtime/runtime.js | 298 -- go.mod | 58 - go.sum | 156 - internal/api/api.go | 2262 -------------- internal/api/api_test.go | 387 --- internal/api/leviathan.go | 1035 ------ internal/app/app.go | 171 - internal/audit/audit.go | 100 - internal/config/config.go | 87 - internal/exportimport/exportimport.go | 209 -- internal/exportimport/exportimport_test.go | 173 - internal/githubclient/githubclient.go | 408 --- internal/githubclient/githubclient_test.go | 75 - internal/ircclient/ircclient.go | 833 ----- internal/ircclient/ircclient_test.go | 28 - internal/knownhosts/knownhosts.go | 98 - internal/knownhosts/knownhosts_test.go | 142 - internal/leviathan/analysis/carve.go | 876 ------ internal/leviathan/analysis/diff.go | 244 -- internal/leviathan/analysis/entropy.go | 125 - internal/leviathan/analysis/fuzzy.go | 224 -- internal/leviathan/analysis/hash.go | 63 - internal/leviathan/analysis/inspector.go | 162 - internal/leviathan/analysis/ioc.go | 89 - internal/leviathan/analysis/keys.go | 367 --- internal/leviathan/analysis/multisearch.go | 113 - internal/leviathan/analysis/overview.go | 88 - internal/leviathan/analysis/rules.go | 648 ---- internal/leviathan/analysis/search.go | 189 -- internal/leviathan/analysis/strings.go | 198 -- internal/leviathan/analysis/timestamps.go | 106 - internal/leviathan/binsnoop/README.md | 277 -- internal/leviathan/binsnoop/binsnoop.go | 177 -- internal/leviathan/binsnoop/common.go | 127 - internal/leviathan/binsnoop/common_test.go | 47 - internal/leviathan/binsnoop/extract.go | 124 - internal/leviathan/binsnoop/magic.go | 268 -- internal/leviathan/binsnoop/match.go | 134 - internal/leviathan/binsnoop/parse.go | 107 - internal/leviathan/binsnoop/scanner.go | 352 --- internal/leviathan/binsnoop/sig_archives.go | 1143 ------- .../leviathan/binsnoop/sig_compression.go | 1081 ------- internal/leviathan/binsnoop/sig_crypto.go | 795 ----- .../leviathan/binsnoop/sig_executables.go | 485 --- .../leviathan/binsnoop/sig_filesystems.go | 1289 -------- internal/leviathan/binsnoop/sig_firmware.go | 1333 -------- internal/leviathan/binsnoop/sig_images.go | 677 ---- internal/leviathan/binsnoop/sig_misc.go | 1399 --------- internal/leviathan/binsnoop/types.go | 117 - internal/leviathan/disasm/aarch64.go | 288 -- internal/leviathan/disasm/arm32.go | 209 -- internal/leviathan/disasm/cfg.go | 541 ---- internal/leviathan/disasm/image.go | 74 - internal/leviathan/disasm/mips.go | 144 - internal/leviathan/disasm/thumb.go | 117 - internal/leviathan/disasm/x86.go | 91 - internal/leviathan/disasm/x86_decode.go | 1172 ------- internal/leviathan/document/document.go | 439 --- internal/leviathan/document/session.go | 62 - internal/leviathan/parsers/asn1.go | 321 -- internal/leviathan/parsers/cert.go | 196 -- internal/leviathan/parsers/executable.go | 412 --- internal/leviathan/parsers/formats.go | 2182 ------------- internal/leviathan/parsers/formats_more.go | 662 ---- internal/leviathan/parsers/macho.go | 408 --- internal/leviathan/parsers/peimports.go | 150 - internal/leviathan/patch/script.go | 232 -- internal/leviathan/pdf/filters.go | 411 --- internal/leviathan/pdf/pdf.go | 352 --- internal/leviathan/pdf/pdf_test.go | 37 - internal/leviathan/pdf/surface.go | 128 - internal/leviathan/pdf/xref.go | 422 --- internal/leviathan/spectrogram/spectrogram.go | 798 ----- .../leviathan/spectrogram/spectrogram_test.go | 177 -- internal/leviathan/squashfs/squashfs.go | 782 ----- internal/leviathan/stego/metadata.go | 694 ----- internal/leviathan/stego/stego.go | 501 --- internal/leviathan/transforms/cipher.go | 402 --- internal/leviathan/transforms/transforms.go | 237 -- internal/localshell/localshell.go | 294 -- internal/localshell/localshell_test.go | 146 - internal/models/defaults.go | 390 --- internal/models/defaults_test.go | 10 - internal/models/models.go | 372 --- internal/preferences/preferences.go | 107 - internal/rawclient/encoding.go | 229 -- internal/rawclient/encoding_test.go | 226 -- internal/rawclient/history_test.go | 51 - internal/rawclient/rawclient.go | 332 -- internal/recording/recorder.go | 538 ---- internal/recording/recorder_test.go | 283 -- internal/secrets/keychain.go | 66 - internal/secrets/redact.go | 37 - internal/secrets/secrets.go | 357 --- internal/secrets/secrets_test.go | 537 ---- internal/sessions/manager.go | 2753 ---------------- internal/sessions/manager_test.go | 507 --- internal/sftpclient/sftpclient.go | 341 -- internal/sshclient/agent.go | 80 - internal/sshclient/hostkey.go | 162 - internal/sshclient/portforward.go | 269 -- internal/sshclient/sshclient.go | 816 ----- internal/sshclient/sshclient_test.go | 152 - internal/sshconfig/sshconfig.go | 302 -- internal/sshconfig/sshconfig_test.go | 218 -- internal/storage/migrations/001_initial.sql | 63 - .../migrations/002_github_protocol.sql | 59 - internal/storage/migrations/003_snippets.sql | 15 - .../storage/migrations/004_vnc_protocol.sql | 105 - .../storage/migrations/005_irc_protocol.sql | 105 - .../migrations/006_irc_channel_state.sql | 15 - internal/storage/migrations/migrations.go | 10 - internal/storage/storage_test.go | 695 ----- internal/storage/store.go | 939 ------ internal/telnetclient/telnetclient.go | 603 ---- internal/terminalenv/terminalenv.go | 137 - internal/terminalenv/terminalenv_test.go | 85 - internal/updater/updater.go | 303 -- internal/updater/updater_test.go | 123 - internal/validation/validation.go | 518 --- internal/validation/validation_test.go | 539 ---- internal/vncclient/vncclient.go | 340 -- internal/vncclient/vncclient_test.go | 61 - main.go | 79 - release.sh | 459 +-- rust/.gitignore | 1 - rust/core/tests/fixtures/README.md | 12 + scripts/build-common.sh | 61 - scripts/linux-release.Dockerfile | 30 - wails.json | 22 - 225 files changed, 20 insertions(+), 79839 deletions(-) delete mode 100644 Makefile delete mode 100755 build-all.sh delete mode 100644 build.ps1 delete mode 100755 build.sh delete mode 100644 build/alchementary-link-ssh-icon.svg delete mode 100644 build/appicon.png delete mode 100644 build/darwin/AppIcon.icns delete mode 100644 build/darwin/AppIcon.iconset/icon_128x128.png delete mode 100644 build/darwin/AppIcon.iconset/icon_128x128@2x.png delete mode 100644 build/darwin/AppIcon.iconset/icon_16x16.png delete mode 100644 build/darwin/AppIcon.iconset/icon_16x16@2x.png delete mode 100644 build/darwin/AppIcon.iconset/icon_256x256.png delete mode 100644 build/darwin/AppIcon.iconset/icon_256x256@2x.png delete mode 100644 build/darwin/AppIcon.iconset/icon_32x32.png delete mode 100644 build/darwin/AppIcon.iconset/icon_32x32@2x.png delete mode 100644 build/darwin/AppIcon.iconset/icon_512x512.png delete mode 100644 build/darwin/AppIcon.iconset/icon_512x512@2x.png delete mode 100644 build/darwin/Info.plist delete mode 100644 build/linux/icons/alchementary-link-128.png delete mode 100644 build/linux/icons/alchementary-link-16.png delete mode 100644 build/linux/icons/alchementary-link-256.png delete mode 100644 build/linux/icons/alchementary-link-32.png delete mode 100644 build/linux/icons/alchementary-link-48.png delete mode 100644 build/linux/icons/alchementary-link-512.png delete mode 100644 build/linux/icons/alchementary-link-64.png delete mode 100644 build/releases/.gitkeep delete mode 100644 build/windows/icon.ico delete mode 100644 build/windows/info.json delete mode 100644 build/windows/wails.exe.manifest delete mode 100644 cmd/genfixtures/main.go delete mode 100644 cmd/vscode-bridge/main.go delete mode 100644 cmd/vscode-bridge/main_test.go delete mode 100644 frontend/embed.go delete mode 100644 frontend/index.html delete mode 100644 frontend/package-lock.json delete mode 100644 frontend/package.json delete mode 100755 frontend/package.json.md5 delete mode 100644 frontend/scripts/build.js delete mode 100644 frontend/scripts/bundle-novnc.js delete mode 100644 frontend/scripts/copy-assets.js delete mode 100644 frontend/scripts/novnc-entry.js delete mode 100644 frontend/scripts/test-irc.js delete mode 100644 frontend/scripts/test-regressions.js delete mode 100644 frontend/scripts/test-session-lifecycle.js delete mode 100644 frontend/src/css/base.css delete mode 100644 frontend/src/css/components.css delete mode 100644 frontend/src/css/forms.css delete mode 100644 frontend/src/css/github.css delete mode 100644 frontend/src/css/irc.css delete mode 100644 frontend/src/css/layout.css delete mode 100644 frontend/src/css/leviathan.css delete mode 100644 frontend/src/css/lib/xterm.css delete mode 100644 frontend/src/css/modals.css delete mode 100644 frontend/src/css/recordings.css delete mode 100644 frontend/src/css/sftp.css delete mode 100644 frontend/src/css/sidebar.css delete mode 100644 frontend/src/css/splitpane.css delete mode 100644 frontend/src/css/tabs.css delete mode 100644 frontend/src/css/terminal.css delete mode 100644 frontend/src/css/tokens.css delete mode 100644 frontend/src/css/vnc.css delete mode 100644 frontend/src/js/app.js delete mode 100644 frontend/src/js/commands.js delete mode 100644 frontend/src/js/github.js delete mode 100644 frontend/src/js/irc.js delete mode 100644 frontend/src/js/leviathan.js delete mode 100644 frontend/src/js/lib/xterm-addon-fit.min.js delete mode 100644 frontend/src/js/lib/xterm-addon-search.min.js delete mode 100644 frontend/src/js/lib/xterm-addon-web-links.min.js delete mode 100644 frontend/src/js/lib/xterm.min.js delete mode 100644 frontend/src/js/notifications.js delete mode 100644 frontend/src/js/profiles.js delete mode 100644 frontend/src/js/raw.js delete mode 100644 frontend/src/js/recordings.js delete mode 100644 frontend/src/js/secrets.js delete mode 100644 frontend/src/js/settings.js delete mode 100644 frontend/src/js/sftp.js delete mode 100644 frontend/src/js/sidebar.js delete mode 100644 frontend/src/js/snippets.js delete mode 100644 frontend/src/js/splitpane.js delete mode 100644 frontend/src/js/sshkeys.js delete mode 100644 frontend/src/js/state.js delete mode 100644 frontend/src/js/tabs.js delete mode 100644 frontend/src/js/terminal.js delete mode 100644 frontend/src/js/themes.js delete mode 100644 frontend/src/js/triggers.js delete mode 100644 frontend/src/js/utils.js delete mode 100644 frontend/src/js/vnc.js delete mode 100644 frontend/src/js/wails-service.js delete mode 100755 frontend/wailsjs/go/api/Service.d.ts delete mode 100755 frontend/wailsjs/go/api/Service.js delete mode 100755 frontend/wailsjs/go/models.ts delete mode 100644 frontend/wailsjs/runtime/package.json delete mode 100644 frontend/wailsjs/runtime/runtime.d.ts delete mode 100644 frontend/wailsjs/runtime/runtime.js delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 internal/api/api.go delete mode 100644 internal/api/api_test.go delete mode 100644 internal/api/leviathan.go delete mode 100644 internal/app/app.go delete mode 100644 internal/audit/audit.go delete mode 100644 internal/config/config.go delete mode 100644 internal/exportimport/exportimport.go delete mode 100644 internal/exportimport/exportimport_test.go delete mode 100644 internal/githubclient/githubclient.go delete mode 100644 internal/githubclient/githubclient_test.go delete mode 100644 internal/ircclient/ircclient.go delete mode 100644 internal/ircclient/ircclient_test.go delete mode 100644 internal/knownhosts/knownhosts.go delete mode 100644 internal/knownhosts/knownhosts_test.go delete mode 100644 internal/leviathan/analysis/carve.go delete mode 100644 internal/leviathan/analysis/diff.go delete mode 100644 internal/leviathan/analysis/entropy.go delete mode 100644 internal/leviathan/analysis/fuzzy.go delete mode 100644 internal/leviathan/analysis/hash.go delete mode 100644 internal/leviathan/analysis/inspector.go delete mode 100644 internal/leviathan/analysis/ioc.go delete mode 100644 internal/leviathan/analysis/keys.go delete mode 100644 internal/leviathan/analysis/multisearch.go delete mode 100644 internal/leviathan/analysis/overview.go delete mode 100644 internal/leviathan/analysis/rules.go delete mode 100644 internal/leviathan/analysis/search.go delete mode 100644 internal/leviathan/analysis/strings.go delete mode 100644 internal/leviathan/analysis/timestamps.go delete mode 100644 internal/leviathan/binsnoop/README.md delete mode 100644 internal/leviathan/binsnoop/binsnoop.go delete mode 100644 internal/leviathan/binsnoop/common.go delete mode 100644 internal/leviathan/binsnoop/common_test.go delete mode 100644 internal/leviathan/binsnoop/extract.go delete mode 100644 internal/leviathan/binsnoop/magic.go delete mode 100644 internal/leviathan/binsnoop/match.go delete mode 100644 internal/leviathan/binsnoop/parse.go delete mode 100644 internal/leviathan/binsnoop/scanner.go delete mode 100644 internal/leviathan/binsnoop/sig_archives.go delete mode 100644 internal/leviathan/binsnoop/sig_compression.go delete mode 100644 internal/leviathan/binsnoop/sig_crypto.go delete mode 100644 internal/leviathan/binsnoop/sig_executables.go delete mode 100644 internal/leviathan/binsnoop/sig_filesystems.go delete mode 100644 internal/leviathan/binsnoop/sig_firmware.go delete mode 100644 internal/leviathan/binsnoop/sig_images.go delete mode 100644 internal/leviathan/binsnoop/sig_misc.go delete mode 100644 internal/leviathan/binsnoop/types.go delete mode 100644 internal/leviathan/disasm/aarch64.go delete mode 100644 internal/leviathan/disasm/arm32.go delete mode 100644 internal/leviathan/disasm/cfg.go delete mode 100644 internal/leviathan/disasm/image.go delete mode 100644 internal/leviathan/disasm/mips.go delete mode 100644 internal/leviathan/disasm/thumb.go delete mode 100644 internal/leviathan/disasm/x86.go delete mode 100644 internal/leviathan/disasm/x86_decode.go delete mode 100644 internal/leviathan/document/document.go delete mode 100644 internal/leviathan/document/session.go delete mode 100644 internal/leviathan/parsers/asn1.go delete mode 100644 internal/leviathan/parsers/cert.go delete mode 100644 internal/leviathan/parsers/executable.go delete mode 100644 internal/leviathan/parsers/formats.go delete mode 100644 internal/leviathan/parsers/formats_more.go delete mode 100644 internal/leviathan/parsers/macho.go delete mode 100644 internal/leviathan/parsers/peimports.go delete mode 100644 internal/leviathan/patch/script.go delete mode 100644 internal/leviathan/pdf/filters.go delete mode 100644 internal/leviathan/pdf/pdf.go delete mode 100644 internal/leviathan/pdf/pdf_test.go delete mode 100644 internal/leviathan/pdf/surface.go delete mode 100644 internal/leviathan/pdf/xref.go delete mode 100644 internal/leviathan/spectrogram/spectrogram.go delete mode 100644 internal/leviathan/spectrogram/spectrogram_test.go delete mode 100644 internal/leviathan/squashfs/squashfs.go delete mode 100644 internal/leviathan/stego/metadata.go delete mode 100644 internal/leviathan/stego/stego.go delete mode 100644 internal/leviathan/transforms/cipher.go delete mode 100644 internal/leviathan/transforms/transforms.go delete mode 100644 internal/localshell/localshell.go delete mode 100644 internal/localshell/localshell_test.go delete mode 100644 internal/models/defaults.go delete mode 100644 internal/models/defaults_test.go delete mode 100644 internal/models/models.go delete mode 100644 internal/preferences/preferences.go delete mode 100644 internal/rawclient/encoding.go delete mode 100644 internal/rawclient/encoding_test.go delete mode 100644 internal/rawclient/history_test.go delete mode 100644 internal/rawclient/rawclient.go delete mode 100644 internal/recording/recorder.go delete mode 100644 internal/recording/recorder_test.go delete mode 100644 internal/secrets/keychain.go delete mode 100644 internal/secrets/redact.go delete mode 100644 internal/secrets/secrets.go delete mode 100644 internal/secrets/secrets_test.go delete mode 100644 internal/sessions/manager.go delete mode 100644 internal/sessions/manager_test.go delete mode 100644 internal/sftpclient/sftpclient.go delete mode 100644 internal/sshclient/agent.go delete mode 100644 internal/sshclient/hostkey.go delete mode 100644 internal/sshclient/portforward.go delete mode 100644 internal/sshclient/sshclient.go delete mode 100644 internal/sshclient/sshclient_test.go delete mode 100644 internal/sshconfig/sshconfig.go delete mode 100644 internal/sshconfig/sshconfig_test.go delete mode 100644 internal/storage/migrations/001_initial.sql delete mode 100644 internal/storage/migrations/002_github_protocol.sql delete mode 100644 internal/storage/migrations/003_snippets.sql delete mode 100644 internal/storage/migrations/004_vnc_protocol.sql delete mode 100644 internal/storage/migrations/005_irc_protocol.sql delete mode 100644 internal/storage/migrations/006_irc_channel_state.sql delete mode 100644 internal/storage/migrations/migrations.go delete mode 100644 internal/storage/storage_test.go delete mode 100644 internal/storage/store.go delete mode 100644 internal/telnetclient/telnetclient.go delete mode 100644 internal/terminalenv/terminalenv.go delete mode 100644 internal/terminalenv/terminalenv_test.go delete mode 100644 internal/updater/updater.go delete mode 100644 internal/updater/updater_test.go delete mode 100644 internal/validation/validation.go delete mode 100644 internal/validation/validation_test.go delete mode 100644 internal/vncclient/vncclient.go delete mode 100644 internal/vncclient/vncclient_test.go delete mode 100644 main.go delete mode 100644 rust/.gitignore create mode 100644 rust/core/tests/fixtures/README.md delete mode 100644 scripts/build-common.sh delete mode 100644 scripts/linux-release.Dockerfile delete mode 100644 wails.json diff --git a/.gitignore b/.gitignore index 04f1d71..65b0396 100644 --- a/.gitignore +++ b/.gitignore @@ -1,42 +1,10 @@ # ========================================================================== -# Alchementary Link — .gitignore +# Nexus Terminal — .gitignore # ========================================================================== # ── Build output ────────────────────────────────────────────────────────── -build/bin/ -dist/ - -# ── VSCode extensions: platform-specific build artifacts (never commit) ──── -# The Go bridge is compiled per-host by vscode/build.sh and staged into each -# extension's bin/ — committing it ships a binary for the wrong OS/arch. -vscode/bin/ -vscode/dist/ -vscode/*/bin/ - -# ── Build platform resources (tracked — icons, plists, manifests) ───────── -# build/darwin/, build/windows/, build/linux/ are tracked for icons & metadata - -# ── Go ──────────────────────────────────────────────────────────────────── -vendor/ -.tools/ -.cache/ -*.exe -*.exe~ -*.dll -*.so -*.dylib -*.test -*.out -__debug_bin* -coverage.out -coverage.html - -# ── Frontend ────────────────────────────────────────────────────────────── -frontend/node_modules/ -frontend/src/js/lib/ - -# ── Wails auto-generated bindings (recreated on every build) ────────────── -frontend/wailsjs/ +rust/target/ +build/releases/ # ── IDE / editor ────────────────────────────────────────────────────────── .idea/ @@ -44,20 +12,12 @@ frontend/wailsjs/ *.swp *.swo *~ -*.sublime-project -*.sublime-workspace -.project -.classpath -.settings/ # ── OS metadata ─────────────────────────────────────────────────────────── .DS_Store -.DS_Store? ._* Thumbs.db -ehthumbs.db Desktop.ini -$RECYCLE.BIN/ # ── Application data (never commit user data or secrets) ────────────────── *.db @@ -66,13 +26,9 @@ $RECYCLE.BIN/ vault.json *.pem *.key -!build/appicon.png - -# Builds -build/releases/v* -build/releases/manifest* - -# Rust port compatibility fixtures (generated by cmd/genfixtures) +# Compatibility fixtures generated from the last Go build are deliberate test data. !rust/core/tests/fixtures/** + +# Agent worktrees .claude/worktrees/ diff --git a/Makefile b/Makefile deleted file mode 100644 index 9f6b1ba..0000000 --- a/Makefile +++ /dev/null @@ -1,89 +0,0 @@ -.PHONY: all build build-compressed dev clean test test-cover lint install deps frontend-deps upx-check run package release help - -# Resolve the Wails binary. Prefer wails on PATH; fall back to GOPATH/bin. -WAILS := $(or $(shell which wails 2>/dev/null),$(shell go env GOPATH)/bin/wails) -VERSION := $(shell git describe --tags --abbrev=0 2>/dev/null || echo dev)-$(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) -LD_UPDATE_URL := $(if $(LINK_UPDATE_URL),-X github.com/andydixon/link/internal/api.appUpdateURL=$(LINK_UPDATE_URL),) -UPX_FLAGS ?= -LINK_FORCE_MACOS_UPX ?= 0 -GOBIN_DIR := $(shell go env GOPATH)/bin -export PATH := $(GOBIN_DIR):$(PATH) - -# Default target -all: build - -# Install all dependencies -deps: frontend-deps - go mod download - -upx-check: - @command -v upx >/dev/null 2>&1 || { echo "UPX is required for compressed builds. Install it first, for example: brew install upx"; exit 1; } - @if [ "$$(uname -s)" = "Darwin" ] && [ "$(LINK_FORCE_MACOS_UPX)" != "1" ]; then \ - echo "UPX compression is disabled for macOS by default. Use ./release.sh for a zipped .app, or run LINK_FORCE_MACOS_UPX=1 UPX_FLAGS='--force-macos --best --no-color --no-progress' make build-compressed."; \ - exit 1; \ - fi - @if [ "$$(uname -s)" = "Darwin" ] && [ "$(LINK_FORCE_MACOS_UPX)" = "1" ] && ! printf '%s\n' "$(UPX_FLAGS)" | grep -q -- '--force-macos'; then \ - echo "macOS UPX builds must pass --force-macos in UPX_FLAGS."; \ - exit 1; \ - fi - -# Install frontend dependencies -frontend-deps: - cd frontend && npm install - -# Development mode with hot reload -dev: deps - $(WAILS) dev - -# Production build -build: deps - CGO_ENABLED=0 $(WAILS) build -trimpath -ldflags "-s -w -buildid= -X github.com/andydixon/link/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)" - -# Production build with UPX compression -build-compressed: deps upx-check - CGO_ENABLED=0 $(WAILS) build -trimpath -upx $(if $(UPX_FLAGS),-upxflags "$(UPX_FLAGS)",) -ldflags "-s -w -buildid= -X github.com/andydixon/link/internal/api.appVersion=$(VERSION) $(LD_UPDATE_URL)" - -# Run tests -test: - go test ./... -v -count=1 - -# Run tests with coverage -test-cover: - go test ./... -v -count=1 -coverprofile=coverage.out - go tool cover -html=coverage.out -o coverage.html - -# Clean build artefacts -clean: - rm -rf build/bin - rm -rf frontend/node_modules - rm -rf frontend/src/js/lib - rm -rf frontend/src/css/lib - go clean -testcache - -# Lint (requires golangci-lint) -lint: - golangci-lint run ./... - -# Package for distribution -package: build - @echo "Binary built at build/bin/" - -# Build and package release artefacts for all platforms -release: - ./release.sh - -# Help -help: - @echo "Alchementary Link — Build Targets" - @echo "" - @echo " make deps — Install all dependencies" - @echo " make dev — Run in development mode with hot reload" - @echo " make build — Production build" - @echo " make build-compressed — Production build with UPX compression" - @echo " make test — Run tests" - @echo " make test-cover — Run tests with coverage report" - @echo " make clean — Clean build artefacts" - @echo " make lint — Run linter (requires golangci-lint)" - @echo " make package — Build and package for distribution" - @echo " make release — Build multi-platform release artefacts" - @echo " make help — Show this help" diff --git a/build-all.sh b/build-all.sh deleted file mode 100755 index 9bdbe57..0000000 --- a/build-all.sh +++ /dev/null @@ -1,262 +0,0 @@ -#!/usr/bin/env bash -# build-all.sh — Cross-build helper for Alchementary Link (macOS/Linux hosts) -# Builds all supported targets where the required toolchains are installed. -# Usage: ./build-all.sh -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" -source "$SCRIPT_DIR/scripts/build-common.sh" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -log() { echo -e "${GREEN}[link]${NC} $1"; } -warn() { echo -e "${YELLOW}[link]${NC} $1"; } -err() { echo -e "${RED}[link]${NC} $1" >&2; } - -MIN_GO_VERSION="1.25.12" -WAILS_VERSION="$(awk '$1 == "github.com/wailsapp/wails/v2" { print $2; exit }' go.mod)" -WAILS_BIN="" -WAILS_USE_GO_RUN=0 -BUILT_TARGETS=0 -SKIPPED_TARGETS=0 - -if [ -x "$SCRIPT_DIR/.tools/go/bin/go" ]; then - export PATH="$SCRIPT_DIR/.tools/go/bin:$PATH" -fi -export GOCACHE="${GOCACHE:-$SCRIPT_DIR/.cache/go-build}" -export GOPATH="${GOPATH:-$SCRIPT_DIR/.cache/go}" -mkdir -p "$GOCACHE" "$GOPATH" -export PATH="$GOPATH/bin:$PATH" - -version_at_least() { - local have="${1#go}" - local want="$2" - local IFS=. - local have_parts want_parts - read -r -a have_parts <<< "$have" - read -r -a want_parts <<< "$want" - for i in 0 1 2; do - local h="${have_parts[$i]:-0}" - local w="${want_parts[$i]:-0}" - if (( h > w )); then return 0; fi - if (( h < w )); then return 1; fi - done - return 0 -} - -check_go() { - command -v go >/dev/null 2>&1 || { err "Go is not installed"; exit 1; } - - local version - version="$(go env GOVERSION 2>/dev/null || go version | awk '{print $3}')" - if ! version_at_least "$version" "$MIN_GO_VERSION"; then - err "Go ${MIN_GO_VERSION}+ is required by go.mod; found ${version} at $(command -v go)" - err "Install a newer Go, or place it at .tools/go/bin/go for this project." - exit 1 - fi -} - -resolve_wails() { - if WAILS_BIN="$(command -v wails 2>/dev/null)"; then - WAILS_USE_GO_RUN=0 - return - fi - - local gopath - gopath="$(go env GOPATH 2>/dev/null || true)" - if [ -n "$gopath" ] && [ -x "$gopath/bin/wails" ]; then - WAILS_BIN="$gopath/bin/wails" - WAILS_USE_GO_RUN=0 - return - fi - - if [ -n "$WAILS_VERSION" ]; then - WAILS_BIN="github.com/wailsapp/wails/v2/cmd/wails@${WAILS_VERSION}" - WAILS_USE_GO_RUN=1 - warn "Wails CLI not found; using pinned ${WAILS_BIN} via go run" - return - fi - - err "Wails is not installed and no Wails version was found in go.mod" - exit 1 -} - -wails_cmd() { - if [ "$WAILS_USE_GO_RUN" -eq 1 ]; then - go run "$WAILS_BIN" "$@" - else - "$WAILS_BIN" "$@" - fi -} - -ensure_upx() { - if ! link_bool_enabled "${LINK_COMPRESS:-0}"; then - return - fi - if command -v upx >/dev/null 2>&1; then - return - fi - warn "UPX is not installed; compressed targets will be skipped unless the target does not use UPX." -} - -linux_deps_ok() { - command -v pkg-config >/dev/null 2>&1 || return 1 - pkg-config --exists gtk+-3.0 || return 1 - if ! pkg-config --exists webkit2gtk-4.1 && ! pkg-config --exists webkit2gtk-4.0; then - return 1 - fi - if ! pkg-config --exists ayatana-appindicator3-0.1 && ! pkg-config --exists appindicator3-0.1; then - return 1 - fi -} - -build_tags() { - if command -v pkg-config >/dev/null 2>&1 \ - && pkg-config --exists webkit2gtk-4.1 \ - && ! pkg-config --exists webkit2gtk-4.0; then - echo "webkit2_41" - fi -} - -build_version() { - local tag - local commit - tag="$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")" - commit="$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")" - echo "${tag}-${commit}" -} - -build_ldflags() { - local version="$1" - local flags="-s -w -X github.com/andydixon/link/internal/api.appVersion=${version}" - if [ -n "${LINK_UPDATE_URL:-}" ]; then - flags="${flags} -X github.com/andydixon/link/internal/api.appUpdateURL=${LINK_UPDATE_URL}" - fi - echo "${flags}" -} - -check_deps() { - check_go - command -v node >/dev/null 2>&1 || { err "Node.js is not installed"; exit 1; } - command -v npm >/dev/null 2>&1 || { err "npm is not installed"; exit 1; } - resolve_wails - ensure_upx - - log "Go: $(go version)" - log "Node: $(node --version)" - log "Wails: $(wails_cmd version 2>/dev/null | head -1)" -} - -install_deps() { - log "Installing Go dependencies…" - go mod download - - log "Installing frontend dependencies…" - if [ -f frontend/package-lock.json ]; then - (cd frontend && npm ci) - else - (cd frontend && npm install) - fi -} - -host_os() { - case "$(uname -s)" in - Darwin) echo "darwin" ;; - Linux) echo "linux" ;; - *) echo "unknown" ;; - esac -} - -host_arch() { - case "$(uname -m)" in - x86_64|amd64) echo "amd64" ;; - arm64|aarch64) echo "arm64" ;; - *) echo "unknown" ;; - esac -} - -build_target() { - local target_os="$1" - local target_arch="$2" - local host="$3" - local host_arch_value="$4" - local version="$5" - local platform="${target_os}/${target_arch}" - link_configure_pure_go_build - local ldflags - ldflags="$(build_ldflags "${version}")" - local tags - tags="$(build_tags)" - local build_args=("-platform" "$target_os/$target_arch" "-trimpath") - if link_use_upx_for_target "$target_os"; then - if ! command -v upx >/dev/null 2>&1; then - warn "Skipping ${platform} (LINK_COMPRESS=1 requires UPX)" - ((SKIPPED_TARGETS += 1)) - return 0 - fi - build_args+=("-upx") - if [ -n "${LINK_UPX_FLAGS:-}" ]; then - build_args+=("-upxflags" "$LINK_UPX_FLAGS") - fi - elif link_skips_upx_for_target "$target_os"; then - warn "Skipping UPX compression for ${platform}; distribute the zipped .app instead, or set LINK_FORCE_MACOS_UPX=1 with suitable LINK_UPX_FLAGS." - fi - if [ -n "$tags" ]; then - build_args+=("-tags" "$tags") - fi - build_args+=("-ldflags" "$ldflags") - - if [ "$target_os" = "linux" ] && ! linux_deps_ok; then - warn "Skipping ${platform} (missing Linux GTK/WebKit build dependencies)" - ((SKIPPED_TARGETS += 1)) - return 0 - fi - - log "Building ${platform}…" - CGO_ENABLED=0 wails_cmd build "${build_args[@]}" - ((BUILT_TARGETS += 1)) -} - -main() { - check_deps - install_deps - - local version - version="$(build_version)" - - local host - host="$(host_os)" - local arch - arch="$(host_arch)" - if [ "$host" = "unknown" ] || [ "$arch" = "unknown" ]; then - err "Unsupported host platform: $(uname -s)/$(uname -m)" - exit 1 - fi - - build_target "$host" "$arch" "$host" "$arch" "$version" - - # Cross-build Linux and Windows targets where toolchains are available - if [ "$host" = "darwin" ] || [ "$host" = "linux" ]; then - for target in linux/amd64 linux/arm64 windows/amd64 windows/arm64; do - local target_os="${target%%/*}" - local target_arch="${target##*/}" - if [ "$target_os" = "$host" ] && [ "$target_arch" = "$arch" ]; then - continue - fi - build_target "$target_os" "$target_arch" "$host" "$arch" "$version" - done - fi - - if [ "$BUILT_TARGETS" -eq 0 ]; then - err "No targets were built; ${SKIPPED_TARGETS} target(s) were skipped because required system toolchains are missing." - exit 1 - fi - - log "Builds complete (${BUILT_TARGETS} built, ${SKIPPED_TARGETS} skipped). Output: build/bin/" -} - -main "$@" diff --git a/build.ps1 b/build.ps1 deleted file mode 100644 index 8c62969..0000000 --- a/build.ps1 +++ /dev/null @@ -1,139 +0,0 @@ -# build.ps1 — Windows build script for Alchementary Link -# Usage: .\build.ps1 [dev|build|test|clean|deps] -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -function Write-Info($Message) { Write-Host "[link] $Message" -ForegroundColor Green } -function Write-Warn($Message) { Write-Host "[link] $Message" -ForegroundColor Yellow } -function Write-Err($Message) { Write-Host "[link] $Message" -ForegroundColor Red } - -function Test-BoolEnabled([string]$Value) { - return $Value -match '^(1|true|TRUE|yes|YES|on|ON)$' -} - -function Get-WailsPath { - $wails = Get-Command wails -ErrorAction SilentlyContinue - if ($wails) { return $wails.Source } - - $goPath = & go env GOPATH - $fallback = Join-Path $goPath 'bin\wails.exe' - if (Test-Path $fallback) { return $fallback } - return $null -} - -function Check-Deps { - if (-not (Get-Command go -ErrorAction SilentlyContinue)) { throw 'Go is not installed.' } - if (-not (Get-Command node -ErrorAction SilentlyContinue)) { throw 'Node.js is not installed.' } - if (-not (Get-Command npm -ErrorAction SilentlyContinue)) { throw 'npm is not installed.' } - - $wailsPath = Get-WailsPath - if (-not $wailsPath) { - throw 'Wails is not installed. Run: go install github.com/wailsapp/wails/v2/cmd/wails@latest' - } - - Write-Info "Go: $(& go version)" - Write-Info "Node: $(& node --version)" - Write-Info "Wails: $(& $wailsPath version | Select-Object -First 1)" - - return $wailsPath -} - -function Install-Deps { - Write-Info 'Installing Go dependencies...' - & go mod download - - Write-Info 'Installing frontend dependencies...' - Push-Location 'frontend' - & npm install - Pop-Location -} - -function Get-BuildVersion { - try { - $tag = & git describe --tags --abbrev=0 2>$null - if (-not $tag) { $tag = 'dev' } - } catch { - $tag = 'dev' - } - - try { - $commit = & git rev-parse --short HEAD 2>$null - if (-not $commit) { $commit = 'unknown' } - } catch { - $commit = 'unknown' - } - - return "$tag-$commit" -} - -function Get-Ldflags([string]$Version) { - $flags = "-s -w -buildid= -X github.com/andydixon/link/internal/api.appVersion=$Version" - if ($env:LINK_UPDATE_URL) { - $flags += " -X github.com/andydixon/link/internal/api.appUpdateURL=$($env:LINK_UPDATE_URL)" - } - return $flags -} - -function Dev { - $wailsPath = Check-Deps - Install-Deps - Write-Info 'Starting development mode...' - & $wailsPath dev -} - -function Build { - $wailsPath = Check-Deps - Install-Deps - $version = Get-BuildVersion - $ldflags = Get-Ldflags $version - - $env:CGO_ENABLED = '0' - Remove-Item Env:CGO_CFLAGS -ErrorAction SilentlyContinue - Remove-Item Env:CGO_CXXFLAGS -ErrorAction SilentlyContinue - - $buildArgs = @('build', '-trimpath') - if (Test-BoolEnabled $env:LINK_COMPRESS) { - if (-not (Get-Command upx -ErrorAction SilentlyContinue)) { - throw 'LINK_COMPRESS=1 requires UPX. Install it first.' - } - $buildArgs += '-upx' - if ($env:LINK_UPX_FLAGS) { - $buildArgs += @('-upxflags', $env:LINK_UPX_FLAGS) - } - } - $buildArgs += @('-ldflags', $ldflags) - - Write-Info 'Building production binary (CGO_ENABLED=0)...' - & $wailsPath @buildArgs - Write-Info 'Build complete: build\bin\' -} - -function Run-Tests { - Write-Info 'Running tests...' - & go test ./... -v -count=1 -} - -function Clean { - Write-Info 'Cleaning build artefacts...' - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue build\bin - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue frontend\node_modules - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue frontend\src\js\lib - Remove-Item -Recurse -Force -ErrorAction SilentlyContinue frontend\src\css\lib - & go clean -testcache - Write-Info 'Clean complete' -} - -$task = $args[0] -if (-not $task) { $task = 'build' } - -switch ($task) { - 'dev' { Dev } - 'build' { Build } - 'test' { Run-Tests } - 'clean' { Clean } - 'deps' { Check-Deps | Out-Null; Install-Deps } - default { - Write-Err 'Usage: .\\build.ps1 [dev|build|test|clean|deps]' - exit 1 - } -} diff --git a/build.sh b/build.sh deleted file mode 100755 index a7eb273..0000000 --- a/build.sh +++ /dev/null @@ -1,267 +0,0 @@ -#!/usr/bin/env bash -# build.sh — Build script for Alchementary Link -# Provides a reproducible build process for CI and local builds. -# Usage: ./build.sh [dev|build|test|clean|deps] -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" -source "$SCRIPT_DIR/scripts/build-common.sh" - -# Colours for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' - -log() { echo -e "${GREEN}[link]${NC} $1"; } -warn() { echo -e "${YELLOW}[link]${NC} $1"; } -err() { echo -e "${RED}[link]${NC} $1" >&2; } - -MIN_GO_VERSION="1.25.12" -WAILS_VERSION="$(link_wails_version go.mod)" -WAILS_BIN="" -WAILS_USE_GO_RUN=0 - -if [ -x "$SCRIPT_DIR/.tools/go/bin/go" ]; then - export PATH="$SCRIPT_DIR/.tools/go/bin:$PATH" -fi -export GOCACHE="${GOCACHE:-$SCRIPT_DIR/.cache/go-build}" -export GOPATH="${GOPATH:-$SCRIPT_DIR/.cache/go}" -mkdir -p "$GOCACHE" "$GOPATH" -export PATH="$GOPATH/bin:$PATH" - -version_at_least() { - local have="${1#go}" - local want="$2" - local IFS=. - local have_parts want_parts - read -r -a have_parts <<< "$have" - read -r -a want_parts <<< "$want" - for i in 0 1 2; do - local h="${have_parts[$i]:-0}" - local w="${want_parts[$i]:-0}" - if (( h > w )); then return 0; fi - if (( h < w )); then return 1; fi - done - return 0 -} - -check_go() { - command -v go >/dev/null 2>&1 || { err "Go is not installed"; exit 1; } - - local version - version="$(go env GOVERSION 2>/dev/null || go version | awk '{print $3}')" - if ! version_at_least "$version" "$MIN_GO_VERSION"; then - err "Go ${MIN_GO_VERSION}+ is required by go.mod; found ${version} at $(command -v go)" - err "Install a newer Go, or place it at .tools/go/bin/go for this project." - exit 1 - fi -} - -resolve_wails() { - if WAILS_BIN="$(command -v wails 2>/dev/null)"; then - WAILS_USE_GO_RUN=0 - return - fi - - local gopath - gopath="$(go env GOPATH 2>/dev/null || true)" - if [ -n "$gopath" ] && [ -x "$gopath/bin/wails" ]; then - WAILS_BIN="$gopath/bin/wails" - WAILS_USE_GO_RUN=0 - return - fi - - if [ -n "$WAILS_VERSION" ]; then - WAILS_BIN="github.com/wailsapp/wails/v2/cmd/wails@${WAILS_VERSION}" - WAILS_USE_GO_RUN=1 - warn "Wails CLI not found; using pinned ${WAILS_BIN} via go run" - return - fi - - err "Wails is not installed and no Wails version was found in go.mod" - exit 1 -} - -wails_cmd() { - if [ "$WAILS_USE_GO_RUN" -eq 1 ]; then - go run "$WAILS_BIN" "$@" - else - "$WAILS_BIN" "$@" - fi -} - -ensure_upx() { - if ! link_bool_enabled "${LINK_COMPRESS:-0}"; then - return - fi - if link_skips_upx_for_target "$(host_os)"; then - return - fi - if command -v upx >/dev/null 2>&1; then - return - fi - err "LINK_COMPRESS=1 requires UPX. Install it first, for example: brew install upx" - exit 1 -} - -check_linux_deps() { - if [ "$(uname -s)" != "Linux" ]; then - return - fi - - command -v pkg-config >/dev/null 2>&1 || { - err "pkg-config is required for Linux Wails builds." - err "On Ubuntu/Pop!_OS install: sudo apt-get install -y build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev" - exit 1 - } - - local missing=() - pkg-config --exists gtk+-3.0 || missing+=("gtk+-3.0") - if ! pkg-config --exists webkit2gtk-4.1 && ! pkg-config --exists webkit2gtk-4.0; then - missing+=("webkit2gtk-4.1 or webkit2gtk-4.0") - fi - if ! pkg-config --exists ayatana-appindicator3-0.1 && ! pkg-config --exists appindicator3-0.1; then - missing+=("ayatana-appindicator3-0.1 or appindicator3-0.1") - fi - - if [ "${#missing[@]}" -gt 0 ]; then - err "Missing Linux build dependencies: ${missing[*]}" - err "On Ubuntu/Pop!_OS install: sudo apt-get install -y build-essential pkg-config libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev" - exit 1 - fi -} - -build_tags() { - link_build_tags -} - -host_os() { - case "$(uname -s)" in - Darwin) echo "darwin" ;; - Linux) echo "linux" ;; - *) echo "unknown" ;; - esac -} - -build_version() { - link_build_version -} - -build_ldflags() { - link_build_ldflags "$1" -} - -# Check prerequisites -check_deps() { - check_go - command -v node >/dev/null 2>&1 || { err "Node.js is not installed"; exit 1; } - command -v npm >/dev/null 2>&1 || { err "npm is not installed"; exit 1; } - check_linux_deps - resolve_wails - ensure_upx - - log "All prerequisites found" - log "Go: $(go version)" - log "Node: $(node --version)" - log "Wails: $(wails_cmd version 2>/dev/null | head -1)" -} - -# Install dependencies -install_deps() { - log "Installing Go dependencies…" - go mod download - - log "Installing frontend dependencies…" - if [ -f frontend/package-lock.json ]; then - (cd frontend && npm ci) - else - (cd frontend && npm install) - fi - - log "Dependencies installed" -} - -# Development mode -dev() { - check_deps - install_deps - log "Starting development mode…" - wails_cmd dev -} - -# Production build -build() { - check_deps - install_deps - log "Building production binary…" - local version - version="$(build_version)" - local tags - tags="$(build_tags)" - link_configure_pure_go_build - local build_args=("-trimpath") - if link_bool_enabled "${LINK_COMPRESS:-0}"; then - if link_skips_upx_for_target "$(host_os)"; then - warn "Skipping UPX compression on macOS; distribute the zipped .app instead, or set LINK_FORCE_MACOS_UPX=1 with suitable LINK_UPX_FLAGS." - else - log "Using UPX compression" - build_args+=("-upx") - if [ -n "${LINK_UPX_FLAGS:-}" ]; then - build_args+=("-upxflags" "$LINK_UPX_FLAGS") - fi - fi - fi - if [ -n "$tags" ]; then - log "Using Wails build tags: ${tags}" - wails_cmd build "${build_args[@]}" -tags "$tags" -ldflags "$(build_ldflags "${version}")" - else - wails_cmd build "${build_args[@]}" -ldflags "$(build_ldflags "${version}")" - fi - - # Replace Wails-generated icon with custom app icon on macOS. - if [[ "$OSTYPE" == darwin* ]]; then - local app_bundle="build/bin/alchementary-link.app" - local custom_icns="build/darwin/AppIcon.icns" - if [ -d "$app_bundle" ] && [ -f "$custom_icns" ]; then - cp "$custom_icns" "$app_bundle/Contents/Resources/iconfile.icns" - log "Replaced app icon with custom AppIcon.icns" - fi - fi - - log "Build complete: build/bin/" -} - -# Run tests -run_tests() { - check_go - log "Running tests…" - go test ./... -v -count=1 - (cd frontend && npm test) -} - -# Clean build artefacts -clean() { - log "Cleaning build artefacts…" - rm -rf build/bin - rm -rf frontend/node_modules - rm -rf frontend/src/js/lib - if command -v go >/dev/null 2>&1; then - go clean -testcache - fi - log "Clean complete" -} - -# Main -case "${1:-build}" in - dev) dev ;; - build) build ;; - test) run_tests ;; - clean) clean ;; - deps) check_deps && install_deps ;; - *) - echo "Usage: $0 [dev|build|test|clean|deps]" - exit 1 - ;; -esac diff --git a/build/alchementary-link-ssh-icon.svg b/build/alchementary-link-ssh-icon.svg deleted file mode 100644 index cded489..0000000 --- a/build/alchementary-link-ssh-icon.svg +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - link — ssh - - - - - - - user@link:~$ - - - >_ - - - - - - SSH v2 AES-256 - - - - - - diff --git a/build/appicon.png b/build/appicon.png deleted file mode 100644 index 05101d58ccb8fc48d7941edf77ffdaf39985a5b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76258 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

SP1go(|2~8jq^skw`|4!@;Bxd)@i!krx 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

HR?O2?0ywZ$C{hoRub#b4nrmxLhr_gaT zr@!ESrL{sRR}NJNFM-c%xJFOI96rZx**xl^?H#H2N&8exq&Ba2pB~9q=sg^4dw3_A ziK*%?OLTB&#D+)=$HWVP(S!F_ZYaMDqqA2Sx^i4*+wkZkLQ3x|0k=`ql2671=C}lR zIyR|iOar3HggoOdrG<%ZgA5TThxhO*l>*FJq5bfQdG3cVfZOAlaMHuePsm+u8y;U3 zdq%B)PK|%uYMCxX1?OA_pXMbop%cV8p}j6b*OZ&jF7V^69)}r zyLs!SZQg)(3vC>_fv41|7gp4xo^cjzGsrvo^*s|iDZ9lk;bucJn1@er9Kw1;Z22Vp z9SsS})sN|&+gh#6{MQ~qoKoQ!njP_m1N5t2eAAHWe1aNd6q;EJPw57YeEX2&mZF_e z^OqRbXa1FBuCkGnTr`Q??$3-gU!WLEtZ()Jr(SFIgI1NZ%hEETXbS7P^=QOK0sBle z6(V$ogzC`&8+j1tp4k=ydPHUM`AtRxi9O||JKucTM_MHKP8~0QFcR{x?@IStXi-|d z)dowCGa~hDso9<0(1K4@#OW+#L1Ps=h8yO`?>CzAp|)jcUdD%A6C+aOSqcx+Mp#H% zCbCeptQpzj!VI~JP2Op|q`+ay9fMPWUp+RhU2rO$dq$_iVq06|~h7hIk{6 zk&WAYdY+9Ra2bF8u9w8UDWRFgGd1nf$9Jn^n3;yiDRC~NF1uAMkmr9VDya&qrzxqt z+*;9-T7v7PM@R1(-gAW9KQeAr6j*Q!P9X_+M18S~>8pU^3u?A&EFS8H9&8j;M^lH& zB6LHeM&a}VYUjofoB1btJ6g9gJG15FFP02bk6bO5lPuCe(}u;~q~JJ~Oe%g>d%?>y zP$$Q?R+vv$+oLSzvF(*o$#rL|z}?eb-MF|l7efXN>*MRm}#vSg&@n z`Qx-K>HPJeOD`7|WUlfd7#s*1l9F8ySe7SdwW`hDxo1l{;P;Z^F-;>sFm_5pDuOI5fU zDU(b|P?_CL3fa?+QnRXS$mt|h?GjqYMDn5SbrMQapXK)6Z-et^JMCUeV4jB1*Ovv$ z^o&z-2mx{Hb&eNZ`HV6RVqIlvHl!@Qy~zC9zaAfkFnw+Jwa9Z04i6uhcz(K|z-Nw6 zc2~sBD~$o`lp?aZlI&*Ap|nTh3L?G|NhDnAfqQ~OSZ$y+*5v}R8`vI{`UIh72=aYw zBs_8d!T@na_N!=)<@>}j6~?!87;~Pp&ZVoqrp0~eGFr0P_xkSMhravUrIxN9Q(s*O z;K4^v*b(8+7~JA1v4Au8ZCMx2O`qmm%ztX zTR*EtHVe47&L{WT(Jk<-T`H&i=<^(}xrDDE^BvW_2fXFA6UNr}so9S4j>KOvD3E#P zfLRDn7A?ymGJJ-H)1Q+JEZGm;^#oi~dUBAWW1rl~s&KnQ4xWYQ2w&}v$zCPNu6Ext z5lxz1n|ePVj}1T4+9T)BEv=t6+p~gC@8CYhGR{HhU+-SBwYpI0tzBw1`ugy-0Zhol z^a728&aSaj3a_l*{=sp%wv&~(YGIaaQb}1-S>Z@xW0?=tZq=h&>+#8+QdCpv@?epl zVNr*Z1K)y(Xv^)5iAY;tuJ4);7~Sz7$nL<}O**|Pw8W@iEXx%DN-QAb)KYskqSEab z;LLJ4An$M_r6zf@CqR4iO=cPN;2GJ%t!}~31Zq^OR7#Mtrn-&H8_`SJw=Zw|B1OkV z$Nh2LitUQ)h{?ryops(bcp2Ee*%&!^Ssn8(adPHT-Ts@sYxtplCMxE8(W;c_m8?4V(Y%(s|lWo~#cD?INmdLCpB=e9?==RfoaOv{rZ@$kxztzyE zRYgYG#WhjJbrrP}xl7*VO;;-AISD7Re(LpdZZ`OphvY^3*xagW*rKRfVCkG~-!|6g zn(gF4jto=xZ5~e+349@xj6rLCLi>-qW}N^ diff --git a/build/darwin/AppIcon.iconset/icon_128x128.png b/build/darwin/AppIcon.iconset/icon_128x128.png deleted file mode 100644 index 19b83255d72210e3b94927b0bc1f696a20d6a20c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9511 zcmc(FcTiN%v+vmeOJ+%uVF?0~BxP;61_q*@byMNsG$E$kvy6c>&>Zv|Gb84oi`_mKkNL!thh=B+I08)+nDo<|O zkbfRH{>?kNx-;^oL0I2^q6Gl{TsQJp0C4_K{~7>z3jx4}1pvro006C9cH3jQn+|*{ zO?8#)e-WYK$?S~;=B1&f3fq9fNkJ08=F@~5(QPkPL$BwSUN$n;9yT`(AR;6pAs{3s zAT04zSX4$xSVmloPe@2cNJuEty#K!`IJ-V~u=V?&748kJ`rjz<{FenU2WJ~kFH2{) z|5@kOM1K(gaKbcH?mc}uz5U)dMeqAmL{6YfmO`)4h>-GK2oF2r6PQ8Twa$&W17+2?(b{piDz9CuP z4`^9<2`+?2w4<(f;)j9p^ozD4c^_l{9$w94^Tx?Uqn>ZWqTc2Vql8cEv+)`PMwuPf z^<^LNzp!0T%1aA6d{<;UTnovr3vru{v0^l5*10-0UewUMTgUMS)0K8CGd9r{tKkyrs1zd z`-7AV{UeqPHBW8Nw;ms6#wO4{$tSOi(smfAnk@%?^{VKfPTO1d$G0>MQkGac%yVk8 zZrVCj_k5jIU77#*r4;j^WqN7meX*T_WEm zA~g9)>AS_Y+@?<%M#~6KqP97f`IbeOWtWwgH7P%$jQ>!*KAqPK^#17JW2d_N*s?w6 zj5jC&0m4MYCs9qtD^5~9-WH^cU4cTeA78`V7P*e0-dK!Ze+`zi%YWBTGh*ufruxQ| zd3L>nTe+tGdy?jTJ-usboJyy`8|Rv7&XR#sN}l{kr-XBLWV2|vnUrglnbyK{1gwUU`0oVd& z*1DRE4EBy~X4oziagQEIUEK^Dv+3dymk?t zO>*3ZYfg@G=7)_1ilnfIqBcuq3p7WNhT*USqN}2|ndASOTnFzy0Pd1d>2E zeVY004W^Nlp$`f!q$H<~RA_{t9~TNiUjA;KZe@8nCafe!#5qVYg6~&0zZ=W~HVteV zCMDp+6FeZro6ygdX#AOq`dt#^$1JFn$6()Zm=$f6k95LQYYKn$ zbLW8TF^SuTJLmYbkbh`4hjm^t4509NRX1j`-`&VA}tOmXw$ z%(==<$~_#@jrHR6(E7_JsR7H7W#Z-J1lMx444u^Y)Eed1r$BARg=13O(0B41a}sLH zyxa$;jPt)<@Vi%oPj*V3-A}>{7oM$rAeE!Hx9%dgDhp1Rrj4|rnTME-zZJt}xAE3U zpVSUj@ecT)$x=P_cAQyx%z1Kn<^eswZQg-jU3v=oUHS^w8dSMjftreDmg{%8*R%ir z_2{0x+YMF^OI&60mz#JU)BSZ`_7}ZMu&5dfbV$`$r2J(&!zeo3<_ixxia+66KKL$?iundQ=T$9GS0r{qF@ytc>Fp0t(EY*x;o~8SYL7obFh+@Th z`B1-M3&>5Id&0N(^v|uW5&2c+IN_Z@?wG}yUcbpC*IKfK)^f29wtXygVoBjK`_6RDM`K%!&w3$R?+SxKE$5e9=V4|hO7+{W2jWVaBV$4bkLcJoi!|Q z`LUk>l=Se$6lkg4gAWHve{Y^Uo=uo?H$`VssCAnom97M3yQ{03GA27PrD{}#rZw`f zScqu$JklGgcJDhX#!)H}%H_GEYV0*sX^+pPCzcG+(2^asLlx&WFs#baM?!feRU0@Yz zuB^-v978%&yPy{wkQ7HD)GSpk!)9Y(vr`8ButBzZBw4T=#B*dKljkn6^zdRaab6BO z;1l<9;ot>ADEAAodkqLTYI(r!M(XKJ<%`LvRLJ3Cdi68Aw_AG+s=T}A&3Q$I<#3lA z3qe20JkYi`0(h^M%+SLxsm( zb%BL92iC1jCW9&K>qvd;Z6H^FstSC(C8w@FU}#79b;#Zhb&tnJG6ij5IwYObhLG_y z#?K5}TfgLVgU1*Qgg!2oclvW(&sr6G)u-8 z%`M1ymTh5}YXw@j3;2|SH}FnJ$-NuPayXBzqTO=&Kt9X9p^FonjZo92NO4VxcQSvC zvc*;VPcmAxa8c^5&b#t;)X1}%YYo$Q#zcKd#X!ej`yv~Y-;v*%h`ne>V=D38Q> z4Y{SDk)& z_l5>u>4ch`k%?m5qRCZxFN6xPi4(?_gV?49-!G68yncp1VW#SbKhqWP`;{o*u_0%< zcJz*4qOj7eT;%;Bwig?bCvrz`E zR}?!H-zF1&pq&}CpaqZ+HL9L9Aa`6vNCvPMqlh5`{@;kR123S!XArLC_rsCQE>5k& zdYN zCWs(1vs&?o)S7cL$LSszHwOq8+-?nSKd}g6jXZnVn{k=NCSz-Q88{a1**n*a{)X(> z4jfXXec4Ngm;)b2O6_wYN>#yb)3@IKP@$9ovIRJsBJqp{Y3&KVNJk_`D?s*XuN4|3 zPyNNadCxfZ#>KeLEO`#R6u&Oqj?8T6v^@5Y)G+(@a+hb(9bSwQ@ zqy>ongv+Pqp;i7Q9`f}M($mokka`jMLk#>jd_Kz|;n$0~U!<`J|H<>h%X>V0>y3q$ zc09xOS?{hmPdzu&KE=OEk`$bokOFSU`^wc^En=?@kOLq4;i5j_cGoVE3c=3nU3NWa zr`^MW9lZUVgKpeB-W;`jpO{5SBHJw>j2dSU>#cQ*%{H3 zj613hyasfVM~%sWeR;P>0unfcS2rYmxk*jdL9 zG9FZ3fKv3+$f4>Ke<~hmU>y zYK_<`d-o7Si`}ThNvbFJc+c+y#48Zi8Qe8SZxim3kD$7=B~pTZ-6_bU9lHL?CEZDS zx-%O2pp^v`$ap2V?#$Cxf?FGTet+w0ypwyh)sQX@|*!1XBHad=LE=8binBqCG zo{txSaY|?uDJDIgb4|=_V;+k0K^676j7`5W5BOxmptUOHGKHr0VF2D0KAgvf3pe_j}{t#lmKux8+m?^bVi zkHNm+2kgp7P-JM*e(5bg|5>rFo?CY3W=G82$C05ipP_zt^-md2TPdhZcNo_T0( z&B^VxHV0)KnG5ZVwzn*SEYfE=)gdR5xL34$Z{zYo2xL51Met^$%U5b3_oeOwhoP@5 z$D^=(PW1PUNo-l@$&Z?y8>{b;NYv5vr zj*ANjAkA5W(DjY8gcz^C4QhD7`2Vis-t>UF>`*QH2B5Wa9W47vMA&y3O3GxNr0Z2iFi9jFlo3$nvAIw7W} zSWYAf^va0|pW z-uh+XJ3$9DL^cJr$)Svd~RB>jNVyHJ_K24IVipboK<;N3WG0*MOdq|AjPs0 z^QM4UN{i4nzu6euKJ<6bX)Aw!FkkE3?34h`9o~XdAczB9EU3x+{Hu&R`sXVYW1UDkPUWrLa5Nz9A>!w(X9rz>erOA}jmT=9v;d^>EMcGt3O$48pr#_iUIf_NXrI()_ya*!GHyxZ0bh!lHE$L#`5G*+1HCP=!*ZLEF2i43XrX5Nuc zrrvGXxS0;XQYaAl^qRTn30#NTcV7^HZi#T88c6?||6e1xWAuczl=(t?j^IoDG5KdL zfMGE3_+arvgAuO?4>ti5RZ+hDSB<|kR3W*oDf-6}{`C{|%CL~ZUL!jU^=KI}io%Gn zGIs4*uR$6S($2VPe+=xeiiqV38wVi^qAa|CulDS8%z#)Xff@|>|06C70thZ^AFf?< z)fc;J7M!KtL{4t=dWjo&0O)k@G11FA#Q;x<0R0;BCVL}GiBG9X8B5u%bS{7R5-4r8 zF>T!g%m-6F81~p|_Fxt@zXo$a1RL`{-|Wj{m;V{GPg$CIh&$u>D9rNyihHSlGQ8G|9aA>W~h)V^ahy&Wth zDOm3uzR`DA#QYdU@46$w4NNU?s6p`Z$+sQP*Ds0SJ}>UZiizA4Ox0X|f(eDd+$d<> z#o?O#QJ7@8&x%MA3gx~Ri1SDVD|R=<6H%5mE!Q_;7Y~ANcSF%yF4m%7bFN~bZQtIv zhe|EnZ>hNlW?sL4x3W8ca`2N!oe4p|n{{g)nMxiH^*h9Tg6m+Jp(~exmSTrFXh^-e zMoFO|(BKnbp(`>*2H=_Ub=E*&+6g&>GzY2)Em^@Na>USOUWxEoo+lX~slk%U;+zF^ zbcdQ5$K6-d>Tfx`nF_tM*R#*r2H$xUXOlI-CRq}uiA=IgvgC&IdbaQzvi`n$6T!gZ z*hVVCGWasrlG%;iojfWN?+&|aAv4~I$BD<-CbnxJFkTjKqr-G{yNx(6tt5?mnLbjU zI)G<~UZ^F*cmyv2g*ZbFF~d7W{B5>u5|Xl#vW}r*dlGvRQ-ntWKX#{Dg;k1Fiux{C z2Q@sBP#wiNbJ8)}SqV35#VO~`{2f;P*f2pKKuW58h<4)!X#E$eX%)J?qIO7x^%dtVoYgrtOib_(tx)12_svA2J%c^0w-u_0il)CT1pPC`q^98*Sgw z0rJsUx5+=H;Z1+`N_z{)o`==s)R_Be5|~i5vhRr%24$bSpFb=WE&o#5c7jB3B>Z88 zJ=V-mQ~6D_)3np{SO=2sDP6@@?D=rD^#eT6?(h5YOExqQnr9g$m4xDyu8>~R9(teR z$=u4-&egsH9M?v?7r)&5V)?%(wM^LpPi{y=)V#(yPzW(_rRGhU-KLWp$sPv;7z(@z z=N3FaJgv4cS)|{UKr-#w;O9V0ov_bxBSdeWDN56nP*-iI43#^9c83+-Cg z=PW~VUUTNi*}FTmt{C5=u8NbUQ^55vocg!Co?ym{me15Y zJI1puZse^u{L&V=6<$Z)zpAv4`?O2j96Ag0WF2uJ?74NrlJ^>KRiR67R-W{06>yNq zedx3K&TsobMvni$`NO?i>E3)sgksB2&WPYTat^6d&9ApHQ+IqwEq=3ThEFNZ<%0XD zba)3zrpy$GZ8NX6=NS<9;w&Tv9n=)vMX`ypURt?C_bZT*cvhjOBt+!YEQ9kPm-VzA zQutAPX|PH#sgn)gCgKsChu~q@$4{@L4<(f!KY0AWGC-(XtXs?#;!EXA^|SjP31xU6 ziBQfBM}#k04Sf27M${x3eExy4z>khVHYaaC=cS8{QCkqf-ZrCjy=V5!Y^pBYZLcl- z=e9ifL%Y1MdQ(Irn}hPiT}a!wR&&jIgfE%ZzhS+bQIpMQE-L7DRd6=fq>laEu7hBv ztu#H=^=~3gI42JTej2bxZ?2q>etvni`k#pHht_265X^PKoXzNU|ISvj>yYF-*b0HC ziV%pXnC9UyBTyG@Mf<4}%5PhjO|E8F&~Ou-%*f5;7b`!lDAj+=B~>g_ECVg9)O=>( z%eD0I^Sh)#`K7?jll3}{M|K|XKRfs;t#209q{qB^Rrso~a5wL`d3t7p zyvx1IeVJAHoNv8ma-O_SXF!2Dos`|QFxm$J8wx#8tHcyVg!085ygrE9W?U}#H&oBL zBpQ&zn~&;h{^bJhJ84ufoE)C0*lB2~e5s2G(tV6%i@>0Nof(X|xOGx5#u$_S3qVdg z_kHIXDl#5=t|$|+Vt3=ebe00(fqH-Sgmn?MLbXC)3BeWMii^_$Yd(!w=OM4q40VIikbjYJLE z72wCZ6qtaMf{qb?phc1Q;Vpn=CjaVQs9O8+8M$DA!A+CV*qM;`F$Kh@yL_b7=0u_M z_*YBL_?$^dnE~|(UbT$)^zP^Dq?%g{$Z_>F!57C3v<7uEwSt343nb!>TcRsZhRqyt zhqlG*@y8@!=_%oRF+lzjSG`hzqjp%Uwh6aOXnmQDu4WB1xLonO@9jM?9Z@18ZW`^O z93XTS>Eec1s`_Fa=tTFt<%`SvR6tsCbr!Xxy+?XLdN2q5bV5C^d&)Do?$Nddsn6N6 z<5J$=jSe=KqFy3HD=R?o2=NI0@Z4H2+|GLZ9?cfCs*h8{b)ri8IQf3(I*C3v&k4ND zOH0s|EOGH+C1@sUCK7~>>o*p8YAJS_i|M0V0^tX*x2cvHZw~B=2MpJz=%7~ze&gys zq{zVWet+)6eIm_GiRy(awwr>?0iPQuo!^%{EDN39tyOScU&Dwzqv6^v^A<^dH$4YA zc=_W4?@4j+Xbt0V%+Tro8F`13szCB&bSt(JdKC@*|Bbz;5nYVhr3p^mYA)%1vYL2| z*Ai|~_l`0HuS+q&H+2?vK$KX0+jnFxVvh6E$Z*ZHbZmet*n(0YiFNbUW);<6&ofj7*p2kBN8SRm;>CnT(_r{bb2-oSmNuU>t~mvZB8g7@sa z)SHm(wG1v6HZhh5F($)iUbcBF=~2At<=N%#sCK3bkE$Uw?@3;H*Y zk@B5(Ue8_m0fK#=fkVqlH!}nC?E@;A&o;(>WzyW0<>p5oj4Q+2lE&xl(E`84`>*Cw zlarGhJXduf7>!nesR{ZQTZ`57|j28PBq}{uP>9fhD`&&;!|Bdwv zWlGP;&Hz<0&Hc0?x+ZFjxp7-PoCwu9L32EYA+igaNGsxJA(I6pnU099a+I|5AaEQ- zdO0vI39WIw!&c8EEwnMo&xczHk$VJ@WZ*;E#azay4L)WiT#lNoDj3wVZM+EXi_;! zoMAM6|H*xY+E?lHWCyCA^=D#((8B;v@Ze0PhD^^<&wi+s(0kh~>;aX?$rhC5UuE5L znQU6^*4-_MtA^tXjH0=#1Jaq>X)!`+RO4b@T_5QPP)%+6NvP5xNq9|AMeFF1gdX4C#eh+y z@|Win;i0sCn#a{2Hf(`022ER7R2yh?2ZQrL&_ez3!l*5Y0FOsC6p-FMwnT4XkO-1B^ zcIyxM=s#Eolg-adppa*^<=H;Uuf-`oiV)NElW7{8J3G{@ryc)tUdOB8*J_rPW}-30 zslbRvr*e~)A68)sZY}(p{`5p#!1wbb&F`G=A60j;FBOz`t?TsDOFsAn4pS0%IN_L9 z^nQDuZ|(W2P)ogM=0BHc#>uGbT8td2bJlU=_HhM4I?4hSHTpmGqqkgGUCWF-bgDX< n0&|NTnd5TY`bk*7TtiZuY%`7^10FZmhyV>$ZIx;zi?IIyH2~rq diff --git a/build/darwin/AppIcon.iconset/icon_128x128@2x.png b/build/darwin/AppIcon.iconset/icon_128x128@2x.png deleted file mode 100644 index 16093e5862e01e2b1251bbc3de143843504661cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22157 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 Pd7i=3)z4*}Q$iB}x`hvU diff --git a/build/darwin/AppIcon.iconset/icon_16x16@2x.png b/build/darwin/AppIcon.iconset/icon_16x16@2x.png deleted file mode 100644 index 5f018e829fdac12a35867a8aae82a0051c21ac5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1994 zcmZ{lcTm%b632f5LP!t@jz%|C^gm7^U1L?{3N;Sm5FawNhs0E8j{U=<4hC@KJm24&Pa=yMvp zcWthk|M;&#Zyii=6i5i#&H}Q^0~Y{k0c%baj&vc!;#P>aXNV8VE7*sV0Cj}AmMTI+ z6{+Qfyn;d?QJNZ;5C{|kfp~SN^Z$T=U%YX?_N$M zV1#)!eW%U15K`aDZZ-1<<64&>Ga~cYNm%!jAG&TCW>3 z4VKoH;Kf%?I314q_6Qi9GHM^a)+7++$=eA)c^!%4?8 zFwJUiettPbhhZW45||{rZfe#hIq7${CaJQ?y{3~q$fkScGsSP7Cj)}Lk0-Z>m8ev) z2jQh&t5Nik$#bzjR3{*X+NMne&b7|)J@=o3%b5xdb@F0}sDiqWCKUeyYXynCt(lhk z=v;$)6Bs?2lV;)O#(96ml=J92BmAdwQmuyy@vyymG+sHRO|(OChZR;PlYhD?fmh{> zuON5`u1^z$J0BT0=4ZWgZPusm7n1USZ+g|Za%X;CM^Oj(+MPEOrx3Yydr}It$>?Fs zU;1G}y=xhv7c!!!_x2~|+JV{^wq?rcux?@FZS{@jN%LV)W{W)W`J*jv&*QJ%7QU=` z?%dHUf=;OwFp{u#wB*}1>&S$}9?9Y5S&gex319a^$v}W=sZC>1byQ&WlH{T7FPBOh zHt^SZncHY+73(UE!9p%TDJxSQkeL(18YQD*5ltH{LVAVP8&~@R+eHsr0@s&dim)jc zx0to=yom+gT{Vui*K=XGEx)g8Bt|Q+T~RLIQi1(v@%zQa?_F0)WajeQiNXG{rJy{& z<+&mBR-^W>p`MBf-$uLj8e<5rw4YjY84+^945|!pv(Uq75+opC0CC`(ff(jL3vXF? zKD!W+-sUz~Tjv#6Xr zn|aodgW-~H==YMn>=uea7Q<|L%_5L*kU*m83m_)v$!RS7^+O=|(0e(_A;TewsBs0; zOLTcuD>|OHx*Kgow_~=S7idSrL5ELJ^|7rx3Nj$ErA(w1*D(LUqQw2VjL?VTCl66W zhr>;%Akg)V`Tj3WvFrR;;bzDwXS-DwEHM8U; zwESxI8GrjfRuYGGKkMJp7437n7y3QVUhHr_JxU_zg*-1Uqii>(49IraSoKBdwW)#+ zjHju%cx6RGhShOrF|n2YHf=pptYNaw@4C^cj0=`9&$#g?=bs6WPWhaJpO-%XVn4d>>zEg0!-W+bcz3O(+km5! zEB@V#lMG6>mBGE5u9Omyt>c|h2E&&olyY&aP`_2U!eiTrbrT-&8gKT|@5ynA=ZOw~ z#K9o4(-8XHS+ehbb{uQ^O`rcYP$(weHFfV}G?%!nJeu1eID3j`*PHQfS}+oq>`K`m zec9hniHSKX-ZY>|=r;{zfwGoV1wpS1emhHAB0sr23sGiS7_$3zr_xlKJ1D8p1mGKa zqTH8cm=kHk62Z!?{mHqk8pdCdkwj=^yzfkfN}7`QC#YHzuwO3a=-6gYv!m3gsn5Ah zC~nUEaGQ0%EIVGi3f5Ty1%f+DDt)D4abPMnUAZ#KKp=7FSut#Drl-{GeS>_}V0T8f zBn%m`qU-F@Syt(B7^kUEAd^Lli?YDY`it9|v{c7(aG`qIWnd?LP+F8P6xZD~p7gS9 zWTDIlr4-Q6t8Cv?v$h(D*S+B*eQ`?9FkZJWo-{@pYsDk(9n-cuMsj3tQ`C-?cwRzY z##@>NSp-=WOB^$gnXqauR@m_9e}pDW?pWJ GCH)P>bC=ct diff --git a/build/darwin/AppIcon.iconset/icon_256x256.png b/build/darwin/AppIcon.iconset/icon_256x256.png deleted file mode 100644 index 16093e5862e01e2b1251bbc3de143843504661cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22157 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 W%Shd|yhj6kZeBONR(cf{@&5os_W|Yr diff --git a/build/darwin/AppIcon.iconset/icon_32x32.png b/build/darwin/AppIcon.iconset/icon_32x32.png deleted file mode 100644 index 5f018e829fdac12a35867a8aae82a0051c21ac5b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1994 zcmZ{lcTm%b632f5LP!t@jz%|C^gm7^U1L?{3N;Sm5FawNhs0E8j{U=<4hC@KJm24&Pa=yMvp zcWthk|M;&#Zyii=6i5i#&H}Q^0~Y{k0c%baj&vc!;#P>aXNV8VE7*sV0Cj}AmMTI+ z6{+Qfyn;d?QJNZ;5C{|kfp~SN^Z$T=U%YX?_N$M zV1#)!eW%U15K`aDZZ-1<<64&>Ga~cYNm%!jAG&TCW>3 z4VKoH;Kf%?I314q_6Qi9GHM^a)+7++$=eA)c^!%4?8 zFwJUiettPbhhZW45||{rZfe#hIq7${CaJQ?y{3~q$fkScGsSP7Cj)}Lk0-Z>m8ev) z2jQh&t5Nik$#bzjR3{*X+NMne&b7|)J@=o3%b5xdb@F0}sDiqWCKUeyYXynCt(lhk z=v;$)6Bs?2lV;)O#(96ml=J92BmAdwQmuyy@vyymG+sHRO|(OChZR;PlYhD?fmh{> zuON5`u1^z$J0BT0=4ZWgZPusm7n1USZ+g|Za%X;CM^Oj(+MPEOrx3Yydr}It$>?Fs zU;1G}y=xhv7c!!!_x2~|+JV{^wq?rcux?@FZS{@jN%LV)W{W)W`J*jv&*QJ%7QU=` z?%dHUf=;OwFp{u#wB*}1>&S$}9?9Y5S&gex319a^$v}W=sZC>1byQ&WlH{T7FPBOh zHt^SZncHY+73(UE!9p%TDJxSQkeL(18YQD*5ltH{LVAVP8&~@R+eHsr0@s&dim)jc zx0to=yom+gT{Vui*K=XGEx)g8Bt|Q+T~RLIQi1(v@%zQa?_F0)WajeQiNXG{rJy{& z<+&mBR-^W>p`MBf-$uLj8e<5rw4YjY84+^945|!pv(Uq75+opC0CC`(ff(jL3vXF? zKD!W+-sUz~Tjv#6Xr zn|aodgW-~H==YMn>=uea7Q<|L%_5L*kU*m83m_)v$!RS7^+O=|(0e(_A;TewsBs0; zOLTcuD>|OHx*Kgow_~=S7idSrL5ELJ^|7rx3Nj$ErA(w1*D(LUqQw2VjL?VTCl66W zhr>;%Akg)V`Tj3WvFrR;;bzDwXS-DwEHM8U; zwESxI8GrjfRuYGGKkMJp7437n7y3QVUhHr_JxU_zg*-1Uqii>(49IraSoKBdwW)#+ zjHju%cx6RGhShOrF|n2YHf=pptYNaw@4C^cj0=`9&$#g?=bs6WPWhaJpO-%XVn4d>>zEg0!-W+bcz3O(+km5! zEB@V#lMG6>mBGE5u9Omyt>c|h2E&&olyY&aP`_2U!eiTrbrT-&8gKT|@5ynA=ZOw~ z#K9o4(-8XHS+ehbb{uQ^O`rcYP$(weHFfV}G?%!nJeu1eID3j`*PHQfS}+oq>`K`m zec9hniHSKX-ZY>|=r;{zfwGoV1wpS1emhHAB0sr23sGiS7_$3zr_xlKJ1D8p1mGKa zqTH8cm=kHk62Z!?{mHqk8pdCdkwj=^yzfkfN}7`QC#YHzuwO3a=-6gYv!m3gsn5Ah zC~nUEaGQ0%EIVGi3f5Ty1%f+DDt)D4abPMnUAZ#KKp=7FSut#Drl-{GeS>_}V0T8f zBn%m`qU-F@Syt(B7^kUEAd^Lli?YDY`it9|v{c7(aG`qIWnd?LP+F8P6xZD~p7gS9 zWTDIlr4-Q6t8Cv?v$h(D*S+B*eQ`?9FkZJWo-{@pYsDk(9n-cuMsj3tQ`C-?cwRzY z##@>NSp-=WOB^$gnXqauR@m_9e}pDW?pWJ GCH)P>bC=ct diff --git a/build/darwin/AppIcon.iconset/icon_32x32@2x.png b/build/darwin/AppIcon.iconset/icon_32x32@2x.png deleted file mode 100644 index c8f864cc5680aa89392880646012727db12e65ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4089 zcmZ`+XHe7Mw*7?!LJdKRbVCt|^dcf6gepY>DxFZIC`gF}kPe~uCQT3&M2a+#juayx zB>2Z5iXdH3zyMODC&J^s5AV&Jd+*wF&f2ritl6`_?Aa&D41JxAnV%T|05(Gd-CL)O z`tJj0IK7V!>xxea;bL&h1OP%L0019*s-8;t6#xj32LOr_0H{3%fU^&Z-rQ6_ePD1l zzOH-nKVY)npFUNf{)Q%cPznUj22uf5%`;D>^Zt6){;qfY-PBxgZl?q&$}6hK$Scbz zsF*7#smUv-T~d~kmsgXQmoIc2_ zhi7!48~|Y740W~4gC^IDJ%YF_dIlmZLr8QY;<492z6g1EPJ<+CI#D9IO42H>Kq|43 zh!9I(DN)Qf$pS&#I3ZdOtXUC%3?SW*9B=)gOcSk#;+Y8X*xF+Maoq2OYD%m=;^VaO zqp80U!P(VnmDL}qubKkJg6YS#FsEp`dmLF~hY8xfx#d_$Bk*LOTf2kXUX>WH@xb*f zNNK5w(9S3;zHILxY5pwiicBUw8NG4*ypyyXO8;ViHLtk1IDDbMGXo`ZlHe zb%6vs#cNxM)8sfHmQ;VzPGb9Z$fVuQF=T_L*k(scsvMSWNytsP;m|R7NG~(wZ9R;d zA;2;hQ9@$rEKpxaB?X*(7^)}_DDioXE0%&WIciMT7S-Y&iWgw^(b=Iw${pcW*%_`+ zih@VbynK9oHZ?rGi(5fB913E-3tE|d|ASu4#OFwt04jWs_V-uL{HAF96jGo0(U+jb zadEy^ix&Yem2U@$pJA$62?7=0Z^!fUXEBPRzwnrVps=E2^KvyzT(;+ph=`{oyH@(A zAJOoUfhkj3w1xyVZr^6DRrU)~>F1STc>j#3)((SP2lX-2@ISj5L%xeN9BpC5vWpVOy=VhF+S0wlUr zR4qG2G1!>IB|kT{O-M}=h`({Nmctcv9?v=kvC^_O?U8-X2vuA$6>W$=#()Y2bq1tL z_N`LJ#!)q|RFEXP;LZqJM+V(Q25~^PDE81#W{eFl!zZEXap}_)HafcbatR4?7kRRw z|5>g~n6%obdEW0~6Mq%kVX?zWT`c{M?{$BwCeHB+{nR52vq(cS^N|1?X1tlXc{!es z(O|~)W{Mg;W&<3mEU5tN;ip^{)5eq38II z57dJ2V%|!$`ZoqN!AGt%2Shlfa07dg^hm*1Q{5q*e)rn89$GjqC1Fv@ zPJu2M*GKIAq=Id!lAa=DTlBO<;B7!rzh0dA2dabh95HTNxd2fnDRSw1Q9ip@$T%k5+KtQ}{(fXj+t%ew`^QFXlE?pCz2!hx7ccw)k_rCN zgi{N8)FL&&HIh%{{RnmtHE6-fy@MX{`_|MA{2n0z#5p+)(yc$al=_ADvB1bvo z4l0tckLk|oVQ0;Em=fiWhn^5T6k@!r4-#CAm1<&$T;9A4M%62#Xb}4!c}}S}sZt7S zTahAARmPorG67n&suxm=$l^q*jjmvmK4|oJ(BX2)&kS7W4f=6XgMHRBd`$s0gF_Fo z#78F1fs%I4faSp`>bGe&smnK)ZVfNG6|l5H1f%M9$?{* zgj^AaOUR9I2cgmB?DKc8Fk!HinYZt4>i;oOQNl5u{SGMam?9F)wjMC$j}7i4B=^lj z-AlNCFw&zk#kc^ibTa^QMH>YFKcSizSPF~1Ei3{yYu9xsSQnv*ns)=Ixdw4P-QHO) zQ(DP`k##hXS0IWXmp9Ey9Ld22o9)s$l|+0y5bnaZ@Cm;KaQketxcv|F-EYS_cTYf# zCKI++>YMS$$*%zem7Yv{K&$oezHL>7WXcm$dMlM8&XoS_`HB2LiOZ_)Ws8;;tB@-! z`d;hPyhZtPEmB1c9N>q`&#r+vvSHH130V4^Yxr$mm^feW0hhr|*_04eR1kW)uLUbh z&s6OIcv&u`JR^)9|5Lt}Uus!tgN^KFYWChbyi{y9-D2;mEgt0jyL-E)(5!v!_{-81 zB5Wl~;_R|^|NM;0$5u$-SJF$HSNMaWHrqKCuH*=DtDWJQ5c6&cR4Fg_#D(YYsR)Mq zGpXMNE1 z0AD%)6RUfUtzIzOy$=}jq7mryJR~Y|(Jq+7Ai)3jD9X{cQl282(=~F`eRQw%;Ad;h z8J?K$0W5=Ma9{G%xr#Q1dl}DKf9%umB~qOA_2$gqms03awM8PIQNqc*i|JtOGLI0Y zm4RH*`9{SE>`2wMD!z_zBlPy;G<4AKY+Fc{#rl^ec&`8ROC@IP6{$ zmB^zBC<$#KL!(9m&*b7MGqhQP%>K66hzTs~H7XT@eEXgTl&P0HvkUyub+~>KvTPA#tJc!p zANtd*^m{S%g`dG=tnme^=L@6Mc^1GzoP&MA_iuwB@Nw_#*X3&rVb_h9fJaszb}gDp zggj6p_8!c&XEKr~c+=)S+jx;L(5j*l1;C=YL}Sx5O4V(hiJHGX}~k_GWJ=7!Je zXQCrxo&NoJ_?LO{1d95_tXX8O90cLL25@@@b%oyDeFep#$K_oAb^|CJiM>mJJ#65UEWw940pNZ(+)y;Bb;9$d zrKoj^@x4vfVTAx=VD`II*SMh9ID1+x&~VLTNSfP=kDKVCFXlqm5MLR@N~^KD-I160 z=o!)UN5Z_us)oRxf}|W$4!MfYF8V>&9@-un+o5%>?&)3&gV+b6Vjg~z=~)%H|BSKA zNcOI4xMKT){nqcu=tbYb#q{F&j5UL1{u@r zV-GE+xG(WEo6A_FL6+rDLzu{yrzA}kPE1ZsPCe!l{CgTwC=)ZzFuCZXoTHpcW>HQt z&YD;BWm%sLYS?CBv#|4>sB@JWpL^PpX$8qW9!6FiZTZ>cvbHTj-fO$Lwn{X$WA%uk zmLs5*CHLGLZxhiI+3S3MMrq7zyOh5l@~n88Uz`t&Znd?m3xLWP8sDnKH=V-sDzArr z;g)Qo(n2Z`8n0-+2MvFuVYmT#-n1gEuRTF*$hvdwoxoxm@)vBuVyn6lz#!|n_7@2Z zDh(CSv8}AqLSFVXHJdkwGIwu4lx?Y%)*z>;jBm2$mMJTc4!ft>0v}Y(t@Y{mLUvV2 z%{}B-)~PNZ1304n(@xqDj+i!- zHbjc=ZHe<_xv%}{#dE{BK-8y_=UVhrrJK6nlW0C>+Xf`Dr+I{+ffO6=@TQ!+&hnVL z6vqq3Z2(rJroJ^`Hs|hoWB77J1Dn_yq;TC(jAh5~2x&IA1KXO0*0c9I)_$eSQ6q3% zoR-&C<{WlsI=cC{>JuYT%+vvdexUt|-)D zxIHZqLFVXaH0ZQuLfk*^<0GZD$c zcg^DqqE5)|4p;2FX?Y}xjFwtFO+QP8CfqCXXJshK?yhk73}@`xq7?lxd42H&N-|9m Vbjq8xIsF&|hI(k-dTpoZ{{q#Pam)Y! diff --git a/build/darwin/AppIcon.iconset/icon_512x512.png b/build/darwin/AppIcon.iconset/icon_512x512.png deleted file mode 100644 index e3f99304e7a0d9fd5fa5ce03eb718fb7adb5a741..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54200 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 W%Shd|yhj6kZeBONR(cf{@&5os_W|Yr diff --git a/build/darwin/AppIcon.iconset/icon_512x512@2x.png b/build/darwin/AppIcon.iconset/icon_512x512@2x.png deleted file mode 100644 index 91fd9587a6c79547ed952c706078c9f8bcec5f4d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76258 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

P`N&n7;AL?ULQi@`7|D>GWXtpus~}f#e-9mn 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 - - - CFBundlePackageType - APPL - CFBundleName - {{.Info.ProductName}} - CFBundleExecutable - {{.OutputFilename}} - CFBundleIdentifier - com.alchementary.link - CFBundleVersion - {{.Info.ProductVersion}} - CFBundleGetInfoString - {{.Info.Comments}} - CFBundleShortVersionString - {{.Info.ProductVersion}} - CFBundleIconFile - iconfile - LSMinimumSystemVersion - 10.13.0 - LSApplicationCategoryType - public.app-category.developer-tools - NSHighResolutionCapable - true - NSSupportsAutomaticTermination - - NSSupportsSuddenTermination - - NSHumanReadableCopyright - {{.Info.Copyright}} - {{if .Info.FileAssociations}} - CFBundleDocumentTypes - - {{range .Info.FileAssociations}} - - CFBundleTypeExtensions - - {{.Ext}} - - CFBundleTypeName - {{.Name}} - CFBundleTypeRole - {{.Role}} - CFBundleTypeIconFile - {{.IconName}} - - {{end}} - - {{end}} - {{if .Info.Protocols}} - CFBundleURLTypes - - {{range .Info.Protocols}} - - CFBundleURLName - com.wails.{{.Scheme}} - CFBundleURLSchemes - - {{.Scheme}} - - CFBundleTypeRole - {{.Role}} - - {{end}} - - {{end}} - - diff --git a/build/linux/icons/alchementary-link-128.png b/build/linux/icons/alchementary-link-128.png deleted file mode 100644 index 95184c9619fe58208f7426dd71739267dfa1079d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9511 zcmc(Fbx>T-v+r4f#hu`=NPu9$T{dV40g@0TxC9NZ0TxdJ1a|@?xVy9H5;RB%gb2RD zU4t%i`TlNoT=)mK0R}4rl!!sla!7d061Xk$`78up5FQBldSt=Dm**DIa9tza70k)K7^YM-UsGUC12K{ zB81bc6sjrra`)#@;8+ovD?vf*^mw7~xkC*?$?+aO%>X}37Do#{c4_{2b>;T86Qlc? z%DlHNyV0PlucLVEy2CcjXOCC1r{I=e+Pu5os{3(sWHfNv(C~v#LuTN`bZP0;xq-$O zagC^3VPWkRzu5b+zgKpm14288FNDV?LIqNpvz#_g4pOUTtTqhWkj^Z2_Ty??Qm%ts zQnw`I^9dzGR*Vg%7HO~TnfR%1H2tczP|n-PubW3B$*f^A!La-Lu!xr#{V4wPx-6V} z{!u3T4LzABe6MUa5_3}nk3JOI4A(%iYJ**;qb(WCn6$6YjF!|j?$@&aL3gH}NRLgl z#%Q~}c5)m=J;WK_`NP6zQa3vB;)vhG>KAf!pvRlc8y^2WiHCt_TXV1W2X?74<7RXM zTV4NGyzNo)rQR`fx~hkk$9wlrGh-7d@1)bWg{iyrluZ_cKDw23&!_Dy`s11#2Pujz z>=!sRShj5J%Y8nR5reD;gA^kw$+g-<~b#26%n8_qJ8p zduq{^ea;gY4+o*c;}R(+;}j+-pY8}y#H>Q0n9pxvu1lOJP%jMHx4#<0(doD6s}Vl+ zaZ7D;$}FqS-nC3a?;}yufv)b26jr%I|D99yG)M8k83lJ8~DpRsm1# zM1@Sj33f!nst~&NUPrVNSp7#JhCb$Pn&M%IGOiF~!7%b`7T7&&>Ng{4UF{UlK0fpp zYyh^1p0%nXA%T5hof)Gq zpKqK+W>XzEqQz3*O`rWJ-z3b5S0n*(Qc(7KECyN$OR~7jF3!XH$$v5IYM|{_9rqj| z4Z_p(W`LF(ol~%M9V{uB%C`+kZO1LMuMdCE{}NPZNNPyAf!9XzLW@B-IKUDT1a|Lb z6oN|fq#nS@;%}@8YvyKOnu1n$(Z53Y&5O?W_A5is3t>S=V$kzoWH%!*Eglg(1Uj+f zq1#bKzpC`(h2$B9#@gz;y_^i_9}t|+Lpx?EXuq&9_mAuk6pc>da0>i%_{yC9be}`@$rtgJ<3n)mbBIFw&sHX*jkk@l9(=E)e$AlDR2{;DHM{s>h7xsdf z!6pHX!^C(TI0A>nI8%D24qrjz2vM>f#kDdXi_Y1=sKTx-M|8k+Ub zZ7yV=evSoYq(dJtCx)*yV~QoKuWGrQ;Usz zTpL+`|GIb0-tPjdg(j>q`pHhbjqdukAoH6}IY>m689Jn5BwY5ojeZmrX8n~L70DNW zBm7834x{?ZKeiF>@9Tet8|siYZ`qrir?b#NGVZ-9-dPihGI!lp>I~EtM z=#8I|ZT`^z=wB9d|EKS}R%VbX{x*>oo=9NF=3J_(2Z=?BN84;TG6!ja^TQaHsp5apjam!P%E%eXnV>(g5Sw)8DH= zCDgAc6&YE1kOV1cO8SB3@P7=P*<$FAU^&JOwF7dEJ7O72Vxi&%*%-+o}`Z@(U=sDWU<$x{-%!3MK#NSl=jgqXFLl(`F^tYq+rVqdkSqa(q>ObC3#F{U`oZX z5=CR^SH2kDiYt!U_pNSX}5HNXv`yYI3BpUloUDS1Wym)6hCeH&Wh;ZzwDy8*Xa`LPW~rSKi1 z#~%_=_~$OIPTq~g|8`g0%Cb_}b;k!OpcjJFaJJmrxXOGbs?dF+J}A)?_guB&@WY|W z-P(Xk|&YlNE-y1sG6;Yt>*KNNcHh?&9&9XX(Ts71jj9o!cn6_!-~6o%YwelxC5;fY`7 ziBQbxUp2NNhBz+*l&C}Ldoj2|g zrB@n>^%_3u0KW3ZRdG+@_A~TBhK<6G7Ol24!U3=7cX@Z9-mu-27lLjhGpZK%_#|5o z8P*(sd-a3_Tx*9IpOc87U86`^f2B-rxFR27qQEROVu!F|E512Fl>fWDwM?!>Vq{pKSQbYs3$s8jR*Frv?D*l@4SEex#5(zdZV~v{8F)2zb>(U z#c>K~dEV3;Z(Mg4AIW_+VYE^I25H)%JFohW%U3Gn|$oE2~h1-^i=&2x`O zGCDal3-Tc7DRu;zt6=aag!WT?4iG6!K}a7$Y+c$;98dKW{QR_;N-TpQ2ZDZ~*6_Q-yav{I=A3H^i=q^csC-WzQf|gFGAh)`|CZ9eGcai@hu!d=0Q!~ zkI?P38{uXkst=n-#Z9BsCl>te55mL26Oeor@lzE1K5QY=KK}Qs`QOAbaKFimf~yDI zyc-P#7Pj2Oc9|cpInF$`Qv2fGBuWU(Oh^KE<9uYRua_{_hlqjC{bVBEVYWBU5%NJ! z8=bb@D962{fnA(~?87eX0?s^@T(78kaRTcdAe0KLALFI@fKm|_Tpxa%ABSQ~G9deM zx1%GxI|+MS6>tM+Cyg4B0ta$BMFVDd^E))jg`}Yq2Os?zWDs#NR@b zmx&ZR#^jU9+3g&5jD?lYks8G@L4@Y9*5)iv*rcC~bqddYp>ron91VkOj)y1f>5{o~~EYxVCNp?2{1Nk@>KTH?upzwhPe(hS{v zB3@9Z#C-R{uF(xf4g_V~5hEoA( z9VUMXY~#Qr-}D4hOhT9sIrT!LuURPHA_0q z_+Hgk*BI<8uK%92IC;7T&DWl?i(lm%YB{C%Zg<4YL#)|5M;;bjXJM9vonTVqW2QEE z3+r)QWE|qWM0`ouGH3fsEEB^>$f33fSAi&~{>8-H(;krP1eh_l-DlW2q$Jj?!)qTu z;@n+xdtP?Gr71A|*i3MDw5@p=WS%z5p$0jPz`mi`e;=C%f+OO<$^y3=U9LhMaUl5+ zI0|`VF&>HGb)ojm&L>fxLu-e2`OGAu#OH1*PJW=YIPstCyG`#GN# zt#|1^wr2MBVB?>>e6gmGwE%3!vN-IZwRy0M{Po;An>=vMnb)fnOjgRq91QX<&R5O< z$I}f~ZsW|xda2@4b$Y*Y3}<@Ma4AuqIR@T{6K2W`3Cg%xrf@aRq|rU`iSRoxT1aRm zOKL3YBZYJy?MN_@YD4oM%)4l)f-+0Bjo>=%J~w49uPKXLXtgtm8_IoN39ePI17p0^}`EulJvP;hov9bnnHAh?Ek9^rI>vho4F zHl1!2-EtO`|59z_hg0)O?k_B{uQ-qPENY3!9vQbjWBU&pXh)9Fo0A-! z(+V;+#&95rVf4zBIO`H^t9<{=Ru$75A|_L=RKwkK=ja)n8Ms%!&iOD& z@WUe$vqK73ApCWG5zBNn4HLjdtWG76BR=+J1wM_fB3=zyH@r5?5sR>}X>fGCbKmxp zkPb)4Z_E)?0sLr%$(?SWc=((#_%I{zWtWX75Fz@WmdhEMU?e}=g_n4P-CX~18yQmR z&U_%9Ou1jbc{?3|xqv_7`3+O|Gcs)|p92Bh`DOeADj@A!-hYkYp5Zf=5~fS7dAzT2 zC!}9A0s6s!lf$J?^@cpc++28!l!bY6-_-w7QwHa>B6#?AE{B-Mx+w6@bIWD<6c`SLa!l~@> zYoMgX+N5P4FdIy9r{8C--iMi2{~pW+;cYH>f43`(S@|E3V9yZwA&LnYk;L9m$K7?F z?G4T9_9wzUPm~(Z0vIW8sFZ?%(%xTx>#@{-2I5HZC^*hUvNKQO0#kLg?mbb&S3%q> zq?*Cv)XX!^*H{fx%9E1eG41spWu2jXKc7y$OtQ`{E-7M}s3u!spV1$(9r76pPw72D z(b>X+6N7Xw$ToZL3!9yQ=v;Qixqzufc2x*Y9_fz5#l{r@nfI&vF`~i`1X46so}oh^ zFjsOKH!(5|zDRVE>=y+D5xG+DEBHl(yd|5f!l?-Jx~9v!(91`Gce|h{O=l~SZ`s$; z(AMuC+d?E4A2wG%05feoykF7fPchi%UTcik?`qXjOQM|1P4xl35brWrYT&}Duc^>( z1{zXps#cV*576%e%yopvNB|rY-i~SrOe;Qnkor(1zBx09NR|+~!Xq9w%l#}JBso}I zQIx%iifUIiWxxN1O6@(n7h{2^)<)I^>);3XqAZd|*d%lOG=XuZai;8WZud5Bedgac z@51Ss9a@QnnFn9zSTMPgx{*d^;M`+VDPY1mbw70<+ro4X1jNbUY_^-M?X(i+rWU7i zt-faX#LN?@=)U;A_!R!J|IfXt79r(A z<-*=emO*v*L}YtW_PkW|PGZ|*{f0`6sh|p%03ahWd7_0#E%&aTzFdaP29J+-B?+0|yg8hFO!Eo}Ru1%X)?ZWoVBM9RLFw4Ng1 z?D2nCU{5vjQkCZjb{lsapK3$WJftdFi##5$wR|EAu>JdS{E8LDjpANGN+u#Xq{^k1 zwT3<>doZ5#1V}9*0|Xa)c2kkzNw{|addsT0l zj)^>>H=;MHW3-IzWsLmtE?-?ODV8WiHC8pY=zDHX;Df8nvF@~$;X_l9uh-Egvk|yE z%Ur959pbPv9SjoxFJe0R8za#Q6<#pr+Y8>{v`w>ya8 zviS=Y_pZ@wvny%KEx)!wY=_m7_OB@(VEguHnnGq_9xNmF_}zDIS>i#%ok~>6?aGsS zs|*eleh7WB(DD5MNYC~gxOlvOC(Vo35MOlV**O83wyb@MWYgOn^wd3XV)Hp>jj$<& z`5bUBr8dtX(Uhq?p-sk()&f2JL9Ds>puMVsn+PUB##1wg;9)r;0>?7sjEI1gig|DW z5mIHlXQSjG( zx$d`y{o0WOe`=HSQELouV6|78xDRO^*KDfZ2=^he{5Pz3J8F`Jj3s%U&T@{X>Xflx zJGBt>w55iJn%-@sNyfnqAv^Qmr!!NEPrJCfUi(kP_ESreRxtV|f8KiZrhj)k$z@35 z18fz~Ls<|+P(=NBm;tDbvZU$jfb!YYW|6Ae=GWgwCo{4$c|}Uk%S-g0a!M9S7fC}4 zDl}f``*1El{_-I)KyEo8<8-4|{fVvn$1nCiiW^&n)oIah-W0qkDA>zAX_}tdBzji@ z%VUn9BkgqSbX#Fjy5QZYo?IZU)gF*%N+V`7DTwlh!-hf*RV&bi;UT=Shi?xfcNkXk z{|(i1EQ|PO^W-5rn|?ckdruqW4JL;t%6IFVD_-lM19hGvSi{k%-{<;c&aNF)OVLKe z{{oOxFMK|Dgb0s^TqsC~uiD=FFYRT2vH;z`x3vibn+; zg4N>HB-em1%W^M`LD!KSt*hp6&4x zQ<)KjEZ|-*JK=I9BBTe@!g*BF!QUzX})YItK&eRADBFqu+d#(vC z-09ZygzZ}9Z^xe!fu*K|9z+9q%bazJ{tjB9En3E0&LMTB);b#1(4aDfA3k^YMYTl; z2)L-VhO&W>S%kAIe7W+gQGg@u%jU1nA5#D+iM3heverKFA@SinwC|K^LFbHnaKpWI z8&a3OZOf^2uoo3%CP}qSf>M%);Naun`{KB^T)Lij`#zb?Z&4ekBGZm6?&aW}%W)KW zX_g&um75x`BT?+^&4SlN&_o~r9oK6p^w3o3FcZ~7H3yI#zTKf*VYof8%OBC-oS_2W z9Qux{c@rZ7#{2!ajt&Skw#2IzD_L&~GKajb9JIb)b1}@cx_8#VwY~KtatsFRdraFz zd0lkuq~Mj$kGv+uz@ya+!_h-$|7YZ#j93|xE3H$$9p9s1;P-FrJ(b{c)HYRM>P}N} z*R!>R6P)HS^V$y-={TJV{yr(QutVbPv7iHdiOZ~|`-`M+$`Gw_h@*1llvlbx^jA#J z(I>Yn`Y`5@{0a3^__^M>;rZp6F(6|*>}bDHah|$*f;~7*X@`Q?OeU$ThI&-((>6dcgM7?CsMa2Iu4xvvqG`4 ztrJ@tP>!s)1^r_+aDr=&ArQPM1m2vuRBfWKMLp~In zf8)Jo7bM>WXRW7mGP8;@KZ-UUHubd0T}_MRNh`}Lb3?W!VSZP3wf^Ptv0wJ{o2H2 zv^6JcJ&u*`wefiD$qnEga1R_=OuCxto9!G@N`J97@-3C(swgu%c4t@}-jOi6Xp7>X z6YIa8Pf1Ejs`psahM?73_@^f5UTrUx^Xrc0I((OhKCvTauryriUzBp|5~9l@mFjOf z4f!|LFPI@UBQpb3MmP1-1nU^9GUUW=cXPm1<^{}f=!ZxysUs{2Uj$F)6JqL(S$1lvT!p&HIH_Eg>L4b zVVj;o;pa7zTA!Jbk%b-}Jw7!Mw05Nh41A0VP}gcJ75o#R!6H!GwfCz0(Rs-QnBAK> zM3yaXkSQ1v?ixBsVHOqMjYn(5s2`G_Z&guG+skN`cVt1PqN-!X7m?5p>CX-}ogL;4 z4u>X|A;suN;|`uZl&^V{Mn`g};!$@lItV@T_W%#hRH#dLA9o*wND6+m$;2E|3ZHI6 zng3PREtAfo(Q4V-7Qe1PxkM|Nx!5C|xEz<5I<>@A`pfx2IAU2+KwrzG&G5;rwkH2N+`m+^- z0`hKz4leN4aJuJHP_p;8348C}4LKgi(@imAp3jkl#>rVZftR^6lRtlDX+a0{F;9>7 zQ}kXLI=_{Qte*FupRn+bxqK*xjf!{CO}SZf-Uy<&gGb@W-=K^Wy0%Sus_%jS{Ckey z$G7w`xPhDO*gkAE4SxXoI%x2WHT%=Z*?S!l(d5+B)YVnR$G5aIo$FH`5SrL!c(IX` zO0S;U)C`F#cmDl{N51(svoii1K=BiGnq@*6RFG0QP*u}hUUPsi^Wg3If+pn0i{BDo zCI(txOmBXy>B&0h3{~~mb)zsV`O{u3-|QLKS(y8^Lr3peTeVP!__G649&6QB|E+nkFmg>1sCjw0*iq|*_AF=fW37L zi}&ZDd~itR0~z?6xhK2DsRH$C()z!*Nbs7Jl4W;9Y;YCfHo(k@3$ZnsE}U+Gaxss_YHyxLs_1ptcf!d9~gzy{K(x7MD^( sckRmd#($Lb6AYF9SoB8UsT^3j@P1pisjL z28L1t28LG&3=CE?7#PG0=Ijcz0ZOnXdAqy(2LsNC?{|PA>?NMQuI#T^1bCT*#E(91 z0t%^?xJHzuB$lLFB^RXvDF!10BQsqC6J0~I5JO`t14AoQ6Kw+nD+2?AS@FkEH00)| zWTsW(*5G*jc@Id5mY z_SU|%HDgEGTkpv+6P(W0a*8e1=?YUkr|Ua+-t^~|=Zn8Tw6J#7U@tp7-~NyNpY^Zz zz3+ZK>BQNIp`zK`?=Nvz`T3WXoDR#J{@6D?<9SCU_>Lmrs3Ksp@-aKF0y_ae84%an;^?@%qi#MB`P(YAD8`7R$=_^N#fKK$`jMIccsOBy#MO`iGL<{ zlKWC>=TGgQ{5k7|W+mV02=VNXpAz%t|J_>67jeK}iCO8G(nKM%pzg_Yiu`XL-|%Lp z%TK45IkWq1_h>(UD3`pu@X#OOsXw1geB4^JE|8_;4@wZVxr;OLmvdQSkZ_e@s|q#-#=pj%9n-3mmU!_D`HS V)#@zY8DO4g@O1TaS?83{1OP=m4uk*z diff --git a/build/linux/icons/alchementary-link-256.png b/build/linux/icons/alchementary-link-256.png deleted file mode 100644 index f3d119f53dee4ef47198165a7a95f8b827a6d2f0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22157 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 diff --git a/build/linux/icons/alchementary-link-32.png b/build/linux/icons/alchementary-link-32.png deleted file mode 100644 index 36f2ac34f5d59f55e13119f6c4cf2f56b00e7732..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1994 zcmZ`)cTm#_7XAeYAweKGu5?5}nt%xcLg-aOQ9uk*#2{sj94sQmzye7SaTP+OXi!S% z#RU;JfRs=}3sn%71*IfZfq*on=w000m{ z+giEsP~q={3-YSsN^$MqjjyeXBLGAx0|4Ox0POQ9!V&<4BLH9p3jio807!&n)jJ#U z27Wt$~ynVSE@K(uB5lbty)OyHrdf{Z# z0@9Exenue+L9IJIz)yV7k684MVoLt{Ys{XdpqU?WqATK$XUF2=oq^}TE^exPCS+rWXHzO1Ml#)Xue79o^e z#jaql-k)?i0n@GF7Zz4TFincd=fEU|RSU~@naMk+YE!D4y=ptjgIv0AAxrAo88RT+ z`*3n|SdB`Rycb#SyAnemnLHiuPjv;-sO=Yt!0EObp{Id!a77ETp-w>z5mi+G!JHCU zWTz}$urbrx5Swp&djex9f7Bx0(!>r_NjrnSIU;;4FWqja7!TWNK;zZJ+9jAOTbzgr z`NHGPNrDa3h*1-0i@usW9h_M~e}4x0qD;d-IE?<(q7_zKTBZwYy*@K{y=4<+7&c;P`1&W-=RK`2T${Ax5#8cuo7!tFlUBo^>{cb>(+3;; zK8Ihstphl0{`}DkqOR#xFp~JiSee)DcF{@6Ju<^fvpScilD_VSlYt=3a{H!|nwa34 zMVWnvU(S^^uHml;vNqAsx138f1_wD0r7TY|Au~rNwQ8oNCw1-h2$@xSuRNTJ92UH3 zNqk>IDdHA<{E~JCY;$Y8mu3QIr|0Z&dtqPKNSt19hl*mMjWYMo(sv6B-@7i9$(r z8Hh>YlgQTjr?c}>neCo~b@jf%)lS8@WI|&41ETjDRF71fchLIjH6gF(vuylsNoHjw z#M}8w$uwju@(yYm?5=e)<0fc)sH0rgujQRXRX@_fqnN1EJ5CoPj!(4t%*g3&d0`*t z;Ij-WwdR+;;AWmQ=3)5c8vA_}&U=PqkfksOLCYxQD-rQoC%ATeE=kx^X!Y5Ri``CNg0O_i~|BUydLw`HKZMyb>okSI$FCm3{# z^U+FCAo#l{v-MZoPxu@Eu$A7g|JmrefkdC{?eOmfPLliV%oypAXG(&!tcvZp3Lw{c zZ6yF<*sclQGn=O364g}*S+<9rrNlP+>x|WC$;Qe0J6BAPWu3KwJ8@>gU&Xla_uQ?= z5;Ky6)k1zZ94-bENSA6f3@DJCha(kl3e1{*a{HO^;F$ku_!*@=ApV2LuD(@KE?ivM zS#aAIT3<}32dZh1sh~<+~P(t$?hvrTRZ2K|Z zOp8Y2QavcUqtE;MDRFTJrR&BtX`|+$98k`prYPuT(Ql_ni{wY=XCdkwYZGqY_Ed&O z3zL!#O#;4=Cn^I-CVA2JoRe6^&wuhRtB%=MWHb?4oft4vrIDfL_Ytbq4D43Qy12AE z(HtqYTH14-6DsR-KRjo>&MS=9t$_6xLBU{VS#^LMECEcVW~x`m7>gutJt>84%=DC7 zzH3x^JJ_97BLhQ5EgQIbcUDw;?5+;N9 O=>W9#WvfaHEa{(mkCx#8 diff --git a/build/linux/icons/alchementary-link-48.png b/build/linux/icons/alchementary-link-48.png deleted file mode 100644 index ea71092dd27fe2da134f7ba211139c4b1070b5a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2981 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 diff --git a/build/linux/icons/alchementary-link-512.png b/build/linux/icons/alchementary-link-512.png deleted file mode 100644 index 829caabfb85585a37d59793762e2b0c15d36731b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 54200 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 diff --git a/build/linux/icons/alchementary-link-64.png b/build/linux/icons/alchementary-link-64.png deleted file mode 100644 index 883c2113cfca3a1777f4e0d9df3bf71c69cfadb0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4089 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 - - - - - - - - - - true/pm - permonitorv2,permonitor - - - \ No newline at end of file diff --git a/cmd/genfixtures/main.go b/cmd/genfixtures/main.go deleted file mode 100644 index 19f118c..0000000 --- a/cmd/genfixtures/main.go +++ /dev/null @@ -1,89 +0,0 @@ -// Command genfixtures writes the Go-generated compatibility fixtures that the -// Rust port must read unchanged. Run from the repo root: -// -// go run ./cmd/genfixtures rust/core/tests/fixtures -package main - -import ( - "log" - "os" - "path/filepath" - "time" - - "github.com/andydixon/link/internal/exportimport" - "github.com/andydixon/link/internal/models" - "github.com/andydixon/link/internal/recording" - "github.com/andydixon/link/internal/secrets" - "github.com/andydixon/link/internal/storage" -) - -func must(err error) { - if err != nil { - log.Fatal(err) - } -} - -func main() { - out := os.Args[1] - must(os.RemoveAll(out)) - must(os.MkdirAll(out, 0o755)) - - store, err := storage.NewStore(filepath.Join(out, "link.db")) - must(err) - ts := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC) - var profiles []models.Profile - for i, proto := range []models.Protocol{models.ProtocolSSH, models.ProtocolTelnet, models.ProtocolRAW, models.ProtocolGitHub, models.ProtocolVNC, models.ProtocolIRC} { - p := models.NewProfile(proto) - p.ID = "00000000-0000-0000-0000-00000000000" + string(rune('1'+i)) - p.DisplayName = "Fixture " + string(proto) - p.Host = "host-" + string(proto) + ".example" - p.Tags = []string{"fixture", string(proto)} - p.Folder = "Fixtures" - p.Notes = "notes ✓" - p.CreatedAt, p.UpdatedAt = ts, ts - switch proto { - case models.ProtocolSSH: - p.Username = "alice" - p.Favourite = true - p.LastConnected = &ts - p.SSH.PrivateKeyPath = "/home/alice/.ssh/id_ed25519" - p.SSH.JumpHosts = []models.JumpHost{{Host: "jump.example", Port: 2222, Username: "bob", AuthMethod: "publickey", SecretMode: models.SecretModeSaved}} - p.SSH.PortForwards = []models.PortForward{{ID: "pf1", Direction: models.PortForwardLocal, BindHost: "127.0.0.1", BindPort: 8080, DestHost: "localhost", DestPort: 80, Enabled: true, Label: "web"}} - p.SSH.EnvVars = map[string]string{"LANG": "en_GB.UTF-8"} - p.SSH.LoginScripts = []models.LoginScriptStep{{Type: "wait", Value: "$ ", Timeout: 5}, {Type: "send", Value: "uptime"}} - p.SSH.ProxyType = "socks5" - p.SSH.ProxyHost = "proxy.example" - p.SSH.ProxyPort = 1080 - p.SSH.AutoReconnect = true - case models.ProtocolRAW: - p.RAW.PayloadPresets = []models.PayloadPreset{{ID: "pp1", Name: "hello", Payload: "aGVsbG8=", Format: "text"}} - case models.ProtocolIRC: - p.IRC.AutoJoinRooms = []string{"#rust", "#go"} - p.IRC.UseTLS = true - } - must(store.CreateProfile(&p)) - profiles = append(profiles, p) - } - must(store.SaveKnownHost(&models.KnownHost{ID: "kh1", Host: "host-ssh.example", Port: 22, KeyType: "ssh-ed25519", Fingerprint: "SHA256:abc", PublicKey: "AAAA", FirstSeen: ts, LastSeen: ts, Trusted: true})) - must(store.CreateSnippet(&models.Snippet{ID: "sn1", Name: "uptime", Command: "uptime", Description: "load", Folder: "ops", Tags: []string{"x"}, CreatedAt: ts, UpdatedAt: ts})) - must(store.SaveIRCLastRooms(profiles[5].ID, []string{"#rust", "#nexus"})) - prefs := models.DefaultAppPreferences() - prefs.TerminalTheme = "dracula" - prefs.SidebarWidth = 320 - must(store.SavePreferences(&prefs)) - must(store.Close()) - - vault := secrets.NewSecretStore(filepath.Join(out, "vault")) - must(vault.Initialise("correct horse battery staple")) - must(vault.SetSecret("profile:00000000-0000-0000-0000-000000000001:password", "s3cret-ssh")) - must(vault.SetSecret("profile:00000000-0000-0000-0000-000000000001:totp", "JBSWY3DPEHPK3PXP")) - - must(exportimport.EncryptAndExport(profiles, "export-pass", filepath.Join(out, "profiles.link"))) - - rec, err := recording.NewRecorder(filepath.Join(out, "recordings"), "rec1", "Fixture — host", 80, 24) - must(err) - rec.WriteOutput([]byte("hello \x1b[1mworld\x1b[0m\r\n")) - rec.WriteInput([]byte("ls\r")) - must(rec.Close()) - log.Println("fixtures written to", out) -} diff --git a/cmd/vscode-bridge/main.go b/cmd/vscode-bridge/main.go deleted file mode 100644 index bb3bec8..0000000 --- a/cmd/vscode-bridge/main.go +++ /dev/null @@ -1,357 +0,0 @@ -// Command vscode-bridge exposes the Link backend to VS Code extensions over a -// localhost-only JSON-RPC and event stream. -package main - -import ( - "context" - "crypto/rand" - "encoding/hex" - "encoding/json" - "errors" - "flag" - "fmt" - "log" - "net" - "net/http" - "os" - "os/signal" - "path/filepath" - "reflect" - "strings" - "sync" - "syscall" - "time" - - "github.com/andydixon/link/internal/api" - "github.com/andydixon/link/internal/audit" - "github.com/andydixon/link/internal/config" - "github.com/andydixon/link/internal/preferences" - "github.com/andydixon/link/internal/recording" - "github.com/andydixon/link/internal/secrets" - "github.com/andydixon/link/internal/sessions" - "github.com/andydixon/link/internal/storage" -) - -type rpcRequest struct { - Method string `json:"method"` - Args []json.RawMessage `json:"args"` -} - -type rpcResponse struct { - Result any `json:"result,omitempty"` - Error string `json:"error,omitempty"` -} - -type runtimeEventRequest struct { - Name string `json:"name"` - Payload map[string]any `json:"payload"` -} - -const ( - bridgeReadHeaderTimeout = 5 * time.Second - bridgeReadTimeout = 15 * time.Second - bridgeIdleTimeout = 60 * time.Second - bridgeMaxHeaderBytes = 16 << 10 - bridgeMaxJSONBodyBytes = 1 << 20 -) - -type eventBroker struct { - mu sync.Mutex - clients map[chan []byte]struct{} -} - -func newEventBroker() *eventBroker { - return &eventBroker{clients: make(map[chan []byte]struct{})} -} - -func (b *eventBroker) publish(name string, payload any) { - msg, err := json.Marshal(map[string]any{"name": name, "payload": payload}) - if err != nil { - return - } - b.mu.Lock() - defer b.mu.Unlock() - for ch := range b.clients { - select { - case ch <- msg: - default: - } - } -} - -func (b *eventBroker) subscribe() chan []byte { - ch := make(chan []byte, 64) - b.mu.Lock() - b.clients[ch] = struct{}{} - b.mu.Unlock() - return ch -} - -func (b *eventBroker) unsubscribe(ch chan []byte) { - b.mu.Lock() - delete(b.clients, ch) - close(ch) - b.mu.Unlock() -} - -type bridgeApp struct { - service *api.Service - manager *sessions.Manager - store *storage.Store - secrets *secrets.SecretStore - events *eventBroker -} - -func main() { - listenAddr := flag.String("listen", "127.0.0.1:0", "address for the localhost bridge") - flag.Parse() - - bridge, err := newBridgeApp() - if err != nil { - log.Fatalf("initialise bridge: %v", err) - } - defer bridge.close() - - token, err := randomToken() - if err != nil { - log.Fatalf("create auth token: %v", err) - } - - mux := http.NewServeMux() - mux.HandleFunc("/health", bridge.auth(token, bridge.handleHealth)) - mux.HandleFunc("/rpc", bridge.auth(token, bridge.handleRPC)) - mux.HandleFunc("/events", bridge.auth(token, bridge.handleEvents)) - mux.HandleFunc("/runtime/event", bridge.auth(token, bridge.handleRuntimeEvent)) - - server := newBridgeHTTPServer(mux) - listener, err := net.Listen("tcp", *listenAddr) - if err != nil { - log.Fatalf("listen: %v", err) - } - - fmt.Fprintf(os.Stdout, "{\"addr\":%q,\"token\":%q}\n", listener.Addr().String(), token) - - go func() { - if err := server.Serve(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Printf("server error: %v", err) - } - }() - - sigCh := make(chan os.Signal, 1) - signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) - <-sigCh - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - _ = server.Shutdown(ctx) -} - -func newBridgeApp() (*bridgeApp, error) { - cfg, err := config.Load() - if err != nil { - return nil, err - } - if err := cfg.EnsureDirectories(); err != nil { - return nil, err - } - store, err := storage.NewStore(cfg.DBPath) - if err != nil { - return nil, err - } - - secretStore := secrets.NewSecretStore(cfg.SecretPath) - keychainStore := secrets.NewKeychainStore("AlchementaryLink") - auditLog := audit.NewLogger(store) - prefs := preferences.NewManager(store) - if err := prefs.Load(); err != nil { - log.Printf("warning: failed to load preferences: %v", err) - } - - recordStore := recording.NewStore(filepath.Join(cfg.DataDir, "recordings")) - manager := sessions.NewManager(store, secretStore, keychainStore, auditLog, recordStore) - service := api.NewService(store, secretStore, keychainStore, manager, auditLog, prefs) - events := newEventBroker() - manager.SetEventSink(events.publish) - service.SetEventSink(events.publish) - - return &bridgeApp{ - service: service, - manager: manager, - store: store, - secrets: secretStore, - events: events, - }, nil -} - -func (a *bridgeApp) close() { - if a.service != nil { - a.service.Shutdown() - } - if a.manager != nil { - a.manager.Shutdown() - } - if a.store != nil { - _ = a.store.Close() - } - if a.secrets != nil { - a.secrets.Lock() - } -} - -func (a *bridgeApp) auth(token string, next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if r.Header.Get("Authorization") != "Bearer "+token { - http.Error(w, "unauthorised", http.StatusUnauthorized) - return - } - next(w, r) - } -} - -func (a *bridgeApp) handleHealth(w http.ResponseWriter, _ *http.Request) { - writeJSON(w, map[string]any{"ok": true}) -} - -func (a *bridgeApp) handleRPC(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - var req rpcRequest - if err := decodeLimitedJSON(w, r, &req); err != nil { - writeJSON(w, rpcResponse{Error: err.Error()}) - return - } - result, err := callService(a.service, req.Method, req.Args) - if err != nil { - writeJSON(w, rpcResponse{Error: err.Error()}) - return - } - writeJSON(w, rpcResponse{Result: result}) -} - -func (a *bridgeApp) handleRuntimeEvent(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - var req runtimeEventRequest - if err := decodeLimitedJSON(w, r, &req); err != nil { - writeJSON(w, rpcResponse{Error: err.Error()}) - return - } - switch req.Name { - case "session:secret:response": - requestID, _ := req.Payload["requestID"].(string) - value, _ := req.Payload["value"].(string) - cancelled, _ := req.Payload["cancelled"].(bool) - a.service.RespondSecret(requestID, value, cancelled) - a.manager.RespondSecret(requestID, value, cancelled) - case "session:hostkey:response": - requestID, _ := req.Payload["requestID"].(string) - accepted, _ := req.Payload["accepted"].(bool) - a.manager.RespondHostKey(requestID, accepted) - default: - writeJSON(w, rpcResponse{Error: "unsupported runtime event"}) - return - } - writeJSON(w, rpcResponse{Result: true}) -} - -func (a *bridgeApp) handleEvents(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodGet { - http.Error(w, "method not allowed", http.StatusMethodNotAllowed) - return - } - w.Header().Set("Content-Type", "text/event-stream") - w.Header().Set("Cache-Control", "no-cache") - w.Header().Set("Connection", "keep-alive") - - flusher, ok := w.(http.Flusher) - if !ok { - http.Error(w, "streaming unsupported", http.StatusInternalServerError) - return - } - - ch := a.events.subscribe() - defer a.events.unsubscribe(ch) - fmt.Fprint(w, ": connected\n\n") - flusher.Flush() - - for { - select { - case <-r.Context().Done(): - return - case msg := <-ch: - fmt.Fprintf(w, "data: %s\n\n", msg) - flusher.Flush() - } - } -} - -func callService(service *api.Service, methodName string, rawArgs []json.RawMessage) (any, error) { - if strings.TrimSpace(methodName) == "" { - return nil, errors.New("method is required") - } - method := reflect.ValueOf(service).MethodByName(methodName) - if !method.IsValid() { - return nil, fmt.Errorf("unknown service method %q", methodName) - } - mt := method.Type() - if len(rawArgs) != mt.NumIn() { - return nil, fmt.Errorf("%s expects %d args, got %d", methodName, mt.NumIn(), len(rawArgs)) - } - - args := make([]reflect.Value, mt.NumIn()) - for i := 0; i < mt.NumIn(); i++ { - target := reflect.New(mt.In(i)) - if err := json.Unmarshal(rawArgs[i], target.Interface()); err != nil { - return nil, fmt.Errorf("arg %d: %w", i, err) - } - args[i] = target.Elem() - } - - values := method.Call(args) - if len(values) == 0 { - return true, nil - } - last := values[len(values)-1] - if last.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) && !last.IsNil() { - return nil, last.Interface().(error) - } - if len(values) == 1 { - if last.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) { - return true, nil - } - return last.Interface(), nil - } - return values[0].Interface(), nil -} - -func randomToken() (string, error) { - var b [32]byte - if _, err := rand.Read(b[:]); err != nil { - return "", err - } - return hex.EncodeToString(b[:]), nil -} - -func newBridgeHTTPServer(handler http.Handler) *http.Server { - return &http.Server{ - Handler: handler, - ReadHeaderTimeout: bridgeReadHeaderTimeout, - ReadTimeout: bridgeReadTimeout, - IdleTimeout: bridgeIdleTimeout, - MaxHeaderBytes: bridgeMaxHeaderBytes, - } -} - -func decodeLimitedJSON(w http.ResponseWriter, r *http.Request, dst any) error { - r.Body = http.MaxBytesReader(w, r.Body, bridgeMaxJSONBodyBytes) - return json.NewDecoder(r.Body).Decode(dst) -} - -func writeJSON(w http.ResponseWriter, value any) { - w.Header().Set("Content-Type", "application/json") - _ = json.NewEncoder(w).Encode(value) -} diff --git a/cmd/vscode-bridge/main_test.go b/cmd/vscode-bridge/main_test.go deleted file mode 100644 index ca3dbaf..0000000 --- a/cmd/vscode-bridge/main_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func TestNewBridgeHTTPServerSetsResourceLimits(t *testing.T) { - server := newBridgeHTTPServer(http.NewServeMux()) - - if server.ReadHeaderTimeout != bridgeReadHeaderTimeout { - t.Fatalf("ReadHeaderTimeout = %s, want %s", server.ReadHeaderTimeout, bridgeReadHeaderTimeout) - } - if server.ReadTimeout != bridgeReadTimeout { - t.Fatalf("ReadTimeout = %s, want %s", server.ReadTimeout, bridgeReadTimeout) - } - if server.IdleTimeout != bridgeIdleTimeout { - t.Fatalf("IdleTimeout = %s, want %s", server.IdleTimeout, bridgeIdleTimeout) - } - if server.MaxHeaderBytes != bridgeMaxHeaderBytes { - t.Fatalf("MaxHeaderBytes = %d, want %d", server.MaxHeaderBytes, bridgeMaxHeaderBytes) - } -} - -func TestHandleRPCRejectsOversizedJSONBody(t *testing.T) { - app := &bridgeApp{} - largeBody := `{"method":"ListProfiles","args":[],"pad":"` + strings.Repeat("x", bridgeMaxJSONBodyBytes) + `"}` - req := httptest.NewRequest(http.MethodPost, "/rpc", strings.NewReader(largeBody)) - rec := httptest.NewRecorder() - - app.handleRPC(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) - } - var resp rpcResponse - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { - t.Fatalf("response is not JSON: %v", err) - } - if !strings.Contains(resp.Error, "request body too large") { - t.Fatalf("error = %q, want request body limit error", resp.Error) - } -} - -func TestHandleRuntimeEventRejectsOversizedJSONBody(t *testing.T) { - app := &bridgeApp{} - largeBody := `{"name":"session:secret:response","payload":{"pad":"` + strings.Repeat("x", bridgeMaxJSONBodyBytes) + `"}}` - req := httptest.NewRequest(http.MethodPost, "/runtime/event", strings.NewReader(largeBody)) - rec := httptest.NewRecorder() - - app.handleRuntimeEvent(rec, req) - - if rec.Code != http.StatusOK { - t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK) - } - var resp rpcResponse - if err := json.Unmarshal(rec.Body.Bytes(), &resp); err != nil { - t.Fatalf("response is not JSON: %v", err) - } - if !strings.Contains(resp.Error, "request body too large") { - t.Fatalf("error = %q, want request body limit error", resp.Error) - } -} diff --git a/frontend/embed.go b/frontend/embed.go deleted file mode 100644 index e4d3898..0000000 --- a/frontend/embed.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package frontend provides the embedded frontend assets for the Wails application. -package frontend - -import "embed" - -// Assets contains the frontend files served by the Wails asset server. -// We embed the root index.html and the entire src/ tree (stylesheets, -// scripts, vendor libs and static assets) so that `go build` works -// without a separate npm build step. -// -//go:embed index.html -//go:embed all:src -var Assets embed.FS diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index 23a4494..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,352 +0,0 @@ - - - - - - Alchementary Link - - - - - - - - - - - - - - - - - - - - - - - -

- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/frontend/package-lock.json b/frontend/package-lock.json deleted file mode 100644 index e40b582..0000000 --- a/frontend/package-lock.json +++ /dev/null @@ -1,547 +0,0 @@ -{ - "name": "alchementary-link-frontend", - "version": "0.1.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "alchementary-link-frontend", - "version": "0.1.0", - "hasInstallScript": true, - "dependencies": { - "@novnc/novnc": "^1.5.0", - "@xterm/addon-fit": "0.11.0", - "@xterm/addon-search": "0.16.0", - "@xterm/addon-unicode11": "0.9.0", - "@xterm/addon-web-links": "0.12.0", - "@xterm/xterm": "6.0.0" - }, - "devDependencies": { - "esbuild": "^0.28.1" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", - "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", - "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", - "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", - "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", - "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", - "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", - "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", - "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", - "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", - "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", - "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", - "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", - "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", - "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", - "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", - "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", - "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", - "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", - "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", - "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", - "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", - "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", - "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", - "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", - "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@novnc/novnc": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@novnc/novnc/-/novnc-1.7.0.tgz", - "integrity": "sha512-ucEJOx4T2avIRCleodk7YobZj5O2Ga2AeLfQ69A/yjG9HHba2+PDgwSkN3FttrmG+70ZGx21sElNFouK13RzyA==", - "license": "MPL-2.0" - }, - "node_modules/@xterm/addon-fit": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", - "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", - "license": "MIT" - }, - "node_modules/@xterm/addon-search": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-search/-/addon-search-0.16.0.tgz", - "integrity": "sha512-9OeuBFu0/uZJPu+9AHKY6g/w0Czyb/Ut0A5t79I4ULoU4IfU5BEpPFVGQxP4zTTMdfZEYkVIRYbHBX1xWwjeSA==", - "license": "MIT" - }, - "node_modules/@xterm/addon-unicode11": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-unicode11/-/addon-unicode11-0.9.0.tgz", - "integrity": "sha512-FxDnYcyuXhNl+XSqGZL/t0U9eiNb/q3EWT5rYkQT/zuig8Gz/VagnQANKHdDWFM2lTMk9ly0EFQxxxtZUoRetw==", - "license": "MIT" - }, - "node_modules/@xterm/addon-web-links": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.12.0.tgz", - "integrity": "sha512-4Smom3RPyVp7ZMYOYDoC/9eGJJJqYhnPLGGqJ6wOBfB8VxPViJNSKdgRYb8NpaM6YSelEKbA2SStD7lGyqaobw==", - "license": "MIT" - }, - "node_modules/@xterm/xterm": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz", - "integrity": "sha512-TQwDdQGtwwDt+2cgKDLn0IRaSxYu1tSUjgKarSDkUM0ZNiSRXFpjxEsvc/Zgc5kq5omJ+V0a8/kIM2WD3sMOYg==", - "license": "MIT", - "workspaces": [ - "addons/*" - ] - }, - "node_modules/esbuild": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", - "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.1", - "@esbuild/android-arm": "0.28.1", - "@esbuild/android-arm64": "0.28.1", - "@esbuild/android-x64": "0.28.1", - "@esbuild/darwin-arm64": "0.28.1", - "@esbuild/darwin-x64": "0.28.1", - "@esbuild/freebsd-arm64": "0.28.1", - "@esbuild/freebsd-x64": "0.28.1", - "@esbuild/linux-arm": "0.28.1", - "@esbuild/linux-arm64": "0.28.1", - "@esbuild/linux-ia32": "0.28.1", - "@esbuild/linux-loong64": "0.28.1", - "@esbuild/linux-mips64el": "0.28.1", - "@esbuild/linux-ppc64": "0.28.1", - "@esbuild/linux-riscv64": "0.28.1", - "@esbuild/linux-s390x": "0.28.1", - "@esbuild/linux-x64": "0.28.1", - "@esbuild/netbsd-arm64": "0.28.1", - "@esbuild/netbsd-x64": "0.28.1", - "@esbuild/openbsd-arm64": "0.28.1", - "@esbuild/openbsd-x64": "0.28.1", - "@esbuild/openharmony-arm64": "0.28.1", - "@esbuild/sunos-x64": "0.28.1", - "@esbuild/win32-arm64": "0.28.1", - "@esbuild/win32-ia32": "0.28.1", - "@esbuild/win32-x64": "0.28.1" - } - } - } -} diff --git a/frontend/package.json b/frontend/package.json deleted file mode 100644 index b1a76c3..0000000 --- a/frontend/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "alchementary-link-frontend", - "version": "0.1.0", - "private": true, - "description": "Frontend for Alchementary Link desktop SSH client", - "scripts": { - "install:assets": "node scripts/copy-assets.js", - "bundle:novnc": "node scripts/bundle-novnc.js", - "build": "node scripts/build.js", - "dev": "node scripts/build.js --watch", - "test": "node scripts/test-session-lifecycle.js && node scripts/test-irc.js && node scripts/test-regressions.js", - "postinstall": "npm run install:assets && npm run bundle:novnc" - }, - "dependencies": { - "@novnc/novnc": "^1.5.0", - "@xterm/addon-fit": "0.11.0", - "@xterm/addon-search": "0.16.0", - "@xterm/addon-unicode11": "0.9.0", - "@xterm/addon-web-links": "0.12.0", - "@xterm/xterm": "6.0.0" - }, - "devDependencies": { - "esbuild": "^0.28.1" - } -} diff --git a/frontend/package.json.md5 b/frontend/package.json.md5 deleted file mode 100755 index e26e0e5..0000000 --- a/frontend/package.json.md5 +++ /dev/null @@ -1 +0,0 @@ -24ecf1ed6af3b41cf2f6d8fa0bc732ec \ No newline at end of file diff --git a/frontend/scripts/build.js b/frontend/scripts/build.js deleted file mode 100644 index 8372299..0000000 --- a/frontend/scripts/build.js +++ /dev/null @@ -1,233 +0,0 @@ -/** - * build.js — Build script for the Alchementary Link frontend. - * - * In production mode (no flags): copies vendor assets, assembles the - * dist/ directory with all files needed by the Wails embed, and validates - * that every required file is present. - * - * In watch mode (--watch): performs the same build, then watches for - * source changes and prints a notification when files change. Wails dev - * mode handles the actual live-reload; this script simply keeps the - * process alive so the frontend:dev:watcher contract is satisfied. - */ -'use strict'; - -const fs = require('fs'); -const path = require('path'); -const { execSync } = require('child_process'); - -const frontendDir = path.resolve(__dirname, '..'); -const distDir = path.join(frontendDir, 'dist'); -const watchMode = process.argv.includes('--watch'); - -// Files that must exist after a successful build (relative to frontend/) -const requiredFiles = [ - 'dist/index.html', - 'dist/src/css/lib/xterm.css', - 'dist/src/js/lib/xterm.min.js', - 'dist/src/js/lib/xterm-addon-fit.min.js', - 'dist/src/js/lib/xterm-addon-search.min.js', - 'dist/src/js/lib/xterm-addon-web-links.min.js', - 'dist/src/js/wails-service.js', -]; - -/** - * Recursively copies a directory tree, creating parents as needed. - */ -function copyDirSync(src, dest) { - fs.mkdirSync(dest, { recursive: true }); - for (const entry of fs.readdirSync(src, { withFileTypes: true })) { - const srcPath = path.join(src, entry.name); - const destPath = path.join(dest, entry.name); - if (entry.isDirectory()) { - copyDirSync(srcPath, destPath); - } else { - fs.copyFileSync(srcPath, destPath); - } - } -} - -/** - * Copies one file and creates the parent directory if needed. - */ -function copyFileSync(src, dest) { - fs.mkdirSync(path.dirname(dest), { recursive: true }); - fs.copyFileSync(src, dest); -} - -/** - * Removes a directory tree if it exists. - */ -function cleanDir(dir) { - if (fs.existsSync(dir)) { - fs.rmSync(dir, { recursive: true, force: true }); - } -} - -function copyWailsRuntimeBindings(srcRoot, destRoot) { - const runtimeFiles = [ - 'go/api/Service.js', - 'runtime/runtime.js', - ]; - - for (const relPath of runtimeFiles) { - const src = path.join(srcRoot, relPath); - if (fs.existsSync(src)) { - copyFileSync(src, path.join(destRoot, relPath)); - } - } -} - -function walkFiles(dir, predicate, files = []) { - if (!fs.existsSync(dir)) return files; - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const full = path.join(dir, entry.name); - if (entry.isDirectory()) { - walkFiles(full, predicate, files); - } else if (entry.isFile() && predicate(full)) { - files.push(full); - } - } - return files; -} - -function cleanupDistAssets() { - console.log('[build] Cleaning production frontend assets...'); - const sourceMapFiles = walkFiles(distDir, (file) => file.endsWith('.js') || file.endsWith('.css')); - let mapCommentCount = 0; - for (const file of sourceMapFiles) { - const input = fs.readFileSync(file, 'utf8'); - const output = input - .replace(/(?:\r?\n)?\/\/# sourceMappingURL=.*?(?=\r?\n|$)/g, '') - .replace(/(?:\r?\n)?\/\*# sourceMappingURL=.*?\*\/(?=\r?\n|$)/g, ''); - if (output !== input) { - fs.writeFileSync(file, output); - mapCommentCount += 1; - } - } - - if (mapCommentCount > 0) { - console.log(`[build] Removed ${mapCommentCount} source-map reference(s).`); - } -} - -/** - * Runs the asset copy script to pull xterm files into src/js/lib/ and - * src/css/lib/. - */ -function copyAssets() { - console.log('[build] Copying vendor library assets…'); - execSync('node scripts/copy-assets.js', { - cwd: frontendDir, - stdio: 'inherit', - }); - execSync('node scripts/bundle-novnc.js', { - cwd: frontendDir, - stdio: 'inherit', - }); -} - -/** - * Assembles the dist/ directory with everything the Wails embed needs: - * dist/index.html - * dist/src/css/ (application + lib stylesheets) - * dist/src/js/ (application + lib scripts) - * dist/wailsjs/ (generated backend bindings, if present) - * dist/src/components/ (if present) - * dist/src/assets/ (if present) - */ -function buildDist() { - console.log('[build] Assembling dist/ directory…'); - - // Start fresh so stale files are removed - cleanDir(distDir); - fs.mkdirSync(distDir, { recursive: true }); - - // Copy the root index.html - fs.copyFileSync( - path.join(frontendDir, 'index.html'), - path.join(distDir, 'index.html'), - ); - - // Copy the entire src/ tree (CSS, JS, components, assets, lib files) - const srcDir = path.join(frontendDir, 'src'); - if (fs.existsSync(srcDir)) { - copyDirSync(srcDir, path.join(distDir, 'src')); - } - - // Copy only runtime Wails bindings so embedded production assets do not - // carry TypeScript declarations and model files. - const wailsjsDir = path.join(frontendDir, 'wailsjs'); - if (fs.existsSync(wailsjsDir)) { - copyWailsRuntimeBindings(wailsjsDir, path.join(distDir, 'wailsjs')); - } - - console.log('[build] dist/ assembled.'); -} - -/** - * Validates that every required file exists. - * Returns true if all files are present. - */ -function validate() { - console.log('[build] Validating required files…'); - let valid = true; - for (const relPath of requiredFiles) { - const full = path.join(frontendDir, relPath); - if (!fs.existsSync(full)) { - console.error(` ✗ Missing: ${relPath}`); - valid = false; - } else { - console.log(` ✓ ${relPath}`); - } - } - return valid; -} - -/** - * Watches the src/ directory for changes (development mode). - * Wails dev mode picks up the changes; we just keep the process alive. - */ -function watch() { - const srcDir = path.join(frontendDir, 'src'); - console.log(`[build] Watching ${srcDir} for changes…`); - - // Use fs.watch recursively where supported (macOS, Windows). - // On Linux this falls back to non-recursive — acceptable for dev use. - try { - fs.watch(srcDir, { recursive: true }, (_event, filename) => { - if (filename) { - const stamp = new Date().toLocaleTimeString('en-GB'); - console.log(`[build] ${stamp} — changed: ${filename}`); - } - }); - } catch (err) { - console.warn('[build] Recursive watch not supported; polling fallback.'); - } - - // Prevent the process from exiting - setInterval(() => {}, 60_000); -} - -// --- Main --- - -try { - copyAssets(); -} catch (err) { - console.error('[build] Asset copy failed:', err.message); - process.exit(1); -} - -buildDist(); -cleanupDistAssets(); - -if (!validate()) { - console.error('[build] Validation failed — some required files are missing.'); - process.exit(1); -} - -console.log('[build] Frontend build complete.'); - -if (watchMode) { - watch(); -} diff --git a/frontend/scripts/bundle-novnc.js b/frontend/scripts/bundle-novnc.js deleted file mode 100644 index 6b21f10..0000000 --- a/frontend/scripts/bundle-novnc.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * bundle-novnc.js — Uses esbuild to bundle noVNC's RFB class into a single - * script that exposes window.noVNC.RFB for use in the frontend. - * - * noVNC 1.5+ uses top-level await which is incompatible with IIFE format, - * so we bundle as ESM then wrap in an async IIFE via banner/footer. - */ -"use strict"; - -const esbuild = require("esbuild"); -const path = require("path"); - -const entryPoint = path.resolve(__dirname, "novnc-entry.js"); -const outFile = path.resolve( - __dirname, - "..", - "src", - "js", - "lib", - "novnc.min.js", -); - -esbuild - .build({ - entryPoints: [entryPoint], - bundle: true, - minify: true, - format: "esm", - outfile: outFile, - target: ["safari15"], - logLevel: "info", - banner: { js: ";(async function() {" }, - footer: { js: "})();" }, - }) - .then(() => { - console.log( - "[bundle-novnc] noVNC bundled successfully → src/js/lib/novnc.min.js", - ); - }) - .catch((err) => { - console.error("[bundle-novnc] Failed:", err.message); - process.exit(1); - }); diff --git a/frontend/scripts/copy-assets.js b/frontend/scripts/copy-assets.js deleted file mode 100644 index 5a6ac97..0000000 --- a/frontend/scripts/copy-assets.js +++ /dev/null @@ -1,87 +0,0 @@ -/** - * copy-assets.js — Copies xterm library assets from node_modules into - * the frontend source tree so they can be served directly by Wails. - * - * Run automatically via the postinstall hook in package.json. - */ -"use strict"; - -const fs = require("fs"); -const path = require("path"); - -const frontendDir = path.resolve(__dirname, ".."); - -// Mapping: source (relative to node_modules) → destination (relative to frontend/) -const assets = [ - { - src: "@xterm/xterm/css/xterm.css", - dest: "src/css/lib/xterm.css", - }, - { - src: "@xterm/xterm/lib/xterm.js", - dest: "src/js/lib/xterm.min.js", - }, - { - src: "@xterm/addon-fit/lib/addon-fit.js", - dest: "src/js/lib/xterm-addon-fit.min.js", - }, - { - src: "@xterm/addon-search/lib/addon-search.js", - dest: "src/js/lib/xterm-addon-search.min.js", - }, - { - src: "@xterm/addon-unicode11/lib/addon-unicode11.js", - dest: "src/js/lib/xterm-addon-unicode11.min.js", - }, - { - src: "@xterm/addon-web-links/lib/addon-web-links.js", - dest: "src/js/lib/xterm-addon-web-links.min.js", - }, -]; - -/** - * Ensures the parent directory of the given file path exists. - */ -function ensureDir(filePath) { - const dir = path.dirname(filePath); - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } -} - -/** - * Copies a single asset, logging the result. - */ -function copyAsset(entry) { - const srcPath = path.join(frontendDir, "node_modules", entry.src); - const destPath = path.join(frontendDir, entry.dest); - - if (!fs.existsSync(srcPath)) { - console.error(` ✗ Source not found: ${entry.src}`); - return false; - } - - ensureDir(destPath); - fs.copyFileSync(srcPath, destPath); - console.log(` ✓ ${entry.src} → ${entry.dest}`); - return true; -} - -// --- Main --- - -console.log("[copy-assets] Copying xterm library files…"); - -let failures = 0; -for (const entry of assets) { - if (!copyAsset(entry)) { - failures++; - } -} - -if (failures > 0) { - console.error(`\n[copy-assets] ${failures} file(s) could not be copied.`); - console.error('[copy-assets] Have you run "npm install" first?'); - process.exit(1); -} - -console.log("[copy-assets] All assets copied successfully."); diff --git a/frontend/scripts/novnc-entry.js b/frontend/scripts/novnc-entry.js deleted file mode 100644 index 1958a13..0000000 --- a/frontend/scripts/novnc-entry.js +++ /dev/null @@ -1,7 +0,0 @@ -// Entry point for esbuild bundling of noVNC. -// Assigns RFB to window.noVNC so it's available as a global in the frontend. -import RFB from "@novnc/novnc"; -import { initLogging } from "../node_modules/@novnc/novnc/core/util/logging.js"; - -initLogging("error"); -window.noVNC = { RFB: RFB }; diff --git a/frontend/scripts/test-irc.js b/frontend/scripts/test-irc.js deleted file mode 100644 index e0abace..0000000 --- a/frontend/scripts/test-irc.js +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env node -const assert = require("assert"); -const fs = require("fs"); -const path = require("path"); -const vm = require("vm"); - -const frontendRoot = path.resolve(__dirname, ".."); -const source = fs.readFileSync(path.join(frontendRoot, "src/js/irc.js"), "utf8"); -const context = { - window: { - AlchementaryLink: { - createElement: () => { - throw new Error("formatting tests must not create DOM elements"); - }, - }, - }, -}; -vm.createContext(context); -vm.runInContext(source, context, { filename: "src/js/irc.js" }); - -const formatting = context.window.AlchementaryLink.IRCFormatting; -assert.ok(formatting, "IRC formatting helpers should be exported"); - -assert.strictEqual( - formatting.formatText("plain \x02bold\x02 \x1ditalic\x1d \x1funderlined\x1f"), - "plain \x1b[1mbold\x1b[22m \x1b[3mitalic\x1b[23m \x1b[4munderlined\x1b[24m", -); -assert.strictEqual( - formatting.formatText("\x0304red\x03 default"), - "\x1b[38;5;9mred\x1b[39;49m default", -); -assert.strictEqual( - formatting.formatText("\x0352extended \x0303,01matrix"), - "\x1b[38;5;196mextended \x1b[38;5;2m\x1b[48;5;0mmatrix", -); -assert.strictEqual( - formatting.formatText("\x04ff8800orange\x04 default"), - "\x1b[38;2;255;136;0morange\x1b[39;49m default", -); -assert.strictEqual( - formatting.formatText("\x0304,comma"), - "\x1b[38;5;9m,comma", - "a colour code without a background must leave its comma visible", -); -assert.strictEqual( - formatting.plainText("\x02bold\x02 \x0304red\x03"), - "bold red", -); -assert.strictEqual( - formatting.formatText("before\x1b[31munsafe\x1b]0;title\x07after"), - "beforeunsafeafter", - "untrusted ANSI and OSC sequences must not reach xterm", -); - -const message = formatting.formatEvent( - { - type: "message", - from: "alice", - text: "hello \x02world\x02", - }, - "12:34:56", -); -assert.ok(message.includes("[12:34:56]")); -assert.ok(message.includes("\x1b[1mworld\x1b[22m")); -assert.ok(message.includes("\x1b[1m\x1b[38;5;"), "nicknames should be bold and coloured"); -assert.ok(message.endsWith("\x1b[0m"), "every line must reset terminal styling"); - -const action = formatting.formatEvent( - { type: "action", from: "bob", text: "waves" }, - "01:02:03", -); -assert.ok(action.includes("\x1b[3m"), "actions should be italicised"); - -console.log("IRC formatting regression tests passed."); diff --git a/frontend/scripts/test-regressions.js b/frontend/scripts/test-regressions.js deleted file mode 100644 index ecedd1c..0000000 --- a/frontend/scripts/test-regressions.js +++ /dev/null @@ -1,575 +0,0 @@ -#!/usr/bin/env node -const fs = require("fs"); -const path = require("path"); - -const root = path.resolve(__dirname, ".."); -const repo = path.resolve(root, ".."); - -function read(rel) { - return fs.readFileSync(path.join(repo, rel), "utf8"); -} - -function assertContains(file, needle, message) { - const text = read(file); - if (!text.includes(needle)) { - throw new Error(`${message}\nMissing ${JSON.stringify(needle)} in ${file}`); - } -} - -function assertNotContains(file, needle, message) { - const text = read(file); - if (text.includes(needle)) { - throw new Error(`${message}\nUnexpected ${JSON.stringify(needle)} in ${file}`); - } -} - -function assertBefore(file, first, second, message) { - const text = read(file); - const firstIndex = text.indexOf(first); - const secondIndex = text.indexOf(second); - if (firstIndex === -1 || secondIndex === -1 || firstIndex >= secondIndex) { - throw new Error( - `${message}\nExpected ${JSON.stringify(first)} before ${JSON.stringify(second)} in ${file}`, - ); - } -} - -function assertTreeNotContains(relDir, needle, message) { - const dir = path.join(repo, relDir); - const offenders = []; - const walk = (current) => { - for (const entry of fs.readdirSync(current, { withFileTypes: true })) { - const full = path.join(current, entry.name); - if (entry.isDirectory()) { - walk(full); - continue; - } - if (!entry.isFile() || !entry.name.endsWith(".js")) continue; - const rel = path.relative(repo, full); - if (read(rel).includes(needle)) offenders.push(rel); - } - }; - walk(dir); - if (offenders.length) { - throw new Error(`${message}\nUnexpected ${JSON.stringify(needle)} in ${offenders.join(", ")}`); - } -} - -assertContains( - "frontend/src/js/terminal.js", - "escapeHtml(title)", - "file-drop prompt titles must escape filenames containing HTML characters", -); -assertContains( - "frontend/src/js/terminal.js", - "SFTPUploadStart", - "drag-drop upload must use chunked SFTP streaming", -); -assertContains( - "frontend/src/js/terminal.js", - "SFTPUploadChunk", - "drag-drop upload must send bounded chunks", -); -assertContains( - "frontend/src/js/terminal.js", - "scheduleFit", - "terminal resize handling must be throttled through scheduled fits", -); -assertContains( - "frontend/src/js/terminal.js", - "dimensionsChanged", - "terminal resize handling must skip unchanged container dimensions", -); -assertContains( - "frontend/src/js/terminal.js", - "cancelScheduledFit", - "terminal teardown must cancel pending fit work", -); -assertContains( - "frontend/src/js/terminal.js", - "this._sendTerminalSize(sessionId, term);", - "every completed terminal fit must publish its geometry to the backend PTY", -); -assertContains( - "frontend/src/js/terminal.js", - "[Exited - press enter to restart]", - "exited local shells must show the inline restart prompt", -); -assertContains( - "frontend/src/js/terminal.js", - "RestartLocalSession", - "pressing enter after a local shell exit must restart the existing session", -); -assertContains( - "frontend/src/js/app.js", - "!localExited &&", - "local shell exits must not show the disconnected overlay", -); -assertNotContains( - "frontend/src/js/terminal.js", - "new ResizeObserver(() => {\n try {\n fitAddon.fit();", - "ResizeObserver must not call fitAddon.fit() directly", -); -assertNotContains( - "frontend/src/js/terminal.js", - "Use SFTP Upload for files over 25 MB", - "drag-drop upload must not reject files only because they exceed the old inline limit", -); -assertContains( - "frontend/src/js/app.js", - "confirmDirectVNCProfile", - "direct VNC connections must show an explicit warning", -); -assertContains( - "frontend/src/js/profiles.js", - "skipDirectVNCWarning", - "save-and-connect should not show duplicate direct VNC prompts", -); -assertContains( - "frontend/src/js/recordings.js", - "import-status", - "profile import failures must be visible inside the import modal", -); -assertContains( - "frontend/src/js/recordings.js", - "_formatImportError", - "profile import failures must be formatted without using HTML injection", -); -assertContains( - "internal/api/api_test.go", - "TestCreateProfileRejectsUnsafeGitHubBaseURL", - "GitHub HTTP/HTTPS URL validation must stay covered", -); -assertContains( - "frontend/index.html", - "mode-leviathan", - "Leviathan mode toggle must be present beside Alchementary Link", -); -assertContains( - "frontend/index.html", - 'id="mode-leviathan"\n type="button"\n hidden', - "Leviathan mode toggle must be hidden before preferences load", -); -assertContains( - "frontend/src/js/app.js", - "prefs.leviathanEnabled === true", - "Leviathan mode must only be exposed by an explicit persisted preference", -); -assertContains( - "frontend/src/js/settings.js", - "KONAMI_SEQUENCE", - "the About window must recognise the Konami sequence", -); -assertContains( - "frontend/src/js/settings.js", - "_toggleLeviathanFromKonami", - "the About-window Konami sequence must toggle Leviathan", -); -assertContains( - "frontend/src/js/settings.js", - "SavePreferences(prefs)", - "the Konami toggle must persist the Leviathan preference immediately", -); -assertContains( - "frontend/src/css/sidebar.css", - "user-select: none", - "sidebar title and mode switcher text must not be selectable", -); -assertContains( - "frontend/src/css/sidebar.css", - "#mode-leviathan", - "Leviathan mode label must have its own right-alignment rule", -); -assertContains( - "frontend/src/css/sidebar.css", - "margin-left: auto;", - "Leviathan mode label must sit on the right side of the sidebar header", -); -assertContains( - "frontend/src/css/tokens.css", - "--tab-bar-height: 44px;", - "tab bar row must match the sidebar header height", -); -assertContains( - "frontend/src/css/sidebar.css", - "height: var(--tab-bar-height);", - "sidebar header must use the shared tab bar height", -); -assertContains( - "frontend/src/css/layout.css", - "height: var(--tab-bar-height);", - "tab bar must use the shared sidebar header height", -); -assertContains( - "frontend/src/js/tabs.js", - "this._setupScrolling();", - "tab bar must enable horizontal scroll handling for overflowed tabs", -); -assertContains( - "frontend/src/js/tabs.js", - "this._setupTooltip();", - "tab bar must show rich hover hints for truncated tabs", -); -assertContains( - "frontend/src/js/tabs.js", - "removeTab(sessionId) {\n this._hideTabTooltip();", - "removing a tab must clear any visible tab tooltip", -); -assertContains( - "frontend/src/js/tabs.js", - "async closeTab(sessionId) {\n this._hideTabTooltip();", - "closing a tab must clear any visible or pending tab tooltip", -); -assertContains( - "frontend/src/js/tabs.js", - "await this.app.closeSessionImmediate(sessionId);", - "all normal tab closes must use the complete session teardown path", -); -assertContains( - "frontend/src/js/app.js", - "this.splitPanes.cleanup(sessionId)", - "closing an owner tab must release all split-pane state and child sessions", -); -assertContains( - "frontend/src/js/app.js", - "window.AlchementaryLink.VNCViewer?.destroyViewer(sessionId)", - "session teardown must disconnect and remove VNC viewers", -); -assertContains( - "frontend/src/js/app.js", - "window.AlchementaryLink.IRCViewer?.destroyViewer(sessionId)", - "session teardown must dispose IRC terminals and resize observers", -); -assertContains( - "frontend/src/js/irc.js", - "wailsService.IRCRooms(this.sessionID)", - "IRC viewers created after connection must hydrate the already joined rooms", -); -assertContains( - "internal/sessions/manager.go", - "GetIRCLastRooms(profile.ID)", - "saved IRC profiles must restore the channels open in their last session", -); -assertContains( - "frontend/src/js/app.js", - "this.recordings.cleanup(sessionId)", - "session teardown must clear frontend recording state", -); -assertContains( - "frontend/src/js/splitpane.js", - "this.app.closeSessionImmediate(leaf.sessionId", - "closing a split child must release its backend session as well as its terminal", -); -assertContains( - "frontend/src/js/splitpane.js", - "await window.AlchementaryLink.wailsService.DisconnectSession(sessionInfo.id)", - "late split-pane connection results must be disconnected after their pane closes", -); -assertContains( - "internal/sessions/manager.go", - "delete(m.sessions, sessionID)", - "explicit backend disconnect must remove the session from the manager map", -); -assertContains( - "frontend/src/js/tabs.js", - "!tab?.isConnected", - "tab tooltip timers must not show hints for removed tab elements", -); -assertContains( - "frontend/src/js/tabs.js", - "_clearTabTooltipTimer", - "tab tooltip cleanup must cancel pending hover timers", -); -assertContains( - "frontend/src/js/tabs.js", - "session.protocol !== \"leviathan\"", - "Leviathan tabs must close without connected-session confirmation", -); -assertContains( - "frontend/src/js/tabs.js", - "session.protocol !== \"github\"", - "GitHub tabs must close without connected-session confirmation", -); -assertContains( - "frontend/src/js/tabs.js", - "Connected since:", - "connection tab hover hints must include the connected-since timestamp", -); -assertContains( - "frontend/src/js/tabs.js", - "tab.appendChild(closeBtn);\n\n // Protocol badge", - "tab close button must render on the left side of each tab", -); -assertContains( - "frontend/src/css/layout.css", - "scrollbar-gutter: stable;", - "overflowing tab lists must expose a stable horizontal scrollbar", -); -assertContains( - "frontend/src/css/tabs.css", - ".tab-tooltip", - "tab hover hints must have dedicated tooltip styling", -); -assertContains( - "frontend/src/js/leviathan.js", - "Hex Editor", - "Leviathan mode must put Hex Editor in the tool list", -); -assertContains( - "frontend/src/js/settings.js", - "Leviathan Binary Tools", - "in-app help must document the integrated Leviathan workbench", -); -assertContains( - "frontend/src/js/settings.js", - "VNC Connections", - "in-app help must document VNC connection profiles", -); -assertContains( - "docs/user-manual.md", - "## 11. Leviathan binary workbench", - "user manual must include a full Leviathan section", -); -assertContains( - "docs/user-manual.md", - "### VNC", - "user manual must include VNC connection guidance", -); -assertContains( - "docs/overview.md", - "### Leviathan binary workbench", - "product overview must include Leviathan as a first-class feature group", -); -assertContains( - "frontend/src/js/leviathan.js", - "LeviathanSetByte", - "Leviathan hex view must support byte editing", -); -assertContains( - "frontend/src/js/leviathan.js", - "_handleHexByteInput", - "Leviathan hex byte edits must update while typing", -); -assertContains( - "frontend/src/js/leviathan.js", - "_updateHexAsciiCell", - "Leviathan hex byte edits must update the ASCII preview immediately", -); -assertContains( - "frontend/src/js/leviathan.js", - "_focusHexByte(cell, 1)", - "Leviathan hex byte edits must advance to the next byte after entry", -); -assertContains( - "frontend/src/js/leviathan.js", - "spectrogram-encode", - "Leviathan must expose an image-to-audio spectrogram encoder", -); -assertContains( - "frontend/src/js/leviathan.js", - "spectrogram-view", - "Leviathan must expose an audio spectrogram visualiser", -); -assertContains( - "frontend/src/js/leviathan.js", - "preserveAspectRatio", - "Leviathan spectrogram encoding must expose aspect-ratio preservation", -); -assertContains( - "frontend/src/js/leviathan.js", - "aspectRatio", - "Leviathan spectrogram visualiser must expose display aspect-ratio control", -); -assertContains( - "frontend/src/js/leviathan.js", - "spectrogram-insert", - "Leviathan spectrogram encoder must support inserting images into carrier audio", -); -assertContains( - "frontend/src/js/app.js", - "setMode(\"leviathan\")", - "App mode switch must route to Leviathan", -); -assertBefore( - "frontend/src/js/splitpane.js", - "localLabel.textContent = 'Local Machine';", - "profiles.forEach(function(profile)", - "split-pane connection choices must put Local Machine before saved profiles", -); -assertContains( - "frontend/src/js/splitpane.js", - "wailsService.ConnectLocal()", - "the Local Machine split-pane choice must open a real local shell", -); -assertContains( - "internal/api/leviathan.go", - "LeviathanRunTool", - "Link API must expose the imported Leviathan engine", -); -assertNotContains( - "internal/api/leviathan.go", - "Pattern: \"*\"", - "Leviathan open dialogs must not use wildcard filters that grey out files on macOS", -); -assertContains( - "internal/leviathan/spectrogram/spectrogram.go", - "github.com/hajimehoshi/go-mp3", - "Leviathan spectrogram visualiser must support MP3 decoding", -); -assertNotContains( - "wails.json", - "\"obfuscated\": true", - "Wails obfuscation must stay disabled because this app embeds frontend/src and uses normal Wails globals", -); -assertNotContains( - "wails.json", - "\"garbleargs\"", - "garble arguments must not be configured when Wails obfuscation is disabled", -); -assertContains( - "frontend/index.html", - "wails-service.js", - "frontend must load the Wails service bridge before app code", -); -assertNotContains( - "frontend/index.html", - 'type="module" src="/src/js/wails-service.js"', - "Wails bridge must load as a synchronous classic script before app code", -); -assertContains( - "frontend/src/js/wails-service.js", - 'prop === "then"', - "Wails service proxy must not masquerade as a Promise thenable", -); -assertContains( - "frontend/src/js/wails-service.js", - "window.go?.api?.Service", - "Wails service bridge must use the normal named Wails API", -); -assertContains( - "frontend/src/js/sshkeys.js", - "textContent: \"Failed to load keys: \" + String(err)", - "SSH key manager errors must be rendered as text rather than injected HTML", -); -assertContains( - "frontend/src/js/sshkeys.js", - "textContent: message", - "SSH key delete confirmation messages must be rendered as text", -); -assertNotContains( - "frontend/src/js/sshkeys.js", - "Failed to load keys: ${err}", - "SSH key manager must not interpolate backend errors into innerHTML", -); -assertContains( - "frontend/package.json", - '"@xterm/xterm":', - "frontend dependencies must use the maintained scoped xterm package", -); -assertContains( - "frontend/package.json", - '"@xterm/addon-fit":', - "frontend dependencies must use the maintained scoped xterm addons", -); -assertContains( - "frontend/scripts/copy-assets.js", - "@xterm/xterm/lib/xterm.js", - "xterm browser assets must be copied from the maintained scoped package", -); -assertContains( - "frontend/scripts/copy-assets.js", - "@xterm/addon-fit/lib/addon-fit.js", - "xterm addon browser assets must be copied from the maintained scoped packages", -); -assertNotContains( - "frontend/package.json", - '"xterm":', - "deprecated unscoped xterm dependency must not be restored", -); -assertNotContains( - "frontend/package.json", - '"xterm-addon-', - "deprecated unscoped xterm addon dependencies must not be restored", -); -assertNotContains( - "frontend/package-lock.json", - '"node_modules/xterm"', - "package lock must not contain the deprecated unscoped xterm package", -); -assertNotContains( - "frontend/package-lock.json", - '"node_modules/xterm-addon-', - "package lock must not contain deprecated unscoped xterm addon packages", -); -assertContains( - "frontend/scripts/build.js", - "sourceMappingURL", - "frontend production build must remove source-map references", -); -assertContains( - "frontend/scripts/build.js", - "bundle-novnc.js", - "frontend production build must regenerate the noVNC bundle", -); -assertContains( - "frontend/scripts/novnc-entry.js", - "initLogging(\"error\")", - "bundled noVNC logging must default to errors only", -); -assertNotContains( - "frontend/src/js/app.js", - "console.debug(\"[session:debug]\"", - "release UI must not print telnet negotiation debug events", -); -assertNotContains( - "frontend/src/js/app.js", - "console.log(\"Backend is ready\")", - "release UI must not print backend-ready lifecycle events", -); -assertContains( - "build.sh", - "LINK_COMPRESS", - "local builds must expose an explicit compression switch", -); -assertContains( - "scripts/build-common.sh", - "link_use_upx_for_target", - "build scripts must share one UPX target-selection helper", -); -assertContains( - "build.sh", - "Skipping UPX compression on macOS", - "local compressed builds must avoid unsupported macOS UPX by default", -); -assertContains( - "build.sh", - "-trimpath", - "local production builds should trim file paths from binaries", -); -assertContains( - "scripts/build-common.sh", - "-buildid=", - "production builds must strip the Go build ID", -); -assertContains( - "scripts/build-common.sh", - "CGO_ENABLED=0", - "production builds must keep the pure-Go SQLite toolchain", -); -assertContains( - "Makefile", - "build-compressed", - "Makefile must expose an explicit compressed build target", -); -assertContains( - "Makefile", - "LINK_FORCE_MACOS_UPX", - "Makefile compressed builds must make macOS UPX opt-in", -); -assertTreeNotContains( - "frontend/src/js", - "window.go.api.Service", - "app code must not call named Wails backend methods directly", -); - -console.log("frontend regression checks passed"); diff --git a/frontend/scripts/test-session-lifecycle.js b/frontend/scripts/test-session-lifecycle.js deleted file mode 100644 index adc3c9f..0000000 --- a/frontend/scripts/test-session-lifecycle.js +++ /dev/null @@ -1,305 +0,0 @@ -#!/usr/bin/env node -const assert = require("assert"); -const fs = require("fs"); -const path = require("path"); -const vm = require("vm"); - -const frontendRoot = path.resolve(__dirname, ".."); - -function loadScript(relativePath, additions = {}) { - const context = { - console, - Promise, - Map, - Set, - setTimeout, - clearTimeout, - window: { AlchementaryLink: {} }, - document: { - querySelector: () => null, - addEventListener: () => {}, - }, - ...additions, - }; - vm.createContext(context); - vm.runInContext( - fs.readFileSync(path.join(frontendRoot, relativePath), "utf8"), - context, - { filename: relativePath }, - ); - return context; -} - -async function testRepeatedSplitCleanup() { - const context = loadScript("src/js/splitpane.js"); - const SplitPaneManager = context.window.AlchementaryLink.SplitPaneManager; - const closed = []; - const app = { - closeSessionImmediate: async (sessionId, options) => { - closed.push({ sessionId, options }); - }, - }; - const manager = new SplitPaneManager(app); - let cancelledDrags = 0; - - for (let i = 0; i < 500; i++) { - const ownerId = `owner-${i}`; - const childId = `child-${i}`; - const ownerEl = { _splitNode: {} }; - const childEl = { _splitNode: {} }; - const ownerLeaf = { - type: "leaf", - paneId: `owner-pane-${i}`, - el: ownerEl, - sessionId: ownerId, - parent: null, - }; - const childLeaf = { - type: "leaf", - paneId: `child-pane-${i}`, - el: childEl, - sessionId: childId, - parent: null, - }; - const branchEl = { _splitNode: {} }; - const branch = { - type: "branch", - first: ownerLeaf, - second: childLeaf, - el: branchEl, - parent: null, - cancelDrag: () => { - cancelledDrags++; - }, - }; - ownerLeaf.parent = branch; - childLeaf.parent = branch; - manager.trees.set(ownerId, branch); - manager.pendingConnections.set(`pending-${i}`, { - ownerSessionId: ownerId, - cancelled: false, - completed: false, - }); - - await manager.cleanup(ownerId); - - assert.strictEqual(ownerEl._splitNode, null); - assert.strictEqual(childEl._splitNode, null); - assert.strictEqual(branchEl._splitNode, null); - } - - assert.strictEqual(manager.trees.size, 0); - assert.strictEqual(manager.pendingConnections.size, 0); - assert.strictEqual(closed.length, 500); - assert.strictEqual(cancelledDrags, 500); - for (const call of closed) { - assert.strictEqual(call.options.removeTab, false); - assert.strictEqual(call.options.cleanupSplits, false); - } -} - -function testClosedPaneRejectsLateConnection() { - const context = loadScript("src/js/splitpane.js"); - const SplitPaneManager = context.window.AlchementaryLink.SplitPaneManager; - const manager = new SplitPaneManager({ closeSessionImmediate: async () => {} }); - const pane = { - dataset: { paneId: "pane-1" }, - isConnected: true, - }; - const leaf = { - type: "leaf", - paneId: "pane-1", - el: pane, - sessionId: null, - parent: null, - }; - pane._splitNode = leaf; - manager.trees.set("owner-1", leaf); - - const token = manager._beginPendingConnection(pane, "owner-1"); - manager._cancelPendingConnection("pane-1"); - - assert.strictEqual( - manager._completePendingConnection(pane, "owner-1", token), - false, - ); - assert.strictEqual(manager.pendingConnections.size, 0); -} - -function testPerSessionStoresReleaseIDs() { - const stateContext = loadScript("src/js/state.js"); - const State = stateContext.window.AlchementaryLink.State; - const state = new State(); - state.addSession({ id: "session-1" }); - state.broadcastTargets = new Set(["session-1"]); - state.removeSession("session-1"); - assert.strictEqual(state.sessions.size, 0); - assert.strictEqual(state.broadcastTargets.size, 0); - - const recordingContext = loadScript("src/js/recordings.js"); - const RecordingManager = - recordingContext.window.AlchementaryLink.RecordingManager; - const recordings = new RecordingManager({}); - recordings.activeSessions.add("session-1"); - recordings.cleanup("session-1"); - assert.strictEqual(recordings.activeSessions.size, 0); -} - -async function testCompleteAppTeardownIsImmediateAndIdempotent() { - let finishBackendClose; - let backendCloses = 0; - const context = loadScript("src/js/app.js"); - const App = context.window.AlchementaryLink.App; - const app = Object.create(App.prototype); - const resources = { - sftp: new Set(["session-1"]), - triggers: new Set(["session-1"]), - recordings: new Set(["session-1"]), - terminals: new Set(["session-1"]), - raw: new Set(["session-1"]), - github: new Set(["session-1"]), - vnc: new Set(["session-1"]), - irc: new Set(["session-1"]), - leviathan: new Set(["session-1"]), - }; - const drop = (name) => ({ - cleanup: (id) => resources[name].delete(id), - close: (id) => resources[name].delete(id), - destroyTerminal: (id) => resources[name].delete(id), - destroyViewer: (id) => resources[name].delete(id), - destroyPanel: (id) => resources[name].delete(id), - }); - - app._closingSessions = new Map(); - app.splitPanes = { cleanup: async () => {} }; - app.sftp = drop("sftp"); - app.triggers = drop("triggers"); - app.recordings = drop("recordings"); - app.terminals = drop("terminals"); - app.rawViewer = drop("raw"); - app.leviathan = drop("leviathan"); - app.tabs = { - order: ["session-1"], - removeTab: (id) => { - app.tabs.order = app.tabs.order.filter((value) => value !== id); - }, - }; - app.state = { - sessions: new Map([["session-1", { id: "session-1" }]]), - getSession(id) { - return this.sessions.get(id); - }, - removeSession(id) { - this.sessions.delete(id); - }, - }; - app.updateStatusBar = () => {}; - - context.window.AlchementaryLink.GitHubViewer = drop("github"); - context.window.AlchementaryLink.VNCViewer = drop("vnc"); - context.window.AlchementaryLink.IRCViewer = drop("irc"); - context.window.AlchementaryLink.wailsService = { - DisconnectSession: () => { - backendCloses++; - return new Promise((resolve) => { - finishBackendClose = resolve; - }); - }, - }; - - const first = app.closeSessionImmediate("session-1"); - const second = app.closeSessionImmediate("session-1"); - assert.strictEqual(first, second); - assert.strictEqual(backendCloses, 1); - assert.strictEqual(app.tabs.order.length, 0); - assert.strictEqual(app.state.sessions.size, 0); - for (const retained of Object.values(resources)) { - assert.strictEqual(retained.size, 0); - } - - finishBackendClose(); - await first; - assert.strictEqual(app._closingSessions.size, 0); -} - -async function testLatencyPingIsSingleFlightAndIgnoresStaleResults() { - const flushAsync = () => new Promise((resolve) => setImmediate(resolve)); - let nextTimerID = 1; - const timers = new Map(); - const latencyEl = { textContent: "", className: "" }; - const context = loadScript("src/js/app.js", { - setTimeout: (callback) => { - const id = nextTimerID++; - timers.set(id, callback); - return id; - }, - clearTimeout: (id) => timers.delete(id), - document: { - querySelector: () => null, - getElementById: (id) => (id === "status-latency" ? latencyEl : null), - addEventListener: () => {}, - }, - }); - const App = context.window.AlchementaryLink.App; - const app = Object.create(App.prototype); - app._latencySessionID = null; - app._latencyTimer = null; - app._latencyGeneration = 0; - app._latencyPingInFlight = false; - - const pending = []; - context.window.AlchementaryLink.wailsService = { - PingSession: (sessionID) => - new Promise((resolve, reject) => { - pending.push({ sessionID, resolve, reject }); - }), - }; - - app._startLatencyPing("session-a"); - app._startLatencyPing("session-a"); - assert.strictEqual(pending.length, 1, "an in-flight ping must not overlap"); - assert.strictEqual(timers.size, 0, "the next ping waits for completion"); - - app._startLatencyPing("session-b"); - assert.strictEqual(pending.length, 2); - pending[0].reject(new Error("stale session closed")); - await flushAsync(); - assert.strictEqual(app._latencySessionID, "session-b"); - assert.strictEqual( - app._latencyPingInFlight, - true, - "a stale rejection must not stop the current session ping", - ); - - pending[1].resolve(42); - await flushAsync(); - assert.strictEqual(latencyEl.textContent, "42 ms"); - assert.strictEqual(latencyEl.className, "latency-good"); - assert.strictEqual(timers.size, 1, "one follow-up ping should be scheduled"); - - const followUp = timers.values().next().value; - timers.clear(); - followUp(); - assert.strictEqual(pending.length, 3); - assert.strictEqual(pending[2].sessionID, "session-b"); - assert.strictEqual(timers.size, 0, "follow-up pings must also be single-flight"); - - pending[2].resolve(250); - await flushAsync(); - assert.strictEqual(latencyEl.className, "latency-bad"); - assert.strictEqual(timers.size, 1); - app._stopLatencyPing(); - assert.strictEqual(timers.size, 0); -} - -(async () => { - await testRepeatedSplitCleanup(); - testClosedPaneRejectsLateConnection(); - testPerSessionStoresReleaseIDs(); - await testCompleteAppTeardownIsImmediateAndIdempotent(); - await testLatencyPingIsSingleFlightAndIgnoresStaleResults(); - console.log("Session lifecycle regression tests passed."); -})().catch((err) => { - console.error(err); - process.exitCode = 1; -}); diff --git a/frontend/src/css/base.css b/frontend/src/css/base.css deleted file mode 100644 index ac13eae..0000000 --- a/frontend/src/css/base.css +++ /dev/null @@ -1,187 +0,0 @@ -/* ========================================================================== - Alchementary Link — Base / Reset - Modern CSS reset and foundational element styles. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Box-model reset - ----------------------------------------------------------------------- */ -*, -*::before, -*::after { - box-sizing: border-box; - margin: 0; - padding: 0; -} - -/* ----------------------------------------------------------------------- - Root element - ----------------------------------------------------------------------- */ -html, -body { - height: 100%; - overflow: hidden; - font-family: var(--font-sans); - font-size: var(--text-base); - line-height: var(--leading-normal); - color: var(--text-primary); - background: var(--bg-base); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - user-select: none; -} - -/* ----------------------------------------------------------------------- - Scrollbar styling (Webkit / Blink) - ----------------------------------------------------------------------- */ -::-webkit-scrollbar { - width: 6px; - height: 6px; -} - -::-webkit-scrollbar-track { - background: transparent; -} - -::-webkit-scrollbar-thumb { - background: var(--border-strong); - border-radius: var(--radius-full); -} - -::-webkit-scrollbar-thumb:hover { - background: var(--text-tertiary); -} - -/* Firefox scrollbar */ -* { - scrollbar-width: thin; - scrollbar-color: var(--border-strong) transparent; -} - -/* ----------------------------------------------------------------------- - Text selection - ----------------------------------------------------------------------- */ -::selection { - background: var(--accent-strong); - color: var(--text-primary); -} - -/* ----------------------------------------------------------------------- - Focus management - ----------------------------------------------------------------------- */ -:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -:focus:not(:focus-visible) { - outline: none; -} - -/* ----------------------------------------------------------------------- - Links - ----------------------------------------------------------------------- */ -a { - color: var(--accent); - text-decoration: none; - transition: color var(--transition-fast); -} - -a:hover { - color: var(--accent-hover); -} - -/* ----------------------------------------------------------------------- - Images - ----------------------------------------------------------------------- */ -img, -svg { - display: block; - max-width: 100%; -} - -/* ----------------------------------------------------------------------- - Lists - ----------------------------------------------------------------------- */ -ul, -ol { - list-style: none; -} - -/* ----------------------------------------------------------------------- - Tables - ----------------------------------------------------------------------- */ -table { - border-collapse: collapse; - border-spacing: 0; -} - -/* ----------------------------------------------------------------------- - Buttons & inputs — inherit font - ----------------------------------------------------------------------- */ -button, -input, -select, -textarea { - font: inherit; - color: inherit; - background: none; - border: none; -} - -button { - cursor: pointer; -} - -/* ----------------------------------------------------------------------- - Monospace elements - ----------------------------------------------------------------------- */ -code, -pre, -kbd, -.mono { - font-family: var(--font-mono); -} - -/* ----------------------------------------------------------------------- - Hidden utility - ----------------------------------------------------------------------- */ -[hidden], -.hidden { - display: none !important; -} - -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -/* ----------------------------------------------------------------------- - Text utilities - ----------------------------------------------------------------------- */ -.truncate { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.text-secondary { color: var(--text-secondary); } -.text-tertiary { color: var(--text-tertiary); } -.text-accent { color: var(--accent); } -.text-success { color: var(--success); } -.text-warning { color: var(--warning); } -.text-error { color: var(--error); } - -/* ----------------------------------------------------------------------- - Wails window drag region - ----------------------------------------------------------------------- */ -.wails-drag { - --wails-draggable: drag; -} diff --git a/frontend/src/css/components.css b/frontend/src/css/components.css deleted file mode 100644 index 8615ac2..0000000 --- a/frontend/src/css/components.css +++ /dev/null @@ -1,1230 +0,0 @@ -/* ========================================================================== - Alchementary Link — Reusable Components - Buttons, form controls, badges, dropdowns, modals, notifications, etc. - ========================================================================== */ - -/* ======================================================================= - BUTTONS - ======================================================================= */ - -.btn { - display: inline-flex; - align-items: center; - justify-content: center; - gap: var(--space-2); - height: 30px; - padding: 0 var(--space-3); - font-size: var(--text-sm); - font-weight: var(--weight-medium); - line-height: 1; - border-radius: var(--radius-md); - border: 1px solid transparent; - cursor: pointer; - white-space: nowrap; - transition: - background var(--transition-fast), - border-color var(--transition-fast), - color var(--transition-fast), - box-shadow var(--transition-fast); - user-select: none; -} - -.btn:disabled { - opacity: 0.45; - cursor: not-allowed; - pointer-events: none; -} - -/* Primary — accent filled */ -.btn-primary { - background: var(--accent); - color: var(--text-inverse); - border-color: var(--accent); -} -.btn-primary:hover { - background: var(--accent-hover); - border-color: var(--accent-hover); -} -.btn-primary:active { - background: #3da69f; - border-color: #3da69f; -} - -/* Accent — success-tinted action, e.g. "Save & Connect" */ -.btn-accent { - background: var(--success); - color: var(--text-inverse); - border-color: var(--success); -} -.btn-accent:hover { - background: #57a85c; - border-color: #57a85c; -} -.btn-accent:active { - background: #4a9a4e; - border-color: #4a9a4e; -} - -/* Secondary — subtle border */ -.btn-secondary { - background: transparent; - color: var(--text-secondary); - border-color: var(--border-default); -} -.btn-secondary:hover { - background: var(--bg-hover); - color: var(--text-primary); - border-color: var(--border-strong); -} -.btn-secondary:active { - background: var(--bg-active); -} - -/* Ghost — transparent, hover only */ -.btn-ghost { - background: transparent; - color: var(--text-secondary); -} -.btn-ghost:hover { - background: var(--bg-hover); - color: var(--text-primary); -} -.btn-ghost:active { - background: var(--bg-active); -} - -/* Danger */ -.btn-danger { - background: transparent; - color: var(--error); - border-color: var(--error); -} -.btn-danger:hover { - background: rgba(239, 83, 80, 0.12); -} -.btn-danger:active { - background: rgba(239, 83, 80, 0.2); -} - -.btn-danger-filled { - background: var(--error); - color: #fff; - border-color: var(--error); -} -.btn-danger-filled:hover { - background: #e53935; - border-color: #e53935; -} - -/* Warning */ -.btn-warning { - background: var(--warning); - color: #1a1a1e; - border-color: var(--warning); -} -.btn-warning:hover { - filter: brightness(1.05); -} -.btn-warning:active { - filter: brightness(0.95); -} - -/* Size variants */ -.btn-sm { - height: 26px; - padding: 0 var(--space-2); - font-size: var(--text-xs); -} - -.btn-xs { - height: 22px; - padding: 0 var(--space-2); - font-size: var(--text-xs); - border-radius: var(--radius-sm); -} - -/* Icon-only button (square) */ -.btn-icon { - width: 30px; - padding: 0; - flex-shrink: 0; -} -.btn-icon.btn-sm { - width: 26px; -} -.btn-icon.btn-xs { - width: 22px; -} - -/* Button group */ -.btn-group { - display: inline-flex; -} - -/* Outline button — transparent background, subtle border */ -.btn-outline { - background: transparent; - border: 1px solid var(--border); - color: var(--text-secondary); -} -.btn-outline:hover { - background: var(--surface-2); - color: var(--text-primary); -} - -.btn-group .btn { - border-radius: 0; -} -.btn-group .btn:first-child { - border-radius: var(--radius-md) 0 0 var(--radius-md); -} -.btn-group .btn:last-child { - border-radius: 0 var(--radius-md) var(--radius-md) 0; -} -.btn-group .btn + .btn { - margin-left: -1px; -} - -/* ======================================================================= - FORM CONTROLS - ======================================================================= */ - -.form-group { - display: flex; - flex-direction: column; - gap: var(--space-1); -} - -.form-label { - font-size: var(--text-sm); - font-weight: var(--weight-medium); - color: var(--text-secondary); - user-select: none; -} - -.form-label-required::after { - content: " *"; - color: var(--error); -} - -/* Text input, select, textarea share a common treatment */ -.form-input, -.form-select, -.form-textarea { - height: 30px; - padding: 0 var(--space-3); - font-size: var(--text-sm); - color: var(--text-primary); - background: var(--bg-input); - border: 1px solid var(--border-default); - border-radius: var(--radius-md); - transition: - border-color var(--transition-fast), - box-shadow var(--transition-fast); -} - -.form-input::placeholder, -.form-textarea::placeholder { - color: var(--text-disabled); -} - -.form-input:hover, -.form-select:hover, -.form-textarea:hover { - border-color: var(--border-strong); -} - -.form-input:focus, -.form-select:focus, -.form-textarea:focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px var(--accent-muted); - outline: none; -} - -.form-input:disabled, -.form-select:disabled, -.form-textarea:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* Select arrow */ -.form-select { - appearance: none; - padding-right: var(--space-8); - background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%236e6e88'/%3E%3C/svg%3E"); - background-repeat: no-repeat; - background-position: right 10px center; - cursor: pointer; -} - -/* Textarea */ -.form-textarea { - height: auto; - min-height: 64px; - padding: var(--space-2) var(--space-3); - resize: vertical; - line-height: var(--leading-normal); -} - -/* Help text */ -.form-help { - font-size: var(--text-xs); - color: var(--text-tertiary); - line-height: var(--leading-normal); -} - -/* Error message */ -.form-error { - font-size: var(--text-xs); - color: var(--error); - line-height: var(--leading-normal); -} - -/* Error state on inputs */ -.form-input.is-error, -.form-select.is-error, -.form-textarea.is-error { - border-color: var(--error); -} -.form-input.is-error:focus, -.form-select.is-error:focus, -.form-textarea.is-error:focus { - box-shadow: 0 0 0 2px rgba(239, 83, 80, 0.2); -} - -/* Inline layout (label beside control) */ -.form-inline { - flex-direction: row; - align-items: center; - gap: var(--space-3); -} -.form-inline .form-label { - min-width: 100px; - flex-shrink: 0; -} - -/* Checkbox */ -.form-checkbox { - display: inline-flex; - align-items: center; - gap: var(--space-2); - cursor: pointer; - font-size: var(--text-sm); - color: var(--text-secondary); - user-select: none; -} - -.form-checkbox input[type="checkbox"] { - appearance: none; - width: 16px; - height: 16px; - border: 1px solid var(--border-strong); - border-radius: var(--radius-sm); - background: var(--bg-input); - flex-shrink: 0; - cursor: pointer; - transition: - background var(--transition-fast), - border-color var(--transition-fast); - position: relative; -} - -.form-checkbox input[type="checkbox"]:checked { - background: var(--accent); - border-color: var(--accent); -} - -.form-checkbox input[type="checkbox"]:checked::after { - content: ""; - position: absolute; - top: 2px; - left: 5px; - width: 4px; - height: 8px; - border: solid var(--text-inverse); - border-width: 0 2px 2px 0; - transform: rotate(45deg); -} - -.form-checkbox input[type="checkbox"]:focus-visible { - outline: 2px solid var(--accent); - outline-offset: 2px; -} - -/* Toggle switch */ -.form-toggle { - display: inline-flex; - align-items: center; - gap: var(--space-2); - cursor: pointer; - font-size: var(--text-sm); - color: var(--text-secondary); - user-select: none; -} - -.form-toggle input[type="checkbox"] { - appearance: none; - width: 32px; - height: 18px; - border-radius: var(--radius-full); - background: var(--border-strong); - position: relative; - cursor: pointer; - flex-shrink: 0; - transition: background var(--transition-normal); -} - -.form-toggle input[type="checkbox"]::after { - content: ""; - position: absolute; - top: 2px; - left: 2px; - width: 14px; - height: 14px; - border-radius: 50%; - background: var(--text-secondary); - transition: - transform var(--transition-normal), - background var(--transition-normal); -} - -.form-toggle input[type="checkbox"]:checked { - background: var(--accent); -} - -.form-toggle input[type="checkbox"]:checked::after { - transform: translateX(14px); - background: var(--text-inverse); -} - -/* ======================================================================= - BADGES - ======================================================================= */ - -.badge { - display: inline-flex; - align-items: center; - height: 18px; - padding: 0 var(--space-2); - font-size: 10px; - font-weight: var(--weight-semibold); - line-height: 1; - border-radius: var(--radius-sm); - white-space: nowrap; - text-transform: uppercase; - letter-spacing: 0.04em; - background: var(--bg-overlay); - color: var(--text-secondary); -} - -/* Protocol badges */ -.badge-ssh { - background: rgba(78, 205, 196, 0.15); - color: var(--protocol-ssh); -} -.badge-telnet { - background: rgba(255, 167, 38, 0.15); - color: var(--protocol-telnet); -} -.badge-raw { - background: rgba(171, 71, 188, 0.15); - color: var(--protocol-raw); -} -.badge-github { - background: rgba(124, 138, 255, 0.15); - color: var(--protocol-github); -} - -/* Status badges */ -.badge-status { - gap: var(--space-1); -} -.badge-status::before { - content: ""; - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--text-tertiary); - flex-shrink: 0; -} -.badge-status.is-connected::before { - background: var(--success); -} -.badge-status.is-connecting::before { - background: var(--warning); - animation: pulse-dot 1.2s ease infinite; -} -.badge-status.is-error::before { - background: var(--error); -} -.badge-status.is-disconnected::before { - background: var(--text-tertiary); -} - -@keyframes pulse-dot { - 0%, - 100% { - opacity: 1; - } - 50% { - opacity: 0.4; - } -} - -/* ======================================================================= - CARDS & SURFACES - ======================================================================= */ - -.surface { - background: var(--bg-elevated); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-sm); -} - -.card { - background: var(--bg-elevated); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-lg); - padding: var(--space-4); - cursor: pointer; - transition: - background var(--transition-fast), - border-color var(--transition-fast), - box-shadow var(--transition-fast); -} -.card:hover { - background: var(--bg-hover); - border-color: var(--border-default); - box-shadow: var(--shadow-md); -} -.card:active { - background: var(--bg-active); -} - -/* ======================================================================= - DROPDOWN / CONTEXT MENU - ======================================================================= */ - -.dropdown { - position: relative; - display: inline-flex; -} - -.dropdown-menu { - position: absolute; - top: 100%; - left: 0; - z-index: var(--z-dropdown); - min-width: 160px; - padding: var(--space-1) 0; - background: var(--bg-overlay); - border: 1px solid var(--border-default); - border-radius: var(--radius-md); - box-shadow: var(--shadow-lg); - opacity: 0; - visibility: hidden; - transform: translateY(-4px); - transition: - opacity var(--transition-fast), - visibility var(--transition-fast), - transform var(--transition-fast); -} - -.dropdown.is-open .dropdown-menu, -.dropdown-menu.is-visible { - opacity: 1; - visibility: visible; - transform: translateY(2px); -} - -/* Right-aligned variant */ -.dropdown-menu.is-right { - left: auto; - right: 0; -} - -.dropdown-item { - display: flex; - align-items: center; - gap: var(--space-2); - width: 100%; - padding: var(--space-1) var(--space-3); - font-size: var(--text-sm); - color: var(--text-secondary); - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); - border: none; - background: none; - text-align: left; -} - -.dropdown-item:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.dropdown-item.is-danger { - color: var(--error); -} -.dropdown-item.is-danger:hover { - background: rgba(239, 83, 80, 0.1); -} - -.dropdown-item.is-disabled { - opacity: 0.4; - pointer-events: none; -} - -.dropdown-item-icon { - width: 16px; - text-align: center; - flex-shrink: 0; - font-size: var(--text-md); -} - -.dropdown-item-shortcut { - margin-left: auto; - font-size: var(--text-xs); - color: var(--text-tertiary); -} - -.dropdown-separator { - height: 1px; - margin: var(--space-1) 0; - background: var(--border-subtle); -} - -.dropdown-header { - padding: var(--space-1) var(--space-3); - font-size: var(--text-xs); - font-weight: var(--weight-semibold); - color: var(--text-tertiary); - text-transform: uppercase; - letter-spacing: 0.05em; -} - -/* ======================================================================= - NOTIFICATIONS (toast) - ======================================================================= */ - -.notifications-container { - position: fixed; - top: var(--space-4); - right: var(--space-4); - z-index: var(--z-notification); - display: flex; - flex-direction: column; - gap: var(--space-2); - pointer-events: none; -} - -.notification { - display: flex; - align-items: flex-start; - gap: var(--space-3); - min-width: 280px; - max-width: 400px; - padding: var(--space-3) var(--space-4); - background: var(--bg-overlay); - border: 1px solid var(--border-default); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-xl); - pointer-events: auto; - animation: notification-in var(--transition-slow) ease forwards; -} - -.notification.is-leaving { - animation: notification-out var(--transition-normal) ease forwards; -} - -@keyframes notification-in { - from { - opacity: 0; - transform: translateX(20px); - } - to { - opacity: 1; - transform: translateX(0); - } -} -@keyframes notification-out { - from { - opacity: 1; - transform: translateX(0); - } - to { - opacity: 0; - transform: translateX(20px); - } -} - -.notification-icon { - flex-shrink: 0; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - font-size: 11px; - font-weight: var(--weight-bold); - line-height: 1; - color: var(--text-inverse); - background: var(--text-tertiary); -} - -.notification-success .notification-icon { - background: var(--success); -} -.notification-error .notification-icon { - background: var(--error); -} -.notification-warning .notification-icon { - background: var(--warning); -} -.notification-info .notification-icon { - background: var(--info); -} - -.notification-body { - flex: 1; - min-width: 0; -} - -.notification-title { - font-size: var(--text-sm); - font-weight: var(--weight-semibold); - color: var(--text-primary); - margin-bottom: 2px; -} - -.notification-message { - font-size: var(--text-xs); - color: var(--text-secondary); - line-height: var(--leading-normal); -} - -.notification-close { - flex-shrink: 0; - width: 20px; - height: 20px; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-sm); - color: var(--text-tertiary); - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); -} -.notification-close:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -/* Colour variants (left border accent) */ -.notification-success { - border-left: 3px solid var(--success); -} -.notification-error { - border-left: 3px solid var(--error); -} -.notification-warning { - border-left: 3px solid var(--warning); -} -.notification-info { - border-left: 3px solid var(--info); -} - -/* ======================================================================= - DISCLOSURE / ACCORDION - ======================================================================= */ - -.disclosure { - border-bottom: 1px solid var(--border-subtle); -} - -.disclosure-header { - display: flex; - align-items: center; - gap: var(--space-2); - width: 100%; - padding: var(--space-3) var(--space-4); - font-size: var(--text-sm); - font-weight: var(--weight-medium); - color: var(--text-secondary); - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); - user-select: none; - text-align: left; - background: none; - border: none; -} - -.disclosure-header:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.disclosure-chevron { - width: 16px; - height: 16px; - flex-shrink: 0; - transition: transform var(--transition-normal); - color: var(--text-tertiary); -} - -.disclosure.is-open .disclosure-chevron { - transform: rotate(90deg); -} - -.disclosure-content { - overflow: hidden; - max-height: 0; - transition: max-height var(--transition-slow); -} - -.disclosure.is-open .disclosure-content { - max-height: 2000px; -} - -.disclosure-content-inner { - padding: 0 var(--space-4) var(--space-4); -} - -/* ======================================================================= - SEARCH INPUT - ======================================================================= */ - -.search-input { - width: 100%; - height: 30px; - padding: 0 var(--space-3) 0 var(--space-8); - font-size: var(--text-sm); - color: var(--text-primary); - background: var(--bg-input); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - transition: - border-color var(--transition-fast), - box-shadow var(--transition-fast); -} - -.search-input::placeholder { - color: var(--text-disabled); -} - -.search-input:hover { - border-color: var(--border-default); -} - -.search-input:focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px var(--accent-muted); - outline: none; -} - -/* Wrapper with icon */ -.search-wrapper { - position: relative; -} - -.search-wrapper::before { - content: "⌕"; - position: absolute; - left: var(--space-3); - top: 50%; - transform: translateY(-50%); - font-size: var(--text-sm); - color: var(--text-tertiary); - pointer-events: none; -} - -/* ======================================================================= - EMPTY STATE - ======================================================================= */ - -.empty-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - flex: 1; - padding: var(--space-8); - text-align: center; -} - -.empty-state-icon { - font-size: 48px; - color: var(--text-tertiary); - margin-bottom: var(--space-4); - opacity: 0.5; -} - -.empty-state h2 { - font-size: var(--text-lg); - font-weight: var(--weight-semibold); - color: var(--text-secondary); - margin-bottom: var(--space-2); -} - -.empty-state p { - font-size: var(--text-sm); - color: var(--text-tertiary); - max-width: 320px; - line-height: var(--leading-relaxed); - margin-bottom: var(--space-6); -} - -.empty-state-actions { - display: flex; - gap: var(--space-3); -} - -/* ======================================================================= - TOOLTIP - ======================================================================= */ - -.tooltip { - position: absolute; - z-index: var(--z-tooltip); - padding: var(--space-1) var(--space-2); - font-size: var(--text-xs); - font-weight: var(--weight-medium); - color: var(--text-primary); - background: var(--bg-overlay); - border: 1px solid var(--border-default); - border-radius: var(--radius-sm); - box-shadow: var(--shadow-md); - white-space: nowrap; - pointer-events: none; - opacity: 0; - transition: opacity var(--transition-fast); -} - -.tooltip.is-visible { - opacity: 1; -} - -/* ======================================================================= - DIVIDER - ======================================================================= */ - -.divider { - height: 1px; - background: var(--border-subtle); - margin: var(--space-2) 0; -} - -.divider-vertical { - width: 1px; - align-self: stretch; - background: var(--border-subtle); - margin: 0 var(--space-2); -} - -/* ======================================================================= - LOADING / SPINNER - ======================================================================= */ - -.spinner { - width: 16px; - height: 16px; - border: 2px solid var(--border-strong); - border-top-color: var(--accent); - border-radius: 50%; - animation: spin 0.6s linear infinite; -} - -.spinner-sm { - width: 12px; - height: 12px; - border-width: 1.5px; -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -/* ======================================================================= - CONTEXT MENUS - ======================================================================= */ - -.context-menu { - position: fixed; - z-index: var(--z-modal, 1000); - min-width: 180px; - max-width: 260px; - padding: var(--space-1) 0; - background: var(--bg-elevated); - border: 1px solid var(--border-default); - border-radius: var(--radius-md); - box-shadow: - 0 4px 16px rgba(0, 0, 0, 0.35), - 0 1px 4px rgba(0, 0, 0, 0.2); - overflow: hidden; -} - -.context-menu-item { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - padding: 6px var(--space-3); - border: none; - background: none; - color: var(--text-primary); - font-size: var(--text-sm); - text-align: left; - cursor: pointer; - transition: background var(--transition-fast); - gap: var(--space-3); -} - -.context-menu-item:hover { - background: var(--bg-hover); -} - -.context-menu-item:active { - background: var(--bg-active); -} - -.context-menu-item.context-menu-disabled { - color: var(--text-disabled); - cursor: not-allowed; -} - -.context-menu-item.context-menu-disabled:hover { - background: none; -} - -.context-menu-item.context-menu-danger { - color: var(--error); -} - -.context-menu-item.context-menu-danger:hover { - background: rgba(239, 83, 80, 0.1); -} - -.context-menu-label { - flex: 1; - min-width: 0; -} - -.context-menu-shortcut { - font-size: 11px; - color: var(--text-tertiary); - flex-shrink: 0; - margin-left: var(--space-3); -} - -.context-menu-divider { - height: 1px; - margin: var(--space-1) 0; - background: var(--border-subtle); -} - -/* ======================================================================= - SSH KEY MANAGER - ======================================================================= */ - -.sshkey-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: var(--space-3); -} - -.sshkey-list { - display: flex; - flex-direction: column; - gap: var(--space-2); - max-height: 400px; - overflow-y: auto; -} - -.sshkey-row { - display: flex; - align-items: center; - justify-content: space-between; - padding: var(--space-2) var(--space-3); - background: var(--bg-base); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - gap: var(--space-3); -} - -.sshkey-row:hover { - border-color: var(--border-strong); -} - -.sshkey-info { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; -} - -.sshkey-name { - font-weight: var(--weight-medium); - font-size: var(--text-sm); - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.sshkey-meta { - font-size: var(--text-xs); - color: var(--text-tertiary); -} - -.sshkey-actions { - display: flex; - gap: var(--space-2); - flex-shrink: 0; -} - -.sshkey-loading, -.sshkey-empty, -.sshkey-error { - padding: var(--space-4); - text-align: center; - color: var(--text-tertiary); - font-size: var(--text-sm); -} - -.sshkey-error { - color: var(--error); -} - -.sshkey-generate-form { - display: flex; - flex-direction: column; - gap: var(--space-3); - padding: var(--space-3); - background: var(--bg-base); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - margin-bottom: var(--space-3); -} - -.sshkey-form-actions { - display: flex; - justify-content: flex-end; - gap: var(--space-2); -} - -/* ======================================================================= - TRIGGER RULES - ======================================================================= */ - -.trigger-toolbar { - display: flex; - justify-content: flex-end; - margin-bottom: var(--space-3); -} - -.trigger-list { - display: flex; - flex-direction: column; - gap: var(--space-2); - max-height: 400px; - overflow-y: auto; -} - -.trigger-empty { - padding: var(--space-4); - text-align: center; - color: var(--text-tertiary); - font-size: var(--text-sm); -} - -.trigger-row { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-2) var(--space-3); - background: var(--bg-base); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); -} - -.trigger-row:hover { - border-color: var(--border-strong); -} - -.trigger-toggle { - flex-shrink: 0; -} - -.trigger-info { - display: flex; - flex-direction: column; - gap: 2px; - flex: 1; - min-width: 0; -} - -.trigger-name { - font-weight: var(--weight-medium); - font-size: var(--text-sm); - color: var(--text-primary); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.trigger-meta { - font-size: var(--text-xs); - color: var(--text-tertiary); -} - -.trigger-pattern { - font-size: var(--text-xs); - color: var(--accent); - background: var(--bg-overlay); - padding: 2px 6px; - border-radius: var(--radius-sm); - max-width: 150px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.trigger-actions { - display: flex; - gap: var(--space-2); - flex-shrink: 0; -} - -.trigger-form { - display: flex; - flex-direction: column; - gap: var(--space-3); - padding: var(--space-3); - background: var(--bg-base); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - margin-bottom: var(--space-3); -} - -.trigger-form .form-row { - display: flex; - gap: var(--space-3); -} - -.trigger-form-actions { - display: flex; - justify-content: flex-end; - gap: var(--space-2); -} diff --git a/frontend/src/css/forms.css b/frontend/src/css/forms.css deleted file mode 100644 index a28084b..0000000 --- a/frontend/src/css/forms.css +++ /dev/null @@ -1,745 +0,0 @@ -/* ========================================================================== - Alchementary Link — Profile Editor & Form Polish - Section headers, grouped fields, protocol-specific visibility, - inline validation, tag input, jump host list, key file selector. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Form panel (profile editor container) - ----------------------------------------------------------------------- */ -.form-panel { - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; -} - -.form-panel-header { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-3) var(--space-4); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.form-panel-title { - font-size: var(--text-md); - font-weight: var(--weight-semibold); - color: var(--text-primary); - flex: 1; -} - -.form-panel-body { - flex: 1; - overflow-y: auto; - padding: var(--space-4); -} - -.form-panel-footer { - display: flex; - align-items: center; - justify-content: flex-end; - gap: var(--space-2); - padding: var(--space-3) var(--space-4); - border-top: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -/* ----------------------------------------------------------------------- - Form sections (disclosure-based grouping) - ----------------------------------------------------------------------- */ -.form-section { - border: 1px solid var(--border-subtle); - border-radius: var(--radius-lg); - margin-bottom: var(--space-4); - overflow: hidden; -} - -.form-section-header { - display: flex; - align-items: center; - gap: var(--space-2); - width: 100%; - padding: var(--space-3) var(--space-4); - font-size: var(--text-sm); - font-weight: var(--weight-semibold); - color: var(--text-primary); - cursor: pointer; - user-select: none; - -webkit-user-select: none; - background: var(--bg-elevated); - border: none; - cursor: pointer; - user-select: none; - text-align: left; - transition: background var(--transition-fast); -} - -.form-section-header:hover { - background: var(--bg-hover); -} - -.form-section-chevron { - width: 14px; - height: 14px; - color: var(--text-tertiary); - transition: transform var(--transition-normal); - flex-shrink: 0; -} - -.form-section.is-open .form-section-chevron { - transform: rotate(90deg); -} - -.form-section-icon { - width: 16px; - text-align: center; - flex-shrink: 0; - font-size: var(--text-sm); - color: var(--text-tertiary); -} - -.form-section-body, -.form-section-content { - padding: var(--space-4); - display: none; -} - -.form-section.is-open .form-section-body, -.form-section.is-open .form-section-content { - display: block; -} - -/* ----------------------------------------------------------------------- - Field grid (two-column layout for compact forms) - ----------------------------------------------------------------------- */ -.form-fields { - display: flex; - flex-direction: column; - gap: var(--space-3); -} - -.form-row { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--space-3); -} - -/* Single-column override */ -.form-row.full-width { - grid-template-columns: 1fr; -} - -/* Three-column for port + protocol combos */ -.form-row.triple { - grid-template-columns: 1fr 100px 120px; -} - -/* ----------------------------------------------------------------------- - Protocol-specific field visibility - Fields are hidden unless their protocol class is active. - ----------------------------------------------------------------------- */ -.proto-only-ssh, -.proto-only-telnet, -.proto-only-raw { - display: none; -} - -.protocol-ssh .proto-only-ssh, -.protocol-telnet .proto-only-telnet, -.protocol-raw .proto-only-raw, -.protocol-github .proto-only-github { - display: flex; -} - -/* For block-level protocol fields */ -.protocol-ssh .proto-only-ssh.form-section, -.protocol-telnet .proto-only-telnet.form-section, -.protocol-raw .proto-only-raw.form-section, -.protocol-github .proto-only-github.form-section { - display: block; -} - -/* ----------------------------------------------------------------------- - Tag input - ----------------------------------------------------------------------- */ -.tag-input-wrapper { - display: flex; - flex-wrap: wrap; - align-items: center; - gap: var(--space-1); - min-height: 30px; - padding: var(--space-1) var(--space-2); - background: var(--bg-input); - border: 1px solid var(--border-default); - border-radius: var(--radius-md); - cursor: text; - transition: border-color var(--transition-fast); -} - -.tag-input-wrapper:hover { - border-color: var(--border-strong); -} - -.tag-input-wrapper:focus-within { - border-color: var(--accent); - box-shadow: 0 0 0 2px var(--accent-muted); -} - -.tag-item { - display: inline-flex; - align-items: center; - gap: 3px; - height: 20px; - padding: 0 var(--space-2); - font-size: var(--text-xs); - color: var(--accent); - background: var(--accent-muted); - border-radius: var(--radius-sm); - white-space: nowrap; -} - -.tag-item-remove { - display: flex; - align-items: center; - justify-content: center; - width: 12px; - height: 12px; - font-size: 10px; - cursor: pointer; - border-radius: 50%; - color: var(--accent); - transition: background var(--transition-fast); -} - -.tag-item-remove:hover { - background: var(--accent-strong); -} - -.tag-input { - flex: 1; - min-width: 60px; - border: none; - background: transparent; - font-size: var(--text-sm); - color: var(--text-primary); - outline: none; - height: 22px; -} - -.tag-input::placeholder { - color: var(--text-disabled); -} - -/* ----------------------------------------------------------------------- - Jump host / proxy list - ----------------------------------------------------------------------- */ -.jump-host-list { - display: flex; - flex-direction: column; - gap: var(--space-2); -} - -.jump-host-item { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-3); - background: var(--bg-input); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - font-size: var(--text-sm); -} - -.jump-host-item .jump-host-order { - font-size: var(--text-xs); - color: var(--text-disabled); - min-width: 16px; - text-align: center; -} - -.jump-host-item .jump-host-value { - flex: 1; - font-family: var(--font-mono); - color: var(--text-secondary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.jump-host-item .jump-host-actions { - display: flex; - gap: var(--space-1); - opacity: 0; - transition: opacity var(--transition-fast); -} - -.jump-host-item:hover .jump-host-actions { - opacity: 1; -} - -.jump-host-add { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2); - font-size: var(--text-sm); - color: var(--text-tertiary); - border: 1px dashed var(--border-default); - border-radius: var(--radius-md); - cursor: pointer; - transition: - border-color var(--transition-fast), - color var(--transition-fast); -} - -.jump-host-add:hover { - border-color: var(--accent); - color: var(--accent); -} - -/* ----------------------------------------------------------------------- - Key file selector row - ----------------------------------------------------------------------- */ -.file-selector { - display: flex; - gap: var(--space-2); -} - -.file-selector .form-input { - flex: 1; -} - -.file-selector .btn { - flex-shrink: 0; -} - -/* File path display */ -.file-path { - font-family: var(--font-mono); - font-size: var(--text-xs); - color: var(--text-tertiary); - background: var(--bg-input); - padding: var(--space-1) var(--space-2); - border-radius: var(--radius-sm); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -/* ----------------------------------------------------------------------- - Inline validation - ----------------------------------------------------------------------- */ -.form-group.is-valid .form-input { - border-color: var(--success); -} - -.form-group.is-valid .form-input:focus { - box-shadow: 0 0 0 2px rgba(102, 187, 106, 0.2); -} - -.form-group.is-invalid .form-input { - border-color: var(--error); -} - -.form-group.is-invalid .form-input:focus { - box-shadow: 0 0 0 2px rgba(239, 83, 80, 0.2); -} - -.form-validation-icon { - position: absolute; - right: var(--space-3); - top: 50%; - transform: translateY(-50%); - font-size: var(--text-sm); -} - -.form-group.is-valid .form-validation-icon { - color: var(--success); -} -.form-group.is-invalid .form-validation-icon { - color: var(--error); -} - -/* ----------------------------------------------------------------------- - Help text under sections - ----------------------------------------------------------------------- */ -.form-section-help { - font-size: var(--text-xs); - color: var(--text-tertiary); - padding: 0 var(--space-4) var(--space-3); - line-height: var(--leading-relaxed); -} - -/* ----------------------------------------------------------------------- - Colour preview (for terminal colour settings) - ----------------------------------------------------------------------- */ -.colour-preview { - display: inline-block; - width: 18px; - height: 18px; - border-radius: var(--radius-sm); - border: 1px solid var(--border-default); - vertical-align: middle; - cursor: pointer; -} - -/* ----------------------------------------------------------------------- - Number input with stepper - ----------------------------------------------------------------------- */ -.form-number { - display: flex; - align-items: center; -} - -.form-number .form-input { - border-radius: var(--radius-md) 0 0 var(--radius-md); - text-align: center; - width: 60px; - /* Hide browser spinners */ - -moz-appearance: textfield; -} -.form-number .form-input::-webkit-inner-spin-button, -.form-number .form-input::-webkit-outer-spin-button { - appearance: none; -} - -.form-number-btn { - width: 24px; - height: 30px; - display: flex; - align-items: center; - justify-content: center; - font-size: var(--text-sm); - color: var(--text-secondary); - background: var(--bg-input); - border: 1px solid var(--border-default); - border-left: none; - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); -} - -.form-number-btn:last-child { - border-radius: 0 var(--radius-md) var(--radius-md) 0; -} - -.form-number-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -/* ----------------------------------------------------------------------- - Form action row (test connection, etc.) - ----------------------------------------------------------------------- */ -.form-actions-inline { - display: flex; - align-items: center; - gap: var(--space-2); - padding-top: var(--space-3); - border-top: 1px solid var(--border-subtle); - margin-top: var(--space-3); -} - -.form-actions-inline .btn { - flex-shrink: 0; -} - -.form-actions-inline .form-actions-status { - font-size: var(--text-xs); - color: var(--text-tertiary); - flex: 1; -} - -/* ----------------------------------------------------------------------- - Form hint — inline help text below fields - ----------------------------------------------------------------------- */ -.form-hint { - font-size: var(--text-xs); - color: var(--text-tertiary); - margin-top: var(--space-1); - line-height: var(--leading-relaxed); -} - -.form-hint.secret-error { - color: var(--danger); -} - -.settings-shell-row { - display: flex; - gap: var(--space-2); - align-items: center; -} - -.settings-shell-row .form-input { - flex: 1; -} - -.form-success { - font-size: var(--text-xs); - color: var(--success); - margin-top: var(--space-1); - line-height: var(--leading-relaxed); -} - -/* ----------------------------------------------------------------------- - Toggle wrapper — label + toggle switch in a row - ----------------------------------------------------------------------- */ -.form-toggle-wrapper { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-3); - padding: var(--space-1) 0; -} - -.form-toggle-label { - font-size: var(--text-sm); - color: var(--text-secondary); - flex: 1; -} - -/* ----------------------------------------------------------------------- - Jump host rows - ----------------------------------------------------------------------- */ -.jump-hosts-list { - display: flex; - flex-direction: column; - gap: var(--space-2); -} - -.jump-host-row { - display: flex; - flex-direction: column; - gap: var(--space-1); - padding: var(--space-2); - background: var(--surface-2); - border-radius: var(--radius-sm); - border: 1px solid var(--border); -} - -.jump-host-fields { - display: flex; - gap: var(--space-2); - align-items: center; -} - -/* ----------------------------------------------------------------------- - Port-forwarding rows - ----------------------------------------------------------------------- */ -.port-forwards-list { - display: flex; - flex-direction: column; - gap: var(--space-2); -} - -.port-forward-row { - display: flex; - flex-direction: column; - gap: var(--space-1); - padding: var(--space-2); - background: var(--surface-2); - border-radius: var(--radius-sm); - border: 1px solid var(--border); -} - -.port-forward-fields { - display: flex; - gap: var(--space-2); - align-items: center; -} - -.port-forward-arrow { - color: var(--text-muted); - font-size: var(--text-sm); - flex-shrink: 0; -} - -.port-forward-enabled-label { - display: flex; - gap: var(--space-1); - align-items: center; - font-size: var(--text-xs); - color: var(--text-secondary); - white-space: nowrap; - cursor: pointer; -} - -.port-forward-enabled-label input[type="checkbox"] { - accent-color: var(--accent); -} - -/* ----------------------------------------------------------------------- - Payload preset rows - ----------------------------------------------------------------------- */ -.payload-presets-list { - display: flex; - flex-direction: column; - gap: var(--space-2); -} - -.preset-row { - display: grid; - grid-template-columns: 120px 1fr 100px auto; - gap: var(--space-2); - align-items: center; -} - -/* ----------------------------------------------------------------------- - Small / compact form controls — used inside repeater rows - ----------------------------------------------------------------------- */ -.form-input-sm, -.form-select-sm { - font-size: var(--text-xs); - padding: var(--space-1) var(--space-2); - height: 30px; -} - -/* ----------------------------------------------------------------------- - Login Script Editor — expect-style send/wait steps - ----------------------------------------------------------------------- */ -.form-help { - font-size: var(--text-xs); - color: var(--text-muted); - margin-bottom: var(--space-3); - line-height: 1.5; -} - -.login-script-list { - display: flex; - flex-direction: column; - gap: var(--space-2); - margin-bottom: var(--space-3); -} - -.login-script-empty { - font-size: var(--text-xs); - color: var(--text-muted); - font-style: italic; - padding: var(--space-2) 0; -} - -.login-script-row { - display: flex; - align-items: center; - gap: var(--space-2); - background: var(--bg-base); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-sm); - padding: var(--space-2); -} - -.login-script-type { - width: 90px; - min-width: 90px; - font-size: var(--text-xs); - padding: var(--space-1) var(--space-2); - height: 30px; -} - -.login-script-value { - flex: 1; - font-size: var(--text-xs); - padding: var(--space-1) var(--space-2); - height: 30px; -} - -.login-script-timeout { - width: 60px; - min-width: 60px; - font-size: var(--text-xs); - padding: var(--space-1) var(--space-2); - height: 30px; -} - -.login-script-timeout-label { - font-size: var(--text-xs); - color: var(--text-muted); - min-width: 10px; -} - -.login-script-actions { - display: flex; - gap: 2px; - flex-shrink: 0; -} - -.login-script-btn { - background: var(--bg-elevated); - color: var(--text-secondary); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-xs); - width: 24px; - height: 24px; - font-size: 10px; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: - background 0.15s, - color 0.15s; -} - -.login-script-btn:hover { - background: var(--bg-surface); - color: var(--text-primary); -} - -.login-script-btn-remove:hover { - background: var(--danger); - color: #fff; -} - -.login-script-add-row { - display: flex; - gap: var(--space-2); -} - -/* ----------------------------------------------------------------------- - TOTP / 2FA — secret input row with action buttons - ----------------------------------------------------------------------- */ -.totp-input-row { - display: flex; - gap: var(--space-2); - align-items: center; -} - -.totp-input-row .form-input { - flex: 1; -} - -/* SSH key path row */ -.key-path-row { - display: flex; - gap: var(--space-2); - align-items: center; -} - -.key-path-row .form-input { - flex: 1; -} - -.jump-hosts-list .form-error { - margin-bottom: var(--space-2); -} - -.totp-toggle, -.totp-test, -.totp-save, -.totp-delete { - flex-shrink: 0; - white-space: nowrap; -} - -.btn-danger { - background: var(--error); - color: #fff; - border: 1px solid transparent; -} - -.btn-danger:hover { - background: #d32f2f; -} diff --git a/frontend/src/css/github.css b/frontend/src/css/github.css deleted file mode 100644 index 05c4b9b..0000000 --- a/frontend/src/css/github.css +++ /dev/null @@ -1,304 +0,0 @@ -/* ========================================================================= - GitHub Repository Viewer - Styles for the non-terminal GitHub connection panel. - ========================================================================= */ - -.github-viewer { - display: flex; - flex-direction: column; - height: 100%; - background: var(--bg-primary); - color: var(--text-primary); - font-family: var(--font-family); -} - -/* ── Header bar ────────────────────────────────────────────────────────── */ - -.github-viewer-header { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-3) var(--space-4) var(--space-3) var(--space-5); - background: var(--bg-secondary); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.github-viewer-title-area { - display: flex; - align-items: center; - gap: var(--space-2); - flex-shrink: 0; -} - -.github-viewer-icon { - display: flex; - align-items: center; - color: var(--protocol-github); - opacity: 0.9; -} - -.github-viewer-title { - font-size: var(--font-size-lg); - font-weight: 600; - color: var(--text-primary); - white-space: nowrap; -} - -.github-viewer-search { - flex: 1; - max-width: 360px; -} - -.github-viewer-search-input { - width: 100%; - padding: var(--space-1) var(--space-3); - background: var(--bg-tertiary); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-sm); - color: var(--text-primary); - font-size: var(--font-size-sm); - outline: none; - transition: border-color 0.15s; -} - -.github-viewer-search-input:focus { - border-color: var(--protocol-github); -} - -.github-viewer-search-input::placeholder { - color: var(--text-muted); -} - -.github-viewer-refresh { - display: flex; - align-items: center; - justify-content: center; - width: 32px; - height: 32px; - background: var(--bg-tertiary); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-sm); - color: var(--text-secondary); - font-size: 16px; - cursor: pointer; - transition: all 0.15s; - flex-shrink: 0; -} - -.github-viewer-refresh:hover { - background: var(--bg-hover); - color: var(--protocol-github); - border-color: var(--protocol-github); -} - -/* ── Status bar ────────────────────────────────────────────────────────── */ - -.github-viewer-status { - padding: var(--space-1) var(--space-4) var(--space-1) var(--space-5); - font-size: var(--font-size-xs); - color: var(--text-muted); - background: var(--bg-secondary); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.github-viewer-status-error { - color: var(--red); -} - -/* ── Repo list ─────────────────────────────────────────────────────────── */ - -.github-viewer-list { - flex: 1; - overflow-y: auto; - padding: var(--space-3) var(--space-4) var(--space-3) var(--space-5); -} - -.github-viewer-empty { - padding: var(--space-8) var(--space-4); - text-align: center; - color: var(--text-muted); - font-size: var(--font-size-sm); -} - -/* ── Group ─────────────────────────────────────────────────────────────── */ - -.github-group { - margin-bottom: var(--space-4); -} - -.github-group-header { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-1) 0; - margin-bottom: var(--space-2); - border-bottom: 1px solid var(--border-subtle); -} - -.github-group-name { - font-size: var(--font-size-sm); - font-weight: 600; - color: var(--text-secondary); - text-transform: none; - letter-spacing: 0.01em; -} - -.github-group-count { - font-size: var(--font-size-xs); - color: var(--text-muted); - background: var(--bg-tertiary); - padding: 1px 6px; - border-radius: var(--radius-full); -} - -/* ── Repo card ─────────────────────────────────────────────────────────── */ - -.github-repo-card { - padding: var(--space-2) var(--space-3); - margin-bottom: 2px; - border-radius: var(--radius-sm); - cursor: pointer; - transition: background 0.12s; -} - -.github-repo-card:hover { - background: var(--bg-hover); -} - -.github-repo-top { - display: flex; - align-items: center; - gap: var(--space-2); - margin-bottom: 2px; -} - -.github-repo-commit { - font-size: var(--font-size-xs); - color: var(--text-muted); - margin-bottom: 4px; -} - -.github-repo-name { - font-size: var(--font-size-sm); - font-weight: 600; - color: var(--protocol-github); -} - -.github-repo-badge { - font-size: 10px; - padding: 0 5px; - border-radius: var(--radius-full); - line-height: 16px; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.03em; -} - -.github-repo-actions { - margin-left: auto; - display: flex; - flex-direction: column; - align-items: flex-end; - gap: 4px; -} - -.github-repo-download, -.github-repo-visit { - padding: 0 6px; - height: 18px; - border-radius: var(--radius-full); - border: 1px solid var(--border-subtle); - background: var(--bg-tertiary); - color: var(--text-secondary); - font-size: 10px; - letter-spacing: 0.04em; - cursor: pointer; - transition: - background 0.15s, - color 0.15s, - border-color 0.15s; -} - -.github-repo-download:hover, -.github-repo-visit:hover { - background: var(--bg-hover); - color: var(--protocol-github); - border-color: var(--protocol-github); -} - -.github-repo-private { - background: rgba(255, 167, 38, 0.12); - color: #ffa726; -} - -.github-repo-fork { - background: rgba(78, 205, 196, 0.12); - color: #4ecdc4; -} - -.github-repo-archived { - background: rgba(255, 255, 255, 0.06); - color: var(--text-muted); -} - -.github-repo-desc { - font-size: var(--font-size-xs); - color: var(--text-secondary); - line-height: 1.4; - margin-bottom: 4px; - display: -webkit-box; - -webkit-line-clamp: 2; - -webkit-box-orient: vertical; - overflow: hidden; -} - -.github-repo-meta { - display: flex; - align-items: center; - gap: var(--space-3); - font-size: 11px; - color: var(--text-muted); -} - -.github-repo-lang { - display: flex; - align-items: center; - gap: 4px; -} - -.github-lang-dot { - width: 8px; - height: 8px; - border-radius: 50%; - display: inline-block; - flex-shrink: 0; -} - -.github-repo-stars { - /* Star colour is inherent in the text */ -} - -.github-repo-pushed { - /* Relative time text */ -} - -/* ── Scrollbar styling ─────────────────────────────────────────────────── */ - -.github-viewer-list::-webkit-scrollbar { - width: 6px; -} - -.github-viewer-list::-webkit-scrollbar-track { - background: transparent; -} - -.github-viewer-list::-webkit-scrollbar-thumb { - background: var(--scrollbar-thumb); - border-radius: 3px; -} - -.github-viewer-list::-webkit-scrollbar-thumb:hover { - background: var(--scrollbar-thumb-hover); -} diff --git a/frontend/src/css/irc.css b/frontend/src/css/irc.css deleted file mode 100644 index 42f231f..0000000 --- a/frontend/src/css/irc.css +++ /dev/null @@ -1,289 +0,0 @@ -/* ========================================================================== - Alchementary Link - IRC viewer - ========================================================================== */ - -.irc-viewer-panel { - background: var(--bg-base); -} - -.irc-viewer { - display: grid; - grid-template-columns: minmax(0, 1fr) 260px; - width: 100%; - height: 100%; - min-width: 0; - min-height: 0; -} - -.irc-main { - display: grid; - grid-template-rows: 34px minmax(0, 1fr) 42px; - min-width: 0; - min-height: 0; -} - -.irc-room-tabs { - display: flex; - align-items: center; - gap: var(--space-1); - min-width: 0; - overflow-x: auto; - overflow-y: hidden; - padding: var(--space-1) var(--space-2); - background: var(--bg-surface); - border-bottom: 1px solid var(--border-subtle); -} - -.irc-room-tab { - height: 24px; - max-width: 180px; - min-width: 44px; - padding: 0 var(--space-2); - border: 1px solid transparent; - border-radius: var(--radius-sm); - background: transparent; - color: var(--text-secondary); - font-size: var(--text-xs); - line-height: 22px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - cursor: pointer; -} - -.irc-room-tab:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.irc-room-tab.active { - background: var(--bg-active); - border-color: var(--border-default); - color: var(--text-primary); -} - -.irc-room-tab.has-unread::after { - content: ""; - display: inline-block; - width: 5px; - height: 5px; - margin-left: var(--space-1); - border-radius: 50%; - background: var(--protocol-irc); - vertical-align: middle; -} - -.irc-terminal { - position: relative; - min-width: 0; - min-height: 0; - overflow: hidden; - padding: var(--space-2); -} - -.irc-terminal .xterm { - height: 100%; -} - -.irc-composer { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: var(--space-2); - padding: var(--space-2); - background: var(--bg-surface); - border-top: 1px solid var(--border-subtle); -} - -.irc-input { - min-width: 0; - height: 26px; - padding: 0 var(--space-2); - border: 1px solid var(--border-default); - border-radius: var(--radius-sm); - background: var(--bg-input); - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--text-sm); -} - -.irc-input:focus, -.irc-join-input:focus, -.irc-filter-input:focus { - outline: none; - border-color: var(--border-focus); -} - -.irc-send { - height: 26px; -} - -.irc-sidebar { - display: grid; - grid-template-rows: minmax(88px, auto) minmax(120px, 1fr) minmax(120px, 1fr); - min-width: 0; - min-height: 0; - background: var(--bg-surface); - border-left: 1px solid var(--border-subtle); -} - -.irc-sidebar-section { - min-height: 0; - padding: var(--space-2); - border-bottom: 1px solid var(--border-subtle); - overflow: hidden; -} - -.irc-users-section { - border-bottom: 0; -} - -.irc-sidebar-title, -.irc-sidebar-title-row { - min-height: 24px; - color: var(--text-tertiary); - font-size: var(--text-xs); - font-weight: var(--weight-semibold); - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.irc-sidebar-title-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-2); -} - -.irc-join-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - gap: var(--space-1); - margin-bottom: var(--space-2); -} - -.irc-join-input, -.irc-filter-input { - min-width: 0; - height: 24px; - padding: 0 var(--space-2); - border: 1px solid var(--border-default); - border-radius: var(--radius-sm); - background: var(--bg-input); - color: var(--text-primary); - font-size: var(--text-xs); -} - -.irc-filter-input { - width: 100%; - margin-bottom: var(--space-2); -} - -.irc-sidebar-list, -.irc-room-list, -.irc-user-list { - min-height: 0; - max-height: 100%; - overflow-y: auto; -} - -.irc-room-row, -.irc-user-row, -.irc-listed-room-main { - width: 100%; - min-width: 0; - border: 0; - background: transparent; - color: var(--text-secondary); - cursor: pointer; - text-align: left; -} - -.irc-room-row { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: var(--space-2); - height: 26px; - padding: 0 var(--space-2); - border-radius: var(--radius-sm); -} - -.irc-room-row:hover, -.irc-user-row:hover, -.irc-listed-room:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.irc-room-row.active { - background: var(--bg-active); - color: var(--text-primary); -} - -.irc-room-name, -.irc-listed-room-name, -.irc-listed-room-topic { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.irc-room-count, -.irc-listed-room-count { - color: var(--text-tertiary); - font-size: 10px; - font-variant-numeric: tabular-nums; -} - -.irc-listed-room { - display: grid; - grid-template-columns: minmax(0, 1fr) auto; - align-items: center; - gap: var(--space-2); - min-height: 34px; - padding: var(--space-1) var(--space-2); - border-radius: var(--radius-sm); -} - -.irc-listed-room-main { - display: grid; - gap: 1px; -} - -.irc-listed-room-name { - color: var(--text-primary); - font-size: var(--text-xs); -} - -.irc-listed-room-topic { - color: var(--text-tertiary); - font-size: 10px; -} - -.irc-user-row { - display: block; - height: 24px; - padding: 0 var(--space-2); - border-radius: var(--radius-sm); - font-size: var(--text-xs); - line-height: 24px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.irc-empty { - padding: var(--space-2); - color: var(--text-tertiary); - font-size: var(--text-xs); -} - -@media (max-width: 900px) { - .irc-viewer { - grid-template-columns: minmax(0, 1fr); - } - - .irc-sidebar { - display: none; - } -} diff --git a/frontend/src/css/layout.css b/frontend/src/css/layout.css deleted file mode 100644 index 2b27d6d..0000000 --- a/frontend/src/css/layout.css +++ /dev/null @@ -1,241 +0,0 @@ -/* ========================================================================== - Alchementary Link — Application Layout - Grid shell, sidebar, tab bar, workspace, and status bar structure. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Main application grid - ----------------------------------------------------------------------- */ -.app { - display: grid; - grid-template-columns: var(--sidebar-width) 1fr; - grid-template-rows: var(--tab-bar-height) 1fr var(--status-bar-height); - height: 100vh; - overflow: hidden; - background: var(--bg-base); -} - -/* Collapsed sidebar variant */ -.app.sidebar-collapsed { - grid-template-columns: var(--sidebar-width-collapsed) 1fr; -} - -/* ----------------------------------------------------------------------- - Sidebar - ----------------------------------------------------------------------- */ -.sidebar { - grid-row: 1 / -1; - grid-column: 1; - background: var(--bg-surface); - border-right: 1px solid var(--border-subtle); - display: flex; - flex-direction: column; - overflow: hidden; - z-index: var(--z-sticky); -} - -/* ----------------------------------------------------------------------- - Tab bar - ----------------------------------------------------------------------- */ -.tab-bar { - grid-column: 2; - grid-row: 1; - position: relative; - background: var(--bg-surface); - border-bottom: 1px solid var(--border-subtle); - display: flex; - align-items: center; - height: var(--tab-bar-height); - min-height: var(--tab-bar-height); - overflow: hidden; -} - -.tab-list { - display: flex; - align-items: center; - flex: 1; - height: 100%; - min-width: 0; - overflow-x: auto; - overflow-y: hidden; - scrollbar-width: thin; - scrollbar-gutter: stable; - scroll-behavior: smooth; -} -.tab-list::-webkit-scrollbar { - height: 6px; -} -.tab-list::-webkit-scrollbar-thumb { - background: var(--border-strong); - border-radius: var(--radius-full); -} -.tab-list::-webkit-scrollbar-thumb:hover { - background: var(--text-tertiary); -} - -.tab-bar::before, -.tab-bar::after { - content: ""; - position: absolute; - top: 0; - bottom: 1px; - z-index: 2; - width: 18px; - pointer-events: none; - opacity: 0; - transition: opacity var(--transition-fast); -} - -.tab-bar::before { - left: 0; - background: linear-gradient(90deg, var(--bg-surface), transparent); -} - -.tab-bar::after { - right: calc(var(--tab-bar-height) * 2); - background: linear-gradient(-90deg, var(--bg-surface), transparent); -} - -.tab-bar.has-overflow-left::before, -.tab-bar.has-overflow-right::after { - opacity: 1; -} - -/* ----------------------------------------------------------------------- - Workspace - ----------------------------------------------------------------------- */ -.workspace { - grid-column: 2; - grid-row: 2; - position: relative; - overflow: hidden; - background: var(--bg-base); - display: flex; - flex-direction: column; -} - -/* Panel container — fills workspace, holds session panels */ -.panel-container { - flex: 1; - position: relative; - overflow: hidden; -} - -/* Individual session panel — one per tab, fills the panel container */ -.session-panel { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - overflow: hidden; -} - -/* Terminal container — fills workspace */ -.terminal-container { - position: absolute; - inset: 0; - padding: 0; -} - -/* ----------------------------------------------------------------------- - Status bar - ----------------------------------------------------------------------- */ -.status-bar { - grid-column: 2; - grid-row: 3; - background: var(--bg-surface); - border-top: 1px solid var(--border-subtle); - display: flex; - align-items: center; - padding: 0 var(--space-3); - font-size: var(--text-xs); - color: var(--text-tertiary); - gap: var(--space-4); - white-space: nowrap; - overflow: hidden; -} - -.status-bar span { - display: inline-flex; - align-items: center; - gap: var(--space-1); -} - -.status-spacer { - flex: 1; -} - -.status-update { - border: 1px solid var(--accent); - background: rgba(78, 205, 196, 0.12); - color: var(--accent); - font-size: var(--text-xs); - padding: 2px 8px; - border-radius: 999px; - cursor: pointer; - transition: - background 150ms ease, - color 150ms ease, - border-color 150ms ease; -} - -.status-update:hover { - background: rgba(78, 205, 196, 0.2); - color: var(--text-primary); - border-color: var(--accent-strong, var(--accent)); -} - -.status-update.is-hidden { - display: none; -} - -/* Status bar indicator dots */ -.status-bar .status-dot { - width: 6px; - height: 6px; - border-radius: 50%; - display: inline-block; -} -.status-dot.is-connected { - background: var(--success); -} -.status-dot.is-connecting { - background: var(--warning); -} -.status-dot.is-error { - background: var(--error); -} -.status-dot.is-disconnected { - background: var(--text-tertiary); -} - -/* Latency indicator */ -#status-latency { - font-variant-numeric: tabular-nums; -} -#status-latency.latency-good { - color: var(--success); -} -#status-latency.latency-warn { - color: var(--warning); -} -#status-latency.latency-bad { - color: var(--error); -} - -/* ----------------------------------------------------------------------- - Resize handle (between sidebar and workspace) - ----------------------------------------------------------------------- */ -.resize-handle { - position: absolute; - top: 0; - right: -3px; - width: 6px; - height: 100%; - cursor: col-resize; - z-index: var(--z-sticky); -} -.resize-handle:hover, -.resize-handle.is-dragging { - background: var(--accent-muted); -} diff --git a/frontend/src/css/leviathan.css b/frontend/src/css/leviathan.css deleted file mode 100644 index a84d5d9..0000000 --- a/frontend/src/css/leviathan.css +++ /dev/null @@ -1,752 +0,0 @@ -/* Leviathan mode inside the Link shell */ - -.leviathan-actions { - display: grid; - grid-template-columns: 1fr 1fr; - gap: var(--space-2); - padding: var(--space-2) var(--space-3); - border-bottom: 1px solid var(--border-subtle); - background: var(--bg-surface); -} - -.leviathan-group { - margin: var(--space-3) var(--space-3) var(--space-1); - padding: 0; - color: var(--sidebar-group-title); - cursor: default; - font-size: 10px; - font-weight: var(--weight-semibold); - letter-spacing: 0.04em; - text-transform: uppercase; -} - -.leviathan-tool { - display: grid; - grid-template-columns: 26px minmax(0, 1fr); - align-items: center; - gap: var(--space-2); - min-height: 30px; - margin: 1px var(--space-2); - border: 1px solid transparent; - border-radius: var(--radius-md); -} - -.leviathan-tool:hover { - border-color: var(--border-subtle); - background: var(--bg-hover); -} - -.leviathan-tool.sidebar-item-active { - border-color: rgba(78, 205, 196, 0.35); - background: var(--accent-muted); -} - -.leviathan-tool-ico { - display: inline-flex; - align-items: center; - justify-content: center; - width: 24px; - height: 20px; - border: 1px solid var(--border-subtle); - border-radius: var(--radius-sm); - color: var(--warning); - background: rgba(255, 167, 38, 0.08); - font-family: var(--font-mono); - font-size: 10px; - font-weight: var(--weight-semibold); -} - -.leviathan-tool.sidebar-item-active .leviathan-tool-ico { - color: var(--accent); - border-color: rgba(78, 205, 196, 0.35); - background: rgba(78, 205, 196, 0.12); -} - -.badge-leviathan { - color: var(--warning); - border-color: rgba(255, 167, 38, 0.35); -} - -.leviathan-panel { - flex-direction: column; - min-width: 0; - overflow: hidden; - background: #181818; -} - -.leviathan-panel-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-3); - min-height: 48px; - padding: var(--space-2) var(--space-3); - border-bottom: 1px solid var(--border-subtle); - background: var(--bg-surface); -} - -.leviathan-title-wrap { - display: flex; - align-items: center; - gap: var(--space-3); - min-width: 0; -} - -.leviathan-tool-mark { - display: inline-flex; - align-items: center; - justify-content: center; - flex: 0 0 auto; - width: 34px; - height: 30px; - border: 1px solid rgba(255, 167, 38, 0.28); - border-radius: var(--radius-md); - color: var(--warning); - background: rgba(255, 167, 38, 0.08); - font-family: var(--font-mono); - font-size: var(--text-xs); - font-weight: var(--weight-semibold); -} - -.leviathan-heading { - display: grid; - gap: 1px; - min-width: 0; -} - -.leviathan-title-wrap h2 { - margin: 0; - overflow: hidden; - color: var(--text-primary); - font-size: var(--text-md); - font-weight: var(--weight-semibold); - line-height: var(--leading-tight); - text-overflow: ellipsis; - white-space: nowrap; -} - -.leviathan-eyebrow, -.leviathan-status { - overflow: hidden; - color: var(--text-tertiary); - font-size: var(--text-xs); - line-height: var(--leading-tight); - text-overflow: ellipsis; - white-space: nowrap; -} - -.leviathan-header-actions, -.leviathan-tool-toolbar { - display: flex; - align-items: center; - gap: var(--space-2); - flex-wrap: wrap; -} - -.leviathan-header-actions .btn, -.leviathan-tool-toolbar .btn { - height: 28px; -} - -.leviathan-panel-body { - min-height: 0; - overflow: hidden; - padding: 0; -} - -.leviathan-workbench { - display: grid; - grid-template-columns: minmax(260px, 330px) minmax(0, 1fr); - gap: var(--space-3); - height: 100%; - padding: var(--space-3); -} - -.leviathan-top-workbench { - grid-template-columns: 1fr; - grid-template-rows: auto minmax(0, 1fr); - gap: var(--space-2); -} - -.leviathan-hex-workbench { - grid-template-columns: 1fr; - grid-template-rows: auto minmax(0, 1fr); -} - -.leviathan-control-plane, -.leviathan-output-plane { - min-width: 0; - min-height: 0; - border: 1px solid var(--border-subtle); - border-radius: var(--radius-lg); - background: var(--bg-surface); -} - -.leviathan-control-plane { - display: flex; - flex-direction: column; - gap: var(--space-3); - overflow: auto; - padding: var(--space-3); -} - -.leviathan-top-workbench .leviathan-control-plane { - align-items: flex-end; - flex-direction: row; - flex-wrap: wrap; - gap: var(--space-2); - min-height: 42px; - overflow: visible; - padding: var(--space-2) var(--space-3); -} - -.leviathan-output-plane { - display: flex; - flex-direction: column; - overflow: hidden; - background: #151515; -} - -.leviathan-params { - display: grid; - grid-template-columns: 1fr; - gap: var(--space-2); -} - -.leviathan-top-workbench .leviathan-params { - display: flex; - align-items: flex-end; - flex: 1 1 auto; - flex-wrap: wrap; - gap: var(--space-2); - min-width: 180px; -} - -.leviathan-param, -.leviathan-param-inline { - display: grid; - gap: var(--space-1); - min-width: 0; - color: var(--text-tertiary); - font-size: var(--text-xs); -} - -.leviathan-param > span, -.leviathan-param-inline > span { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.leviathan-top-workbench .leviathan-param, -.leviathan-top-workbench .leviathan-param-inline { - min-width: 96px; -} - -.leviathan-top-workbench .leviathan-param { - flex: 0 1 150px; -} - -.leviathan-top-workbench .leviathan-param-inline { - flex: 0 0 112px; -} - -.leviathan-hex-workbench .leviathan-param-inline { - flex-basis: 104px; -} - -.leviathan-param .form-input, -.leviathan-param-inline .form-input { - height: 30px; - border-color: var(--border-subtle); - background: var(--bg-input); - font-family: var(--font-mono); - font-size: var(--text-xs); -} - -.leviathan-param textarea.form-input { - min-height: 118px; - padding: var(--space-2); - line-height: var(--leading-normal); - resize: vertical; -} - -.leviathan-top-workbench .leviathan-tool-toolbar { - flex: 0 0 auto; -} - -.leviathan-checkbox { - width: 18px; - height: 18px; - min-height: 18px; - padding: 0; - accent-color: var(--accent); -} - -.leviathan-file-picker { - display: grid; - grid-template-columns: auto minmax(0, 1fr); - align-items: center; - gap: var(--space-2); - min-width: 0; - padding-top: var(--space-2); - border-top: 1px solid var(--border-subtle); -} - -.leviathan-file-path { - min-width: 0; - overflow: hidden; - color: var(--text-tertiary); - font-family: var(--font-mono); - font-size: var(--text-xs); - text-overflow: ellipsis; - white-space: nowrap; -} - -.leviathan-summary { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); - gap: 1px; - flex: 0 0 auto; - min-height: 0; - border-bottom: 1px solid var(--border-subtle); - background: var(--border-subtle); -} - -.leviathan-summary:empty { - display: none; -} - -.leviathan-pill { - min-width: 0; - padding: var(--space-2) var(--space-3); - background: var(--bg-surface); -} - -.leviathan-pill span { - display: block; - overflow: hidden; - color: var(--text-tertiary); - font-size: 10px; - line-height: var(--leading-tight); - text-overflow: ellipsis; - text-transform: uppercase; - white-space: nowrap; -} - -.leviathan-pill strong { - display: block; - overflow: hidden; - margin-top: 2px; - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--text-xs); - font-weight: var(--weight-medium); - text-overflow: ellipsis; - white-space: nowrap; -} - -.leviathan-results { - flex: 1; - min-width: 0; - min-height: 0; - overflow: auto; -} - -.leviathan-dashboard { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); - gap: 1px; - border-bottom: 1px solid var(--border-subtle); - background: var(--border-subtle); -} - -.leviathan-metric { - min-width: 0; - padding: var(--space-3); - background: #151515; -} - -.leviathan-metric span, -.leviathan-kv-row span { - display: block; - overflow: hidden; - color: var(--text-tertiary); - font-size: 10px; - line-height: var(--leading-tight); - text-overflow: ellipsis; - text-transform: uppercase; - white-space: nowrap; -} - -.leviathan-metric strong { - display: block; - overflow: hidden; - margin-top: var(--space-1); - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--text-sm); - font-weight: var(--weight-semibold); - text-overflow: ellipsis; - white-space: nowrap; -} - -.leviathan-sections { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: var(--space-3); - padding: var(--space-3); -} - -.leviathan-section { - min-width: 0; - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - background: var(--bg-surface); -} - -.leviathan-section h3 { - margin: 0; - padding: var(--space-2) var(--space-3); - border-bottom: 1px solid var(--border-subtle); - color: var(--text-secondary); - font-size: var(--text-xs); - font-weight: var(--weight-semibold); - text-transform: uppercase; -} - -.leviathan-kv-list { - display: grid; -} - -.leviathan-kv-row { - display: grid; - grid-template-columns: minmax(80px, 120px) minmax(0, 1fr); - gap: var(--space-2); - min-width: 0; - padding: var(--space-2) var(--space-3); - border-bottom: 1px solid var(--border-subtle); -} - -.leviathan-kv-row:last-child { - border-bottom: 0; -} - -.leviathan-kv-row strong { - overflow: hidden; - color: var(--text-primary); - font-family: var(--font-mono); - font-size: var(--text-xs); - font-weight: var(--weight-medium); - text-overflow: ellipsis; - white-space: nowrap; -} - -.leviathan-entropy-frame { - min-height: 100%; - padding: var(--space-3); - background: #101010; -} - -.leviathan-entropy-canvas { - width: 100%; - height: min(280px, 42vh); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - background: #101010; - cursor: crosshair; -} - -.leviathan-spectrogram-frame { - min-height: 100%; - overflow: auto; - padding: var(--space-3); - background: #05070c; -} - -.leviathan-spectrogram-image { - display: block; - max-width: 100%; - height: auto; - image-rendering: pixelated; -} - -.leviathan-empty, -.leviathan-json { - min-height: 100%; - color: var(--text-secondary); - background: #151515; -} - -.leviathan-empty { - display: grid; - place-items: center; - padding: var(--space-6); - color: var(--text-tertiary); - font-size: var(--text-sm); - text-align: center; -} - -.leviathan-error-state { - display: grid; - place-items: center; - min-height: 100%; - gap: var(--space-2); - padding: var(--space-6); - color: var(--text-secondary); - text-align: center; -} - -.leviathan-error-icon { - position: relative; - display: grid; - place-items: center; - width: 58px; - height: 52px; - color: #161616; - font-size: 24px; - font-weight: var(--weight-bold); -} - -.leviathan-error-icon::before { - position: absolute; - inset: 0; - background: var(--warning); - clip-path: polygon(50% 0, 100% 100%, 0 100%); - content: ""; -} - -.leviathan-error-icon span { - position: relative; - top: 7px; -} - -.leviathan-error-state strong { - color: var(--text-primary); - font-size: var(--text-md); - font-weight: var(--weight-semibold); -} - -.leviathan-error-state p { - max-width: 520px; - margin: 0; - color: var(--text-tertiary); - font-size: var(--text-sm); - line-height: var(--leading-normal); -} - -.leviathan-json { - margin: 0; - padding: var(--space-3); - overflow: auto; - white-space: pre-wrap; - font-family: var(--font-mono); - font-size: var(--text-xs); - line-height: var(--leading-normal); - user-select: text; -} - -.leviathan-table { - width: 100%; - border-collapse: separate; - border-spacing: 0; - font-size: var(--text-xs); -} - -.leviathan-table th, -.leviathan-table td { - max-width: 320px; - border-bottom: 1px solid var(--border-subtle); - padding: var(--space-2) var(--space-3); - overflow: hidden; - text-align: left; - text-overflow: ellipsis; - vertical-align: top; - white-space: nowrap; -} - -.leviathan-offset-link { - color: var(--warning); - font-family: var(--font-mono); - font-size: var(--text-xs); - text-align: left; -} - -.leviathan-offset-link:hover { - color: var(--accent); -} - -.leviathan-export-found { - min-width: 58px; -} - -.leviathan-muted { - color: var(--text-tertiary); -} - -.leviathan-table th { - position: sticky; - top: 0; - z-index: 1; - color: var(--text-tertiary); - background: var(--bg-surface); - font-weight: var(--weight-semibold); - text-transform: uppercase; -} - -.leviathan-table tr:hover td { - background: rgba(255, 255, 255, 0.03); -} - -.leviathan-table-note { - padding: var(--space-2) var(--space-3); - border-top: 1px solid var(--border-subtle); - color: var(--text-tertiary); - font-size: var(--text-xs); -} - -.leviathan-tree { - padding: var(--space-2) var(--space-3); -} - -.leviathan-tree-node { - border-left: 1px solid var(--border-subtle); - padding: var(--space-1) 0 var(--space-1) var(--space-2); - font-family: var(--font-mono); - font-size: var(--text-xs); -} - -.leviathan-tree-node strong { - color: var(--text-primary); - font-weight: var(--weight-medium); -} - -.leviathan-tree-node span { - color: var(--text-tertiary); -} - -.leviathan-hex-grid { - --leviathan-hex-width: 16; - display: grid; - align-content: start; - gap: 0; - min-height: 100%; - padding: var(--space-1); - overflow: auto; - background: #101010; - font-family: var(--font-mono); - font-size: 10px; -} - -.leviathan-hex-row { - display: grid; - grid-template-columns: 82px max-content max-content; - align-items: center; - gap: var(--space-2); - min-height: 23px; - padding: 0 var(--space-1); - border-radius: var(--radius-sm); -} - -.leviathan-hex-row:hover { - background: rgba(255, 255, 255, 0.035); -} - -.leviathan-offset { - color: var(--warning); - font-variant-numeric: tabular-nums; -} - -.leviathan-byte-cells { - display: grid; - grid-template-columns: repeat(var(--leviathan-hex-width), 22px); - gap: 2px; -} - -.leviathan-byte { - width: 22px; - min-height: 20px; - padding: 0; - border: 1px solid var(--border-subtle); - border-radius: var(--radius-sm); - color: var(--text-primary); - background: #1d1d1d; - text-align: center; - font-family: var(--font-mono); - font-size: 10px; - line-height: 18px; - transition: - border-color var(--transition-fast), - background var(--transition-fast); -} - -.leviathan-byte:hover, -.leviathan-byte:focus { - border-color: var(--accent); - background: var(--bg-input); -} - -.leviathan-byte.is-editing { - border-color: var(--warning); -} - -.leviathan-byte.is-saving { - color: var(--accent); -} - -.leviathan-byte.is-search-hit { - border-color: var(--warning); - color: var(--text-inverse); - background: var(--warning); -} - -.leviathan-ascii { - overflow: hidden; - color: var(--text-tertiary); - font-size: 10px; - line-height: 20px; - text-overflow: clip; - white-space: pre; -} - -.leviathan-modal-fields { - display: grid; - grid-template-columns: 120px minmax(0, 1fr); - gap: var(--space-3); -} - -@media (max-width: 1120px) { - .leviathan-workbench { - grid-template-columns: 1fr; - grid-template-rows: auto minmax(0, 1fr); - } - - .leviathan-control-plane { - max-height: 260px; - } - - .leviathan-params { - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); - } -} - -@media (max-width: 720px) { - .leviathan-panel-header { - align-items: stretch; - flex-direction: column; - } - - .leviathan-header-actions { - justify-content: flex-start; - } - - .leviathan-hex-row { - grid-template-columns: 1fr; - gap: var(--space-1); - padding: var(--space-2); - } - - .leviathan-byte-cells { - grid-template-columns: repeat(var(--leviathan-hex-width), 22px); - } -} diff --git a/frontend/src/css/lib/xterm.css b/frontend/src/css/lib/xterm.css deleted file mode 100644 index 819654e..0000000 --- a/frontend/src/css/lib/xterm.css +++ /dev/null @@ -1,285 +0,0 @@ -/** - * Copyright (c) 2014 The xterm.js authors. All rights reserved. - * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) - * https://github.com/chjj/term.js - * @license MIT - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - * - * Originally forked from (with the author's permission): - * Fabrice Bellard's javascript vt100 for jslinux: - * http://bellard.org/jslinux/ - * Copyright (c) 2011 Fabrice Bellard - * The original design remains. The terminal itself - * has been extended to include xterm CSI codes, among - * other features. - */ - -/** - * Default styles for xterm.js - */ - -.xterm { - cursor: text; - position: relative; - user-select: none; - -ms-user-select: none; - -webkit-user-select: none; -} - -.xterm.focus, -.xterm:focus { - outline: none; -} - -.xterm .xterm-helpers { - position: absolute; - top: 0; - /** - * The z-index of the helpers must be higher than the canvases in order for - * IMEs to appear on top. - */ - z-index: 5; -} - -.xterm .xterm-helper-textarea { - padding: 0; - border: 0; - margin: 0; - /* Move textarea out of the screen to the far left, so that the cursor is not visible */ - position: absolute; - opacity: 0; - left: -9999em; - top: 0; - width: 0; - height: 0; - z-index: -5; - /** Prevent wrapping so the IME appears against the textarea at the correct position */ - white-space: nowrap; - overflow: hidden; - resize: none; -} - -.xterm .composition-view { - /* TODO: Composition position got messed up somewhere */ - background: #000; - color: #FFF; - display: none; - position: absolute; - white-space: nowrap; - z-index: 1; -} - -.xterm .composition-view.active { - display: block; -} - -.xterm .xterm-viewport { - /* On OS X this is required in order for the scroll bar to appear fully opaque */ - background-color: #000; - overflow-y: scroll; - cursor: default; - position: absolute; - right: 0; - left: 0; - top: 0; - bottom: 0; -} - -.xterm .xterm-screen { - position: relative; -} - -.xterm .xterm-screen canvas { - position: absolute; - left: 0; - top: 0; -} - -.xterm-char-measure-element { - display: inline-block; - visibility: hidden; - position: absolute; - top: 0; - left: -9999em; - line-height: normal; -} - -.xterm.enable-mouse-events { - /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ - cursor: default; -} - -.xterm.xterm-cursor-pointer, -.xterm .xterm-cursor-pointer { - cursor: pointer; -} - -.xterm.column-select.focus { - /* Column selection mode */ - cursor: crosshair; -} - -.xterm .xterm-accessibility:not(.debug), -.xterm .xterm-message { - position: absolute; - left: 0; - top: 0; - bottom: 0; - right: 0; - z-index: 10; - color: transparent; - pointer-events: none; -} - -.xterm .xterm-accessibility-tree:not(.debug) *::selection { - color: transparent; -} - -.xterm .xterm-accessibility-tree { - font-family: monospace; - user-select: text; - white-space: pre; -} - -.xterm .xterm-accessibility-tree > div { - transform-origin: left; - width: fit-content; -} - -.xterm .live-region { - position: absolute; - left: -9999px; - width: 1px; - height: 1px; - overflow: hidden; -} - -.xterm-dim { - /* Dim should not apply to background, so the opacity of the foreground color is applied - * explicitly in the generated class and reset to 1 here */ - opacity: 1 !important; -} - -.xterm-underline-1 { text-decoration: underline; } -.xterm-underline-2 { text-decoration: double underline; } -.xterm-underline-3 { text-decoration: wavy underline; } -.xterm-underline-4 { text-decoration: dotted underline; } -.xterm-underline-5 { text-decoration: dashed underline; } - -.xterm-overline { - text-decoration: overline; -} - -.xterm-overline.xterm-underline-1 { text-decoration: overline underline; } -.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; } -.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; } -.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; } -.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; } - -.xterm-strikethrough { - text-decoration: line-through; -} - -.xterm-screen .xterm-decoration-container .xterm-decoration { - z-index: 6; - position: absolute; -} - -.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer { - z-index: 7; -} - -.xterm-decoration-overview-ruler { - z-index: 8; - position: absolute; - top: 0; - right: 0; - pointer-events: none; -} - -.xterm-decoration-top { - z-index: 2; - position: relative; -} - - - -/* Derived from vs/base/browser/ui/scrollbar/media/scrollbar.css */ - -/* xterm.js customization: Override xterm's cursor style */ -.xterm .xterm-scrollable-element > .scrollbar { - cursor: default; -} - -/* Arrows */ -.xterm .xterm-scrollable-element > .scrollbar > .scra { - cursor: pointer; - font-size: 11px !important; -} - -.xterm .xterm-scrollable-element > .visible { - opacity: 1; - - /* Background rule added for IE9 - to allow clicks on dom node */ - background:rgba(0,0,0,0); - - transition: opacity 100ms linear; - /* In front of peek view */ - z-index: 11; -} -.xterm .xterm-scrollable-element > .invisible { - opacity: 0; - pointer-events: none; -} -.xterm .xterm-scrollable-element > .invisible.fade { - transition: opacity 800ms linear; -} - -/* Scrollable Content Inset Shadow */ -.xterm .xterm-scrollable-element > .shadow { - position: absolute; - display: none; -} -.xterm .xterm-scrollable-element > .shadow.top { - display: block; - top: 0; - left: 3px; - height: 3px; - width: 100%; - box-shadow: var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset; -} -.xterm .xterm-scrollable-element > .shadow.left { - display: block; - top: 3px; - left: 0; - height: 100%; - width: 3px; - box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset; -} -.xterm .xterm-scrollable-element > .shadow.top-left-corner { - display: block; - top: 0; - left: 0; - height: 3px; - width: 3px; -} -.xterm .xterm-scrollable-element > .shadow.top.left { - box-shadow: var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset; -} diff --git a/frontend/src/css/modals.css b/frontend/src/css/modals.css deleted file mode 100644 index f816c85..0000000 --- a/frontend/src/css/modals.css +++ /dev/null @@ -1,888 +0,0 @@ -/* ========================================================================== - Alchementary Link — Modals & Drawers - Profile editor drawer, settings modal, known hosts viewer, - confirmation dialogs, host key verification dialog. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Modal backdrop - ----------------------------------------------------------------------- */ -.modal-backdrop { - position: fixed; - inset: 0; - z-index: var(--z-modal-backdrop); - background: rgba(10, 10, 20, 0.6); - backdrop-filter: blur(2px); - opacity: 0; - visibility: hidden; - transition: - opacity var(--transition-normal), - visibility var(--transition-normal); -} - -.modal-backdrop.is-visible { - opacity: 1; - visibility: visible; -} - -/* ----------------------------------------------------------------------- - Modal (centred panel) - ----------------------------------------------------------------------- */ -.modal { - position: fixed; - z-index: var(--z-modal); - top: 50%; - left: 50%; - transform: translate(-50%, -50%) scale(0.96); - width: 480px; - max-width: calc(100vw - var(--space-8)); - max-height: calc(100vh - var(--space-8)); - display: flex; - flex-direction: column; - background: var(--bg-elevated); - border: 1px solid var(--border-default); - border-radius: var(--radius-xl); - box-shadow: var(--shadow-xl); - opacity: 0; - visibility: hidden; - transition: - opacity var(--transition-normal), - visibility var(--transition-normal), - transform var(--transition-normal); -} - -.modal.is-visible { - opacity: 1; - visibility: visible; - transform: translate(-50%, -50%) scale(1); -} - -/* Size variants */ -.modal-sm { - width: 360px; -} -.modal-lg { - width: 640px; -} -.modal-xl { - width: 800px; -} - -.modal-header { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-4) var(--space-5); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.modal-header-icon { - width: 20px; - height: 20px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: var(--text-lg); - color: var(--text-tertiary); -} - -.modal-title { - flex: 1; - font-size: var(--text-md); - font-weight: var(--weight-semibold); - color: var(--text-primary); -} - -.modal-close { - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-md); - font-size: var(--text-md); - color: var(--text-tertiary); - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); - flex-shrink: 0; -} -.modal-close:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.modal-body { - flex: 1; - overflow-y: auto; - padding: var(--space-5); -} - -.modal-body.sftp-picker-body { - display: flex; - flex-direction: column; - padding: 0; - min-height: 320px; -} - -.modal-body p { - font-size: var(--text-sm); - color: var(--text-secondary); - line-height: var(--leading-relaxed); -} - -.modal-body p + p { - margin-top: var(--space-3); -} - -.modal-footer { - display: flex; - align-items: center; - justify-content: flex-end; - gap: var(--space-2); - padding: var(--space-3) var(--space-5); - border-top: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.modal-footer-left { - margin-right: auto; -} - -/* ----------------------------------------------------------------------- - Drawer (slides in from the right) - Used for the profile editor. - ----------------------------------------------------------------------- */ -.drawer-backdrop { - position: fixed; - inset: 0; - z-index: var(--z-modal-backdrop); - background: rgba(10, 10, 20, 0.5); - opacity: 0; - visibility: hidden; - transition: - opacity var(--transition-normal), - visibility var(--transition-normal); -} - -.drawer-backdrop.is-visible { - opacity: 1; - visibility: visible; -} - -.drawer { - position: fixed; - z-index: var(--z-modal); - top: 0; - right: 0; - width: 480px; - max-width: 100vw; - height: 100vh; - display: flex; - flex-direction: column; - background: var(--bg-elevated); - border-left: 1px solid var(--border-default); - box-shadow: var(--shadow-xl); - transform: translateX(100%); - transition: transform var(--transition-slow); -} - -.drawer.is-visible { - transform: translateX(0); -} - -/* Drawer width variants */ -.drawer-wide { - width: 560px; -} -.drawer-narrow { - width: 380px; -} - -.drawer-header { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-4) var(--space-5); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.drawer-title { - flex: 1; - font-size: var(--text-md); - font-weight: var(--weight-semibold); - color: var(--text-primary); -} - -.drawer-close { - width: 28px; - height: 28px; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-md); - font-size: var(--text-md); - color: var(--text-tertiary); - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); - flex-shrink: 0; -} -.drawer-close:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.drawer-body { - flex: 1; - overflow-y: auto; - padding: var(--space-5); -} - -.drawer-footer { - display: flex; - align-items: center; - justify-content: flex-end; - gap: var(--space-2); - padding: var(--space-3) var(--space-5); - border-top: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -/* Protocol selector tabs inside the drawer */ -.drawer-protocol-tabs { - display: flex; - gap: 0; - padding: 0 var(--space-5); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.drawer-proto-tab { - position: relative; - padding: var(--space-2) var(--space-4); - background: none; - border: none; - color: var(--text-secondary); - font-size: var(--text-sm); - font-weight: var(--weight-medium); - cursor: pointer; - letter-spacing: 0.04em; - transition: color var(--transition-fast); -} - -.drawer-proto-tab:hover { - color: var(--text-primary); -} - -.drawer-proto-tab.active { - color: var(--accent); -} - -.drawer-proto-tab.active::after { - content: ""; - position: absolute; - bottom: -1px; - left: var(--space-2); - right: var(--space-2); - height: 2px; - background: var(--accent); - border-radius: 1px; -} - -/* Settings tab bar */ -.settings-tab-bar { - display: flex; - gap: 0; - padding: 0 var(--space-5); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.settings-tab { - position: relative; - padding: var(--space-2) var(--space-3); - background: none; - border: none; - color: var(--text-secondary); - font-size: var(--text-sm); - font-weight: var(--weight-medium); - cursor: pointer; - transition: color var(--transition-fast); -} - -.settings-tab:hover { - color: var(--text-primary); -} - -.settings-tab.active { - color: var(--accent); -} - -.settings-tab.active::after { - content: ""; - position: absolute; - bottom: -1px; - left: var(--space-1); - right: var(--space-1); - height: 2px; - background: var(--accent); - border-radius: 1px; -} - -.settings-body { - padding: var(--space-5); -} - -/* Help tab — two-column layout with navigation sidebar */ -.settings-body.help-layout { - display: flex; - gap: 0; - padding: 0; - overflow: hidden; -} - -.help-nav { - width: 180px; - min-width: 160px; - flex-shrink: 0; - display: flex; - flex-direction: column; - padding: var(--space-2) 0; - border-right: 1px solid var(--border-subtle); - overflow-y: auto; - background: var(--bg-surface); -} - -.help-nav-item { - display: block; - width: 100%; - padding: var(--space-2) var(--space-4); - font-size: var(--text-sm); - color: var(--text-secondary); - background: none; - border: none; - text-align: left; - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); - border-left: 2px solid transparent; -} - -.help-nav-item:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.help-nav-item.active { - color: var(--accent); - background: var(--bg-elevated); - border-left-color: var(--accent); - font-weight: var(--weight-medium); -} - -.help-content { - flex: 1; - padding: var(--space-4) var(--space-5); - overflow-y: auto; - min-width: 0; - font-size: var(--text-sm); - line-height: 1.65; - color: var(--text-secondary); -} - -.help-section-title { - font-size: var(--text-lg); - font-weight: var(--weight-semibold); - color: var(--text-primary); - margin: 0 0 var(--space-3) 0; - padding-bottom: var(--space-2); - border-bottom: 1px solid var(--border-subtle); -} - -.help-content h4 { - font-size: var(--text-sm); - font-weight: var(--weight-semibold); - color: var(--text-primary); - margin: var(--space-4) 0 var(--space-2) 0; -} - -.help-content p { - margin: 0 0 var(--space-2) 0; -} - -.help-content ul, -.help-content ol { - margin: 0 0 var(--space-3) 0; - padding-left: var(--space-5); -} - -.help-content li { - margin-bottom: var(--space-1); -} - -.help-content code { - font-family: "Menlo", "Consolas", "Courier New", monospace; - font-size: 0.9em; - background: var(--bg-base); - padding: 1px 5px; - border-radius: var(--radius-sm); - color: var(--accent); -} - -.help-content kbd { - display: inline-block; - font-family: "Menlo", "Consolas", "Courier New", monospace; - font-size: 0.8em; - padding: 1px 5px; - background: var(--bg-base); - border: 1px solid var(--border-default); - border-radius: var(--radius-sm); - color: var(--text-primary); - box-shadow: 0 1px 0 var(--border-strong, var(--border-default)); - line-height: 1.4; -} - -.help-content em { - color: var(--text-tertiary); - font-style: italic; -} - -.help-content strong { - color: var(--text-primary); - font-weight: var(--weight-medium); -} - -.help-shortcuts-table { - width: 100%; - border-collapse: collapse; - margin: var(--space-2) 0 var(--space-3) 0; -} - -.help-shortcuts-table td { - padding: var(--space-1) var(--space-2); - border-bottom: 1px solid var(--border-subtle); - font-size: var(--text-sm); -} - -.help-shortcuts-table td:first-child { - width: 140px; - white-space: nowrap; - color: var(--text-primary); -} - -/* Command palette */ -.palette-overlay { - position: fixed; - inset: 0; - z-index: var(--z-modal-backdrop); - background: rgba(10, 10, 20, 0.5); - display: flex; - align-items: flex-start; - justify-content: center; - padding-top: 20vh; -} - -.palette { - width: 520px; - max-width: calc(100vw - var(--space-8)); - max-height: 400px; - display: flex; - flex-direction: column; - background: var(--bg-elevated); - border: 1px solid var(--border-default); - border-radius: var(--radius-xl); - box-shadow: var(--shadow-xl); - overflow: hidden; -} - -.palette-input { - width: 100%; - padding: var(--space-3) var(--space-4); - background: transparent; - border: none; - border-bottom: 1px solid var(--border-subtle); - color: var(--text-primary); - font-size: var(--text-base); - outline: none; -} - -.palette-list { - flex: 1; - overflow-y: auto; - padding: var(--space-1) 0; -} - -.palette-item { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-2) var(--space-4); - cursor: pointer; - transition: background var(--transition-fast); -} - -.palette-item:hover, -.palette-item-active { - background: var(--bg-hover); -} - -.palette-item-label { - flex: 1; - font-size: var(--text-sm); - color: var(--text-primary); -} - -.palette-item-detail { - font-size: var(--text-xs); - color: var(--text-tertiary); -} - -.palette-item-shortcut { - font-size: var(--text-xs); - color: var(--text-tertiary); - background: var(--bg-surface); - padding: var(--space-half) var(--space-1); - border-radius: var(--radius-sm); - font-family: var(--font-mono); -} - -.palette-section { - padding: var(--space-1) var(--space-4); - font-size: var(--text-xs); - font-weight: var(--weight-semibold); - color: var(--text-tertiary); - text-transform: uppercase; - letter-spacing: 0.06em; -} - -.palette-empty { - padding: var(--space-6) var(--space-4); - text-align: center; - font-size: var(--text-sm); - color: var(--text-tertiary); -} - -/* ----------------------------------------------------------------------- - Confirmation dialog - ----------------------------------------------------------------------- */ -.confirm-dialog { - width: 400px; -} - -.confirm-dialog .modal-body { - text-align: center; - padding: var(--space-6) var(--space-5); -} - -.confirm-dialog-icon { - width: 48px; - height: 48px; - margin: 0 auto var(--space-4); - display: flex; - align-items: center; - justify-content: center; - border-radius: 50%; - font-size: var(--text-2xl); -} - -.confirm-dialog-icon.is-warning { - background: rgba(255, 167, 38, 0.12); - color: var(--warning); -} - -.confirm-dialog-icon.is-danger { - background: rgba(239, 83, 80, 0.12); - color: var(--error); -} - -.confirm-dialog-icon.is-info { - background: rgba(66, 165, 245, 0.12); - color: var(--info); -} - -.confirm-dialog-message { - font-size: var(--text-sm); - color: var(--text-secondary); - line-height: var(--leading-relaxed); - max-width: 300px; - margin: 0 auto; -} - -/* ----------------------------------------------------------------------- - Host key verification dialog - ----------------------------------------------------------------------- */ -.hostkey-dialog { - width: 520px; -} - -.hostkey-fingerprint { - font-family: var(--font-mono); - font-size: var(--text-sm); - color: var(--text-primary); - background: var(--bg-input); - padding: var(--space-3) var(--space-4); - border-radius: var(--radius-md); - border: 1px solid var(--border-subtle); - word-break: break-all; - line-height: var(--leading-relaxed); - margin: var(--space-3) 0; -} - -.hostkey-fingerprint-label { - font-size: var(--text-xs); - font-weight: var(--weight-semibold); - color: var(--text-tertiary); - text-transform: uppercase; - letter-spacing: 0.05em; - margin-bottom: var(--space-1); -} - -.hostkey-mismatch-warning { - display: flex; - align-items: flex-start; - gap: var(--space-3); - padding: var(--space-3) var(--space-4); - background: rgba(239, 83, 80, 0.08); - border: 1px solid rgba(239, 83, 80, 0.2); - border-radius: var(--radius-md); - margin: var(--space-3) 0; -} - -.hostkey-mismatch-warning .warning-icon { - font-size: var(--text-lg); - color: var(--error); - flex-shrink: 0; - line-height: 1; - margin-top: 2px; -} - -.hostkey-mismatch-warning p { - font-size: var(--text-sm); - color: var(--text-secondary); - line-height: var(--leading-relaxed); -} - -/* ----------------------------------------------------------------------- - Settings modal - ----------------------------------------------------------------------- */ -.settings-modal { - width: 640px; - max-height: 80vh; -} - -.settings-nav { - display: flex; - gap: 1px; - background: var(--border-subtle); - border-radius: var(--radius-md); - overflow: hidden; - margin-bottom: var(--space-4); -} - -.settings-nav-item { - flex: 1; - padding: var(--space-2) var(--space-3); - font-size: var(--text-sm); - font-weight: var(--weight-medium); - color: var(--text-tertiary); - text-align: center; - background: var(--bg-surface); - border: none; - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); -} - -.settings-nav-item:hover { - color: var(--text-secondary); -} - -.settings-nav-item.active { - background: var(--bg-active); - color: var(--text-primary); -} - -.settings-section { - margin-bottom: var(--space-6); -} - -.settings-section-title { - font-size: var(--text-sm); - font-weight: var(--weight-semibold); - color: var(--text-primary); - margin-bottom: var(--space-3); - padding-bottom: var(--space-2); - border-bottom: 1px solid var(--border-subtle); -} - -/* ----------------------------------------------------------------------- - Known hosts viewer - ----------------------------------------------------------------------- */ -.known-hosts-modal { - width: 700px; -} - -.known-hosts-list { - display: flex; - flex-direction: column; - gap: 1px; -} - -.known-hosts-item { - display: grid; - grid-template-columns: 1fr 140px 60px; - gap: var(--space-3); - align-items: center; - padding: var(--space-2) var(--space-3); - font-size: var(--text-sm); - background: var(--bg-surface); - border-radius: var(--radius-sm); - transition: background var(--transition-fast); -} - -.known-hosts-item:hover { - background: var(--bg-hover); -} - -.known-hosts-hostname { - font-family: var(--font-mono); - font-weight: var(--weight-medium); - color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.known-hosts-type { - font-size: var(--text-xs); - color: var(--text-tertiary); -} - -.known-hosts-actions { - display: flex; - justify-content: flex-end; -} - -.known-hosts-empty { - padding: var(--space-8); - text-align: center; - font-size: var(--text-sm); - color: var(--text-tertiary); -} - -/* ----------------------------------------------------------------------- - Quick connect popover - ----------------------------------------------------------------------- */ -.quick-connect { - width: 360px; -} - -.quick-connect .modal-body { - padding: var(--space-4); -} - -.quick-connect-input { - width: 100%; - height: 36px; - padding: 0 var(--space-3); - font-family: var(--font-mono); - font-size: var(--text-sm); - color: var(--text-primary); - background: var(--bg-input); - border: 1px solid var(--border-default); - border-radius: var(--radius-md); -} - -.quick-connect-input::placeholder { - color: var(--text-disabled); - font-family: var(--font-sans); -} - -.quick-connect-input:focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px var(--accent-muted); - outline: none; -} - -.quick-connect-help { - font-size: var(--text-xs); - color: var(--text-tertiary); - margin-top: var(--space-2); - line-height: var(--leading-relaxed); -} - -.quick-connect-help code { - font-size: var(--text-xs); - color: var(--text-secondary); - background: var(--bg-overlay); - padding: 1px 4px; - border-radius: 2px; -} - -/* ----------------------------------------------------------------------- - Password strength meter - ----------------------------------------------------------------------- */ -.password-strength-meter { - display: flex; - align-items: center; - gap: var(--space-2); - margin-bottom: var(--space-2); -} - -.strength-bar { - flex: 1; - height: 4px; - background: var(--surface-2); - border-radius: 2px; - overflow: hidden; -} - -.strength-fill { - height: 100%; - width: 0; - border-radius: 2px; - transition: - width 0.3s ease, - background-color 0.3s ease; -} - -.strength-fill.strength-weak { - background: var(--error); -} -.strength-fill.strength-fair { - background: var(--warning); -} -.strength-fill.strength-good { - background: #66bb6a; -} -.strength-fill.strength-strong { - background: var(--success); -} - -.strength-label { - font-size: var(--text-xs); - font-weight: 500; - min-width: 48px; - text-align: right; -} - -.strength-label.strength-weak { - color: var(--error); -} -.strength-label.strength-fair { - color: var(--warning); -} -.strength-label.strength-good { - color: #66bb6a; -} -.strength-label.strength-strong { - color: var(--success); -} diff --git a/frontend/src/css/recordings.css b/frontend/src/css/recordings.css deleted file mode 100644 index 0bff67c..0000000 --- a/frontend/src/css/recordings.css +++ /dev/null @@ -1,201 +0,0 @@ -/* ── Session Recording & Playback ────────────────────────────────────── */ - -/* Recording indicator dot on tabs */ -.recording-dot { - display: inline-block; - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--error, #ef4444); - margin-right: 6px; - flex-shrink: 0; - animation: recording-pulse 1.2s ease-in-out infinite; -} - -@keyframes recording-pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.3; } -} - -/* Recordings list in browser modal */ -.recording-storage { - display: flex; - flex-direction: column; - gap: 8px; - padding: 0 0 12px; - margin-bottom: 12px; - border-bottom: 1px solid var(--border, #3f3f46); -} - -.recording-storage-summary { - display: flex; - justify-content: space-between; - gap: 12px; - font-size: 12px; - color: var(--text-secondary, #a1a1aa); -} - -.recording-cleanup-controls { - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; - gap: 8px; - align-items: center; -} - -.recording-cleanup-status { - min-height: 16px; -} - -.recordings-list { - display: flex; - flex-direction: column; - gap: 4px; -} - -.recording-item { - display: flex; - align-items: center; - justify-content: space-between; - padding: 8px 12px; - background: var(--surface-2, #2a2a2e); - border-radius: 6px; - transition: background 0.15s ease; -} - -.recording-item:hover { - background: var(--surface-3, #333338); -} - -.recording-info { - display: flex; - flex-direction: column; - gap: 2px; - min-width: 0; -} - -.recording-title { - font-size: 13px; - font-weight: 500; - color: var(--text-primary, #e4e4e7); - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.recording-meta { - font-size: 11px; - color: var(--text-tertiary, #71717a); -} - -.recording-actions { - display: flex; - gap: 4px; - flex-shrink: 0; - margin-left: 12px; -} - -@media (max-width: 640px) { - .recording-cleanup-controls { - grid-template-columns: 1fr; - } -} - -/* Playback modal — larger to fit a terminal */ -.modal-playback { - width: 90vw; - max-width: 900px; - max-height: 90vh; -} - -.playback-body { - display: flex; - flex-direction: column; - gap: 8px; - padding: 0 !important; -} - -.playback-terminal { - flex: 1; - min-height: 300px; - background: #1a1a1e; - border-radius: 4px; - padding: 4px; - margin: 0 12px; -} - -.playback-controls { - display: flex; - align-items: center; - gap: 8px; - padding: 8px 12px 12px; - background: var(--surface-1, #1e1e22); - border-top: 1px solid var(--border, #3f3f46); -} - -.playback-progress { - flex: 1; - height: 4px; - cursor: pointer; - appearance: none; - -webkit-appearance: none; - background: var(--surface-3, #333338); - border-radius: 2px; - outline: none; -} - -.playback-progress::-webkit-slider-thumb { - -webkit-appearance: none; - width: 12px; - height: 12px; - border-radius: 50%; - background: var(--accent, #3b82f6); - cursor: pointer; - border: none; -} - -.playback-time { - font-size: 11px; - color: var(--text-tertiary, #71717a); - white-space: nowrap; - min-width: 80px; - text-align: right; -} - -.playback-speed { - font-size: 11px; - background: var(--surface-2, #2a2a2e); - color: var(--text-primary, #e4e4e7); - border: 1px solid var(--border, #3f3f46); - border-radius: 4px; -} - -/* Export/import profile list */ -.export-profile-list { - display: flex; - flex-direction: column; - gap: 4px; - padding: 8px; - background: var(--surface-2, #2a2a2e); - border-radius: 6px; - border: 1px solid var(--border, #3f3f46); -} - -.export-profile-item { - display: flex; - align-items: center; - gap: 8px; - padding: 4px 8px; - font-size: 13px; - color: var(--text-primary, #e4e4e7); - cursor: pointer; - border-radius: 4px; - transition: background 0.1s ease; -} - -.export-profile-item:hover { - background: var(--surface-3, #333338); -} - -.export-profile-item input[type="checkbox"] { - accent-color: var(--accent, #3b82f6); -} diff --git a/frontend/src/css/sftp.css b/frontend/src/css/sftp.css deleted file mode 100644 index 62cdc8c..0000000 --- a/frontend/src/css/sftp.css +++ /dev/null @@ -1,514 +0,0 @@ -/* ========================================================================== - Alchementary Link — SFTP File Browser - Right-side panel for browsing remote files alongside the terminal. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Panel wrapper — overlays the terminal from the right side - ----------------------------------------------------------------------- */ -.sftp-panel { - position: absolute; - top: 0; - right: 0; - bottom: 0; - width: 300px; - min-width: 220px; - max-width: 50vw; - display: flex; - flex-direction: column; - background: var(--bg-surface); - border-left: 1px solid var(--border-subtle); - overflow: hidden; - z-index: var(--z-dropdown, 50); - box-shadow: -4px 0 16px rgba(0, 0, 0, 0.25); -} - -/* Resize handle — positioned at the left edge of the SFTP panel */ -.sftp-resize-handle { - position: absolute; - top: 0; - left: -3px; - bottom: 0; - width: 6px; - cursor: col-resize; - background: transparent; - z-index: var(--z-dropdown, 50); - transition: background var(--transition-fast); -} - -.sftp-resize-handle:hover, -.sftp-resize-handle.is-dragging { - background: var(--accent); -} - -/* ----------------------------------------------------------------------- - Toolbar (top bar with path breadcrumb + action buttons) - ----------------------------------------------------------------------- */ -.sftp-toolbar { - display: flex; - align-items: center; - gap: var(--space-1); - padding: var(--space-1) var(--space-2); - border-bottom: 1px solid var(--border-subtle); - background: var(--bg-elevated); - flex-shrink: 0; - min-height: 34px; -} - -.sftp-toolbar-actions { - display: flex; - align-items: center; - gap: 2px; - flex-shrink: 0; -} - -.sftp-toolbar-btn { - width: 26px; - height: 26px; - display: flex; - align-items: center; - justify-content: center; - border: none; - background: none; - color: var(--text-tertiary); - border-radius: var(--radius-sm); - cursor: pointer; - font-size: var(--text-sm); - transition: - background var(--transition-fast), - color var(--transition-fast); - flex-shrink: 0; - padding: 0; -} - -.sftp-toolbar-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.sftp-toolbar-btn.is-accent:hover { - color: var(--accent); -} - -.sftp-toolbar-btn.is-close:hover { - color: var(--error); -} - -/* ----------------------------------------------------------------------- - Breadcrumb path - ----------------------------------------------------------------------- */ -.sftp-breadcrumb { - flex: 1; - display: flex; - align-items: center; - min-width: 0; - overflow: hidden; - font-size: var(--text-xs); - color: var(--text-tertiary); - gap: 1px; -} - -.sftp-breadcrumb-segment { - background: none; - border: none; - color: var(--text-secondary); - font-size: var(--text-xs); - cursor: pointer; - padding: 2px 4px; - border-radius: var(--radius-sm); - white-space: nowrap; - transition: - background var(--transition-fast), - color var(--transition-fast); - max-width: 100px; - overflow: hidden; - text-overflow: ellipsis; -} - -.sftp-breadcrumb-segment:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.sftp-breadcrumb-segment:last-child { - color: var(--text-primary); - font-weight: var(--weight-medium); -} - -.sftp-breadcrumb-sep { - color: var(--text-disabled); - font-size: 10px; - flex-shrink: 0; - user-select: none; -} - -/* ----------------------------------------------------------------------- - File list - ----------------------------------------------------------------------- */ -.sftp-file-list { - flex: 1; - overflow-y: auto; - overflow-x: hidden; -} - -/* Scrollbar styling */ -.sftp-file-list::-webkit-scrollbar { - width: 6px; -} -.sftp-file-list::-webkit-scrollbar-track { - background: transparent; -} -.sftp-file-list::-webkit-scrollbar-thumb { - background: var(--border-strong, var(--bg-active)); - border-radius: 3px; -} -.sftp-file-list::-webkit-scrollbar-thumb:hover { - background: var(--text-tertiary); -} - -/* ----------------------------------------------------------------------- - File row - ----------------------------------------------------------------------- */ -.sftp-file-row { - display: grid; - grid-template-columns: 20px 1fr auto; - align-items: center; - gap: var(--space-1); - padding: var(--space-1) var(--space-2); - cursor: pointer; - transition: background var(--transition-fast); - user-select: none; - min-height: 30px; -} - -.sftp-file-row:hover { - background: var(--bg-hover); -} - -.sftp-file-row.is-selected { - background: var(--accent-muted); -} - -.sftp-file-row.is-selected:hover { - background: var(--accent-strong); -} - -.sftp-file-row.is-readonly .sftp-file-meta { - color: var(--warning); - font-weight: var(--weight-semibold); -} - -.sftp-file-row.is-unreadable { - opacity: 0.5; - cursor: not-allowed; -} - -.sftp-file-row.is-unreadable .sftp-file-name { - text-decoration: line-through; -} - -.sftp-file-icon { - font-size: 13px; - text-align: center; - line-height: 1; - flex-shrink: 0; - color: var(--text-tertiary); -} - -.sftp-file-icon.is-dir { - color: var(--accent); -} - -.sftp-file-name { - font-size: var(--text-sm); - color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; -} - -.sftp-file-meta { - font-size: var(--text-xs); - color: var(--text-tertiary); - white-space: nowrap; - text-align: right; -} - -/* ----------------------------------------------------------------------- - Parent directory row (..) - ----------------------------------------------------------------------- */ -.sftp-file-row.is-parent .sftp-file-name { - color: var(--text-secondary); - font-style: italic; -} - -/* ----------------------------------------------------------------------- - Context actions bar (shown below file list when a file is selected) - ----------------------------------------------------------------------- */ -.sftp-context-bar { - display: flex; - align-items: center; - gap: var(--space-1); - padding: var(--space-1) var(--space-2); - border-top: 1px solid var(--border-subtle); - background: var(--bg-elevated); - flex-shrink: 0; - min-height: 32px; -} - -.sftp-context-bar .sftp-ctx-label { - flex: 1; - font-size: var(--text-xs); - color: var(--text-secondary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; -} - -.sftp-context-bar .sftp-ctx-btn { - height: 24px; - padding: 0 var(--space-2); - font-size: var(--text-xs); - font-weight: var(--weight-medium); - border: none; - border-radius: var(--radius-sm); - cursor: pointer; - background: transparent; - color: var(--text-secondary); - transition: - background var(--transition-fast), - color var(--transition-fast); - white-space: nowrap; -} - -.sftp-context-bar .sftp-ctx-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.sftp-context-bar .sftp-ctx-btn.is-danger:hover { - background: rgba(239, 83, 80, 0.12); - color: var(--error); -} - -.sftp-context-bar .sftp-ctx-btn.is-accent:hover { - color: var(--accent); -} - -/* ----------------------------------------------------------------------- - States: loading, empty, error - ----------------------------------------------------------------------- */ -.sftp-state { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: var(--space-6) var(--space-4); - text-align: center; - gap: var(--space-2); -} - -.sftp-state-icon { - font-size: 28px; - color: var(--text-disabled); -} - -.sftp-state-msg { - font-size: var(--text-sm); - color: var(--text-tertiary); - line-height: var(--leading-relaxed); - max-width: 200px; -} - -.sftp-state-retry { - margin-top: var(--space-1); -} - -/* Loading spinner (pure CSS) */ -.sftp-spinner { - width: 20px; - height: 20px; - border: 2px solid var(--border-subtle); - border-top-color: var(--accent); - border-radius: 50%; - animation: sftp-spin 0.6s linear infinite; -} - -@keyframes sftp-spin { - to { - transform: rotate(360deg); - } -} - -/* ----------------------------------------------------------------------- - Inline input (for rename / mkdir) - ----------------------------------------------------------------------- */ -.sftp-inline-input { - width: 100%; - height: 26px; - padding: 0 var(--space-2); - font-size: var(--text-sm); - font-family: inherit; - color: var(--text-primary); - background: var(--bg-input); - border: 1px solid var(--accent); - border-radius: var(--radius-sm); - outline: none; - box-shadow: 0 0 0 2px var(--accent-muted); -} - -/* Picker-specific input in the toolbar */ -.sftp-picker-filter { - width: 160px; - height: 26px; - padding: 0 var(--space-2); - font-size: var(--text-xs); - border: 1px solid var(--border-default); - border-radius: var(--radius-sm); - background: var(--bg-input); - color: var(--text-primary); -} - -.sftp-picker-filter:focus { - border-color: var(--accent); - box-shadow: 0 0 0 2px var(--accent-muted); - outline: none; -} - -.sftp-picker-status { - font-size: var(--text-xs); - color: var(--text-tertiary); - margin-right: var(--space-2); -} - -.sftp-picker-badge { - display: inline-flex; - align-items: center; - justify-content: center; - height: 18px; - padding: 0 var(--space-2); - border-radius: var(--radius-sm); - font-size: 10px; - font-weight: var(--weight-semibold); - text-transform: uppercase; - letter-spacing: 0.04em; - border: 1px solid var(--border-default); - background: var(--bg-surface); - color: var(--text-tertiary); -} - -.sftp-picker-badge.is-readonly { - border-color: rgba(255, 167, 38, 0.35); - color: var(--warning); - background: rgba(255, 167, 38, 0.12); -} - -.sftp-picker-badge.is-noread { - border-color: rgba(239, 83, 80, 0.35); - color: var(--error); - background: rgba(239, 83, 80, 0.12); -} - -.sftp-picker-legend { - display: inline-flex; - align-items: center; - gap: var(--space-1); - font-size: 10px; - color: var(--text-tertiary); -} - -/* ----------------------------------------------------------------------- - Transfer progress queue - ----------------------------------------------------------------------- */ -.sftp-transfer-queue { - flex-shrink: 0; - border-bottom: 1px solid var(--border-subtle); - background: var(--bg-elevated); - max-height: 140px; - overflow-y: auto; -} - -.sftp-transfer-header { - font-size: 10px; - font-weight: var(--weight-semibold); - text-transform: uppercase; - letter-spacing: 0.04em; - color: var(--text-tertiary); - padding: var(--space-1) var(--space-2) 0; -} - -.sftp-transfer-row { - display: flex; - align-items: center; - gap: var(--space-1); - padding: 4px var(--space-2); -} - -.sftp-transfer-icon { - font-size: var(--text-sm); - font-weight: var(--weight-semibold); - color: var(--accent); - flex-shrink: 0; - width: 16px; - text-align: center; -} - -.sftp-transfer-info { - flex: 1; - min-width: 0; - display: flex; - flex-direction: column; - gap: 2px; -} - -.sftp-transfer-name { - font-size: var(--text-xs); - color: var(--text-primary); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.sftp-transfer-progress { - height: 3px; - background: var(--border-subtle); - border-radius: 2px; - overflow: hidden; -} - -.sftp-transfer-bar { - height: 100%; - background: var(--accent); - border-radius: 2px; - transition: width 0.15s ease-out; -} - -.sftp-transfer-meta { - font-size: 10px; - color: var(--text-tertiary); -} - -/* ----------------------------------------------------------------------- - Bookmark list - ----------------------------------------------------------------------- */ -.sftp-bookmark-list { - max-height: 250px; - overflow-y: auto; -} - -.sftp-bookmark-row { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-1) var(--space-2); - border-radius: var(--radius-sm); - cursor: pointer; - transition: background var(--transition-fast); -} - -.sftp-bookmark-row:hover { - background: var(--bg-hover); -} diff --git a/frontend/src/css/sidebar.css b/frontend/src/css/sidebar.css deleted file mode 100644 index 58315b0..0000000 --- a/frontend/src/css/sidebar.css +++ /dev/null @@ -1,566 +0,0 @@ -/* ========================================================================== - Alchementary Link — Sidebar - Connection manager: logo, search, sections, profile list, footer. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Sidebar header (logo / title / drag region) - ----------------------------------------------------------------------- */ -.sidebar-header { - display: flex; - align-items: center; - padding: var(--space-3) var(--space-4); - height: var(--tab-bar-height); - min-height: var(--tab-bar-height); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.sidebar-logo { - display: flex; - align-items: center; - gap: var(--space-2); - width: 100%; - min-width: 0; -} - -.sidebar-logo svg { - width: 18px; - height: 18px; - color: var(--accent); - flex-shrink: 0; -} - -.sidebar-title { - font-size: var(--text-sm); - font-weight: var(--weight-semibold); - color: var(--text-primary); - letter-spacing: 0.02em; -} - -.sidebar-product-switch { - display: inline-flex; - align-items: center; - gap: var(--space-2); - flex: 1; - min-width: 0; - -webkit-user-select: none; - user-select: none; -} - -.sidebar-mode-toggle { - appearance: none; - border: 0; - background: transparent; - padding: 0; - height: auto; - color: var(--text-tertiary); - cursor: pointer; - white-space: nowrap; - -webkit-user-select: none; - user-select: none; -} - -#mode-leviathan { - margin-left: auto; - text-align: right; -} - -#mode-leviathan[hidden] { - display: none; -} - -.sidebar-mode-toggle.is-active { - color: var(--text-primary); -} - -.sidebar-brand-gold { - color: var(--sidebar-group-title); -} - -.sidebar-mode-toggle:hover { - color: var(--text-secondary); -} - -.sidebar-mode-toggle.is-active:hover { - color: var(--text-primary); -} - -.mode-leviathan .sidebar-search, -.mode-leviathan .sidebar-footer { - display: none; -} - -/* ----------------------------------------------------------------------- - Sidebar search - ----------------------------------------------------------------------- */ -.sidebar-search { - padding: var(--space-2) var(--space-3); - flex-shrink: 0; -} - -/* ----------------------------------------------------------------------- - Sidebar navigation (scrollable area) - ----------------------------------------------------------------------- */ -.sidebar-nav { - flex: 1; - overflow-y: auto; - overflow-x: hidden; - padding-bottom: var(--space-2); -} - -/* ----------------------------------------------------------------------- - Bulk actions bar (multi-select) - ----------------------------------------------------------------------- */ -.sidebar-bulk-actions { - position: sticky; - top: 0; - z-index: 2; - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-2); - padding: var(--space-2) var(--space-3); - background: var(--bg-secondary); - border-bottom: 1px solid var(--border-subtle); -} - -.sidebar-bulk-count { - font-size: var(--text-xs); - color: var(--text-secondary); - font-weight: var(--weight-semibold); -} - -.sidebar-bulk-buttons { - display: flex; - flex-wrap: wrap; - gap: var(--space-1); -} - -/* ----------------------------------------------------------------------- - Sections (Favourites, Recent, All Connections) - ----------------------------------------------------------------------- */ -.sidebar-section { - /* Each section can be collapsed */ -} - -.sidebar-section + .sidebar-section { - border-top: 1px solid var(--border-subtle); -} - -.sidebar-section-header { - display: flex; - align-items: center; - gap: var(--space-2); - width: 100%; - padding: var(--space-2) var(--space-3); - font-size: var(--text-xs); - font-weight: var(--weight-semibold); - color: var(--text-tertiary); - text-transform: uppercase; - letter-spacing: 0.06em; - cursor: pointer; - transition: - color var(--transition-fast), - background var(--transition-fast); - -webkit-user-select: none; - user-select: none; - text-align: left; - background: none; - border: none; -} - -.sidebar-section-header.sidebar-section-header-group .section-title { - color: var(--sidebar-group-title); -} - -.sidebar-section-header:hover { - color: var(--text-secondary); - background: var(--bg-hover); -} - -.sidebar-section-header.sidebar-section-header-group:hover .section-title { - color: var(--sidebar-group-title); -} - -.sidebar-section-header .section-icon { - width: 14px; - text-align: center; - flex-shrink: 0; - font-size: var(--text-sm); -} - -.sidebar-section-header .section-chevron { - width: 12px; - height: 12px; - margin-right: var(--space-1); - flex-shrink: 0; - transition: transform var(--transition-normal); - color: var(--text-disabled); -} - -.sidebar-section.is-collapsed .section-chevron { - transform: rotate(-90deg); -} - -.section-count { - margin-left: auto; - font-size: 10px; - font-weight: var(--weight-normal); - color: var(--text-disabled); - background: var(--bg-overlay); - padding: 0 var(--space-1); - border-radius: var(--radius-sm); - min-width: 18px; - text-align: center; - line-height: 16px; -} - -/* ----------------------------------------------------------------------- - Sidebar list (container for items) - ----------------------------------------------------------------------- */ -.sidebar-list { - padding: 0 var(--space-2); -} - -.sidebar-section.is-collapsed .sidebar-list { - display: none; -} - -/* ----------------------------------------------------------------------- - Sidebar item (individual connection profile) - ----------------------------------------------------------------------- */ -.sidebar-item { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-2); - margin-bottom: 0.5px; - border-radius: var(--radius-md); - cursor: pointer; - transition: - background var(--transition-fast), - box-shadow var(--transition-fast); - user-select: none; - position: relative; -} - -.sidebar-item:hover { - background: var(--bg-hover); -} - -.sidebar-item:active { - background: var(--bg-active); -} - -.sidebar-item-selected { - background: var(--accent-muted); - box-shadow: inset 0 0 0 1px var(--accent); -} - -.sidebar-item-selected .sidebar-item-name { - color: var(--text-primary); -} - -/* Active / selected state */ -.sidebar-item.active { - background: var(--accent-muted); -} -.sidebar-item.active::before { - content: ""; - position: absolute; - left: 0; - top: 4px; - bottom: 4px; - width: 2px; - border-radius: var(--radius-full); - background: var(--accent); -} - -/* Protocol indicator icon */ -.sidebar-item .protocol-indicator { - width: 14px; - height: 14px; - border-radius: var(--radius-sm); - flex-shrink: 0; - display: inline-flex; - align-items: center; - justify-content: center; - font-size: 8px; - font-weight: var(--weight-semibold); - line-height: 1; - background: var(--bg-overlay); - color: var(--text-tertiary); -} - -.sidebar-item .protocol-indicator::before { - content: "?"; -} - -.sidebar-item .protocol-indicator.proto-ssh::before { - content: "⌨"; -} -.sidebar-item .protocol-indicator.proto-telnet::before { - content: "⇄"; -} -.sidebar-item .protocol-indicator.proto-raw::before { - content: "≋"; -} -.sidebar-item .protocol-indicator.proto-github::before { - content: "GH"; -} -.sidebar-item .protocol-indicator.proto-vnc::before { - content: "⊞"; -} -.sidebar-item .protocol-indicator.proto-irc::before { - content: "#"; -} -.sidebar-item .protocol-indicator.proto-local::before { - content: "›_"; -} - -.sidebar-item .protocol-indicator.proto-ssh { - background: rgba(78, 205, 196, 0.18); - color: var(--protocol-ssh); -} -.sidebar-item .protocol-indicator.proto-telnet { - background: rgba(255, 167, 38, 0.18); - color: var(--protocol-telnet); -} -.sidebar-item .protocol-indicator.proto-raw { - background: rgba(171, 71, 188, 0.18); - color: var(--protocol-raw); -} -.sidebar-item .protocol-indicator.proto-github { - background: rgba(124, 138, 255, 0.18); - color: var(--protocol-github); -} -.sidebar-item .protocol-indicator.proto-vnc { - background: rgba(232, 93, 74, 0.18); - color: var(--protocol-vnc); -} -.sidebar-item .protocol-indicator.proto-irc { - background: rgba(216, 178, 76, 0.18); - color: var(--protocol-irc); -} -.sidebar-item .protocol-indicator.proto-local { - background: rgba(139, 195, 74, 0.18); - color: var(--protocol-local); -} - -/* Local machine item separator */ -.sidebar-item-local { - border-bottom: 1px solid var(--border-subtle); - margin: var(--space-2) var(--space-2) var(--space-2); - padding-bottom: var(--space-2); -} - -/* Text block (name + host) */ -.sidebar-item-text { - flex: 1; - min-width: 0; -} - -.sidebar-item-name { - font-size: var(--text-sm); - font-weight: var(--weight-medium); - color: var(--text-primary); - line-height: var(--leading-tight); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.sidebar-item-host { - font-size: var(--text-xs); - color: var(--text-tertiary); - font-family: var(--font-mono); - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - line-height: var(--leading-tight); - margin-top: 1px; -} - -/* Edit button (appears on hover, beside name) */ -.sidebar-item-edit { - flex-shrink: 0; - width: 22px; - height: 22px; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - color: var(--text-disabled); - background: none; - border: none; - border-radius: var(--radius-sm); - cursor: pointer; - opacity: 0; - transition: - opacity var(--transition-fast), - color var(--transition-fast), - background var(--transition-fast); -} - -.sidebar-item:hover .sidebar-item-edit { - opacity: 1; -} - -.sidebar-item-edit:hover { - color: var(--accent); - background: var(--bg-hover); -} - -/* Favourite star */ -.sidebar-item-star { - flex-shrink: 0; - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - font-size: 11px; - color: var(--text-disabled); - border-radius: var(--radius-sm); - opacity: 0; - transition: - opacity var(--transition-fast), - color var(--transition-fast); -} - -.sidebar-item:hover .sidebar-item-star { - opacity: 1; -} - -.sidebar-item-star.is-favourite { - opacity: 1; - color: var(--warning); -} - -.sidebar-item-star:hover { - color: var(--warning); -} - -/* Inline status dot (right side, tiny) */ -.sidebar-item-status { - width: 6px; - height: 6px; - border-radius: 50%; - flex-shrink: 0; - margin-right: var(--space-1); -} -.sidebar-item-status.is-connected { - background: var(--success); -} -.sidebar-item-status.is-connecting { - background: var(--warning); - animation: pulse-dot 1.2s ease infinite; -} -.sidebar-item-status.is-error { - background: var(--error); -} - -/* ----------------------------------------------------------------------- - Sidebar empty list placeholder - ----------------------------------------------------------------------- */ -.sidebar-empty { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--space-2); - margin: var(--space-6) var(--space-3) 0; - padding: var(--space-5) var(--space-3); - color: var(--text-tertiary); - text-align: center; -} - -.sidebar-empty-icon { - display: grid; - place-items: center; - width: 34px; - height: 34px; - border: 1px solid var(--border-default); - border-radius: var(--radius-lg); - background: var(--bg-elevated); - color: var(--accent); - font-size: var(--text-xl); - font-weight: var(--weight-semibold); - line-height: 1; -} - -.sidebar-empty-title { - color: var(--text-secondary); - font-size: var(--text-sm); - font-weight: var(--weight-semibold); - line-height: var(--leading-tight); -} - -.sidebar-empty-text { - max-width: 190px; - margin: 0; - color: var(--text-tertiary); - font-size: var(--text-xs); - line-height: var(--leading-normal); -} - -.sidebar-empty-actions { - display: flex; - justify-content: center; - margin-top: var(--space-1); -} - -.sidebar-list-empty { - padding: var(--space-3) var(--space-2); - font-size: var(--text-xs); - color: var(--text-disabled); - text-align: center; - font-style: italic; -} - -/* ----------------------------------------------------------------------- - Sidebar footer (actions) - ----------------------------------------------------------------------- */ -.sidebar-footer { - display: flex; - align-items: center; - gap: var(--space-1); - padding: var(--space-2) var(--space-3); - border-top: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.sidebar-footer .btn:first-child { - flex: 1; -} - -/* ----------------------------------------------------------------------- - Sidebar collapsed state - ----------------------------------------------------------------------- */ -.sidebar-collapsed .sidebar-header { - justify-content: center; - padding: var(--space-3) var(--space-2); -} -.sidebar-collapsed .sidebar-title, -.sidebar-collapsed .sidebar-search, -.sidebar-collapsed .sidebar-section-header span:not(.section-icon), -.sidebar-collapsed .sidebar-item-text, -.sidebar-collapsed .sidebar-item-star, -.sidebar-collapsed .section-count, -.sidebar-collapsed .sidebar-footer .btn:first-child span { - display: none; -} - -.sidebar-collapsed .sidebar-item { - justify-content: center; - padding: var(--space-2); -} - -.sidebar-collapsed .sidebar-section-header { - justify-content: center; - padding: var(--space-2); -} - -.sidebar-collapsed .sidebar-footer { - flex-direction: column; -} diff --git a/frontend/src/css/splitpane.css b/frontend/src/css/splitpane.css deleted file mode 100644 index 8386f29..0000000 --- a/frontend/src/css/splitpane.css +++ /dev/null @@ -1,223 +0,0 @@ -/* ========================================================================== - Alchementary Link — Split Pane Layout - Draggable split panes within session panels for multi-terminal views. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Split container — replaces direct terminal-container when split - ----------------------------------------------------------------------- */ -.split-container { - display: flex; - width: 100%; - height: 100%; - overflow: hidden; -} - -.split-container.split-horizontal { - flex-direction: column; -} - -.split-container.split-vertical { - flex-direction: row; -} - -/* ----------------------------------------------------------------------- - Individual pane inside a split - ----------------------------------------------------------------------- */ -.split-pane { - position: relative; - overflow: hidden; - min-width: 100px; - min-height: 100px; - display: flex; - flex-direction: column; -} - -/* Active pane indicator (subtle border glow) */ -.split-pane.is-focused { - box-shadow: inset 0 0 0 1px var(--accent-muted); -} - -/* ----------------------------------------------------------------------- - Pane close button — only visible on hover - ----------------------------------------------------------------------- */ -.split-pane-close { - position: absolute; - top: var(--space-1); - right: var(--space-1); - width: 18px; - height: 18px; - display: flex; - align-items: center; - justify-content: center; - border: none; - border-radius: var(--radius-sm); - background: var(--bg-overlay); - color: var(--text-tertiary); - font-size: 11px; - line-height: 1; - cursor: pointer; - opacity: 0; - transition: opacity var(--transition-fast), background var(--transition-fast), color var(--transition-fast); - z-index: 5; -} - -.split-pane:hover > .split-pane-close { - opacity: 1; -} - -.split-pane-close:hover { - background: var(--error); - color: var(--text-primary); -} - -/* ----------------------------------------------------------------------- - Draggable divider between panes - ----------------------------------------------------------------------- */ -.split-divider { - flex-shrink: 0; - background: var(--border-subtle); - position: relative; - z-index: 2; - transition: background var(--transition-fast); -} - -.split-divider:hover, -.split-divider.is-dragging { - background: var(--accent); -} - -/* Vertical split → side-by-side panes → vertical divider bar */ -.split-container.split-vertical > .split-divider { - width: 4px; - cursor: col-resize; -} - -/* Horizontal split → stacked panes → horizontal divider bar */ -.split-container.split-horizontal > .split-divider { - height: 4px; - cursor: row-resize; -} - -/* Invisible larger hit-area for easier grabbing */ -.split-divider::after { - content: ''; - position: absolute; -} - -.split-container.split-vertical > .split-divider::after { - top: 0; - bottom: 0; - left: -4px; - right: -4px; -} - -.split-container.split-horizontal > .split-divider::after { - left: 0; - right: 0; - top: -4px; - bottom: -4px; -} - -/* ----------------------------------------------------------------------- - Empty pane placeholder — shown when a new pane needs a session - ----------------------------------------------------------------------- */ -.split-pane-placeholder { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - height: 100%; - width: 100%; - gap: var(--space-3); - color: var(--text-tertiary); - font-size: var(--text-sm); - background: var(--bg-base); - padding: var(--space-4); -} - -.split-pane-placeholder-icon { - opacity: 0.4; -} - -.split-pane-placeholder-text { - text-align: center; - max-width: 200px; - line-height: var(--leading-normal); -} - -/* Profile picker list in the placeholder */ -.split-pane-profile-list { - display: flex; - flex-direction: column; - gap: var(--space-1); - max-height: 200px; - overflow-y: auto; - width: 100%; - max-width: 220px; - margin-top: var(--space-2); -} - -.split-pane-profile-list::-webkit-scrollbar { - width: 4px; -} -.split-pane-profile-list::-webkit-scrollbar-track { - background: transparent; -} -.split-pane-profile-list::-webkit-scrollbar-thumb { - background: var(--border-strong); - border-radius: var(--radius-full); -} - -.split-pane-profile-btn { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-1) var(--space-2); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-md); - background: var(--bg-surface); - color: var(--text-secondary); - font-size: var(--text-sm); - cursor: pointer; - transition: background var(--transition-fast), color var(--transition-fast), border-color var(--transition-fast); - text-align: left; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.split-pane-profile-btn:hover { - background: var(--bg-hover); - color: var(--text-primary); - border-color: var(--accent-muted); -} - -.split-pane-profile-btn .protocol-badge { - font-size: 9px; - font-weight: var(--weight-semibold); - text-transform: uppercase; - opacity: 0.7; - flex-shrink: 0; -} - -/* ----------------------------------------------------------------------- - Prevent text selection while dragging dividers - ----------------------------------------------------------------------- */ -body.split-resizing { - cursor: col-resize !important; - user-select: none !important; - -webkit-user-select: none !important; -} - -body.split-resizing-row { - cursor: row-resize !important; - user-select: none !important; - -webkit-user-select: none !important; -} - -/* Disable pointer events on iframes / xterm canvases during drag */ -body.split-resizing .xterm, -body.split-resizing-row .xterm { - pointer-events: none; -} diff --git a/frontend/src/css/tabs.css b/frontend/src/css/tabs.css deleted file mode 100644 index 81d1121..0000000 --- a/frontend/src/css/tabs.css +++ /dev/null @@ -1,496 +0,0 @@ -/* ========================================================================== - Alchementary Link — Tab Bar & Tabs - Compact session tabs with status indicators, protocol badges, and - close / pin controls. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Individual tab - ----------------------------------------------------------------------- */ -.tab { - display: inline-flex; - align-items: center; - gap: var(--space-2); - height: var(--tab-bar-height); - padding: 0 var(--space-3); - font-size: var(--text-sm); - color: var(--text-secondary); - cursor: pointer; - white-space: nowrap; - position: relative; - flex-shrink: 0; - border-right: 1px solid var(--border-subtle); - transition: - background var(--transition-fast), - color var(--transition-fast); - user-select: none; - max-width: 200px; - min-width: 0; -} - -.tab:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -/* Active tab */ -.tab.tab-active { - background: var(--bg-active); - color: var(--text-primary); -} - -/* Active indicator — accent bar at the bottom */ -.tab.tab-active::after { - content: ""; - position: absolute; - bottom: 0; - left: 0; - right: 0; - height: 2px; - background: var(--accent); - border-radius: var(--radius-full) var(--radius-full) 0 0; -} - -/* ----------------------------------------------------------------------- - Tab icon (small terminal/protocol icon) - ----------------------------------------------------------------------- */ -.tab-icon { - width: 14px; - height: 14px; - flex-shrink: 0; - display: flex; - align-items: center; - justify-content: center; - font-size: 12px; - color: var(--text-tertiary); -} - -.tab.tab-active .tab-icon { - color: var(--text-secondary); -} - -/* ----------------------------------------------------------------------- - Tab label - ----------------------------------------------------------------------- */ -.tab-label, -.tab-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - min-width: 0; - flex: 1; -} - -/* ----------------------------------------------------------------------- - Protocol mini-badge inside tab - ----------------------------------------------------------------------- */ -.tab-badge { - display: inline-flex; - align-items: center; - height: 14px; - padding: 0 3px; - font-size: 9px; - font-weight: var(--weight-semibold); - text-transform: uppercase; - letter-spacing: 0.04em; - border-radius: 2px; - flex-shrink: 0; - line-height: 1; -} - -.tab-badge.badge-ssh { - background: rgba(78, 205, 196, 0.15); - color: var(--protocol-ssh); -} -.tab-badge.badge-telnet { - background: rgba(255, 167, 38, 0.15); - color: var(--protocol-telnet); -} -.tab-badge.badge-raw { - background: rgba(171, 71, 188, 0.15); - color: var(--protocol-raw); -} -.tab-badge.badge-github { - background: rgba(124, 138, 255, 0.15); - color: var(--protocol-github); -} -.tab-badge.badge-vnc { - background: rgba(232, 93, 74, 0.15); - color: var(--protocol-vnc); -} -.tab-badge.badge-irc { - background: rgba(216, 178, 76, 0.15); - color: var(--protocol-irc); -} -.tab-badge.badge-local { - background: rgba(139, 195, 74, 0.15); - color: var(--protocol-local); -} - -/* ----------------------------------------------------------------------- - Status dot (connection state) - ----------------------------------------------------------------------- */ -.tab-status { - width: 6px; - height: 6px; - border-radius: 50%; - flex-shrink: 0; - background: var(--text-tertiary); -} - -.tab-status.is-connected { - background: var(--success); -} -.tab-status.is-connecting { - background: var(--warning); - animation: pulse-dot 1.2s ease infinite; -} -.tab-status.is-reconnecting { - background: var(--warning); - animation: pulse-dot 0.8s ease infinite; -} -.tab-status.is-error { - background: var(--error); -} -.tab-status.is-disconnected { - background: var(--text-tertiary); -} - -/* Also match the status-* pattern used by updateSessionState */ -.tab-status.status-connected { - background: var(--success); -} -.tab-status.status-connecting { - background: var(--warning); - animation: pulse-dot 1.2s ease infinite; -} -.tab-status.status-reconnecting { - background: var(--warning); - animation: pulse-dot 0.8s ease infinite; -} -.tab-status.status-error { - background: var(--error); -} -.tab-status.status-disconnected { - background: var(--text-tertiary); -} - -/* ----------------------------------------------------------------------- - Activity indicator — pulses when background tab has new output - ----------------------------------------------------------------------- */ -.tab-activity { - width: 6px; - height: 6px; - border-radius: 50%; - flex-shrink: 0; - background: var(--accent); - opacity: 0; - transition: opacity 150ms ease; -} - -.tab-has-activity .tab-activity { - opacity: 1; - animation: pulse-dot 1.5s ease infinite; -} - -.tab-active .tab-activity { - opacity: 0; -} - -/* Trigger flash — brief glow when a trigger rule matches */ -.tab-trigger-flash { - animation: trigger-flash 2s ease; -} - -@keyframes trigger-flash { - 0%, - 100% { - background: var(--bg-hover); - } - 15% { - background: rgba(239, 83, 80, 0.25); - } - 30% { - background: var(--bg-hover); - } - 45% { - background: rgba(239, 83, 80, 0.15); - } -} - -/* ----------------------------------------------------------------------- - SFTP toggle button — revealed on hover (SSH tabs only) - ----------------------------------------------------------------------- */ -.tab-sftp { - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-sm); - font-size: 10px; - color: var(--text-tertiary); - flex-shrink: 0; - opacity: 0; - transition: - opacity var(--transition-fast), - background var(--transition-fast), - color var(--transition-fast); - cursor: pointer; - background: none; - border: none; - padding: 0; - margin-left: auto; -} - -.tab:hover .tab-sftp, -.tab.tab-active .tab-sftp { - opacity: 1; -} - -.tab-sftp:hover { - background: var(--bg-active); - color: var(--accent); -} - -/* ----------------------------------------------------------------------- - Close button — revealed on hover - ----------------------------------------------------------------------- */ -.tab-close { - width: 16px; - height: 16px; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius-sm); - font-size: 11px; - color: var(--text-tertiary); - flex-shrink: 0; - opacity: 0; - transition: - opacity var(--transition-fast), - background var(--transition-fast), - color var(--transition-fast); - cursor: pointer; - margin: 0 2px 0 0; - padding: 0; - border: 0; - background: none; - line-height: 1; -} - -.tab:hover .tab-close, -.tab.tab-active .tab-close { - opacity: 1; -} - -.tab-close:hover { - background: var(--bg-active); - color: var(--text-primary); -} - -/* ----------------------------------------------------------------------- - Pin indicator - ----------------------------------------------------------------------- */ -.tab-pin { - width: 12px; - height: 12px; - display: flex; - align-items: center; - justify-content: center; - font-size: 9px; - color: var(--text-disabled); - flex-shrink: 0; -} - -.tab.is-pinned .tab-pin { - color: var(--accent); -} - -/* Pinned tabs are always narrow */ -.tab.is-pinned { - max-width: 100px; - padding: 0 var(--space-2); -} - -.tab.is-pinned .tab-label { - /* Hide label on pinned tabs to save space */ - display: none; -} - -.tab.is-pinned .tab-close { - /* Pinned tabs don't show close by default */ - display: none; -} - -/* ----------------------------------------------------------------------- - Drag handle area (for tab reordering) - ----------------------------------------------------------------------- */ -.tab-drag-handle { - cursor: grab; - flex: 1; - display: flex; - align-items: center; - gap: var(--space-2); - min-width: 0; - height: 100%; -} - -.tab-drag-handle:active { - cursor: grabbing; -} - -/* Drag ghost styling */ -.tab.is-dragging { - opacity: 0.5; -} - -.tab.is-drag-over-left::before { - content: ""; - position: absolute; - left: -1px; - top: 4px; - bottom: 4px; - width: 2px; - background: var(--accent); - border-radius: var(--radius-full); -} - -.tab.is-drag-over-right::after { - content: ""; - position: absolute; - right: -1px; - top: 4px; - bottom: 4px; - width: 2px; - background: var(--accent); - border-radius: var(--radius-full); - /* Note: this re-uses ::after — active tab indicator is overridden while dragging */ -} - -/* ----------------------------------------------------------------------- - New-tab button (+) - ----------------------------------------------------------------------- */ -.tab-new { - display: flex; - align-items: center; - justify-content: center; - width: var(--tab-bar-height); - height: var(--tab-bar-height); - font-size: var(--text-lg); - color: var(--text-tertiary); - cursor: pointer; - flex-shrink: 0; - transition: - background var(--transition-fast), - color var(--transition-fast); - border: none; - background: none; -} - -.tab-new:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.tab-new:active { - background: var(--bg-active); -} - -/* ----------------------------------------------------------------------- - Broadcast toggle button - ----------------------------------------------------------------------- */ -.tab-broadcast { - display: flex; - align-items: center; - justify-content: center; - width: var(--tab-bar-height); - height: var(--tab-bar-height); - color: var(--text-tertiary); - cursor: pointer; - flex-shrink: 0; - transition: - background var(--transition-fast), - color var(--transition-fast); - border: none; - background: none; - opacity: 0.5; -} - -.tab-broadcast:hover { - background: var(--bg-hover); - color: var(--text-primary); - opacity: 0.8; -} - -.tab-broadcast.is-active { - color: var(--error); - opacity: 1; - animation: pulse-dot 1.5s ease infinite; -} - -/* ----------------------------------------------------------------------- - Tab overflow indicators (chevrons at edges) - ----------------------------------------------------------------------- */ -.tab-bar-overflow-left, -.tab-bar-overflow-right { - display: none; - align-items: center; - justify-content: center; - width: 20px; - flex-shrink: 0; - font-size: var(--text-sm); - color: var(--text-tertiary); - cursor: pointer; - background: linear-gradient(90deg, var(--bg-surface), transparent); -} - -.tab-bar-overflow-right { - background: linear-gradient(-90deg, var(--bg-surface), transparent); -} - -.tab-bar.has-overflow-left .tab-bar-overflow-left, -.tab-bar.has-overflow-right .tab-bar-overflow-right { - display: flex; -} - -/* ----------------------------------------------------------------------- - Tab context menu trigger (on right-click) - ----------------------------------------------------------------------- */ -.tab-context-area { - position: absolute; - inset: 0; - z-index: 1; - /* Transparent hit area for right-click context menu */ -} - -/* ----------------------------------------------------------------------- - Tab hover tooltip - ----------------------------------------------------------------------- */ -.tab-tooltip { - max-width: 360px; - white-space: normal; -} - -.tab-tooltip-title { - max-width: 320px; - overflow: hidden; - color: var(--text-primary); - text-overflow: ellipsis; - white-space: nowrap; -} - -.tab-tooltip-meta { - margin-top: 2px; - color: var(--text-tertiary); - font-weight: var(--weight-normal); -} - -/* ----------------------------------------------------------------------- - Modified indicator (unsaved changes) - ----------------------------------------------------------------------- */ -.tab.is-modified .tab-close::before { - content: "●"; - font-size: 8px; - color: var(--accent); -} diff --git a/frontend/src/css/terminal.css b/frontend/src/css/terminal.css deleted file mode 100644 index 5e6f58e..0000000 --- a/frontend/src/css/terminal.css +++ /dev/null @@ -1,479 +0,0 @@ -/* ========================================================================== - Alchementary Link — Terminal Integration - xterm.js container, header info strip, RAW mode views, and send bar. - ========================================================================== */ - -/* ----------------------------------------------------------------------- - Terminal wrapper — fills the workspace panel - ----------------------------------------------------------------------- */ -.terminal-wrapper { - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; -} - -/* ----------------------------------------------------------------------- - Terminal header / info strip (optional) - Shows connection info above the terminal. - ----------------------------------------------------------------------- */ -.terminal-header { - display: flex; - align-items: center; - gap: var(--space-3); - padding: var(--space-1) var(--space-3); - font-size: var(--text-xs); - color: var(--text-tertiary); - background: var(--bg-surface); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; - min-height: 24px; -} - -.terminal-header-label { - font-weight: var(--weight-medium); - color: var(--text-secondary); -} - -.terminal-header-value { - font-family: var(--font-mono); - color: var(--text-tertiary); -} - -.terminal-header-separator { - width: 1px; - height: 10px; - background: var(--border-subtle); -} - -.terminal-header-spacer { - flex: 1; -} - -.terminal-header-actions { - display: flex; - gap: var(--space-1); -} - -/* ----------------------------------------------------------------------- - xterm.js container - ----------------------------------------------------------------------- */ -.terminal-container { - flex: 1; - position: relative; - overflow: hidden; - padding: 0; -} - -/* ----------------------------------------------------------------------- - Disconnected overlay - ----------------------------------------------------------------------- */ -.session-panel.is-disconnected > *:not(.terminal-disconnect-overlay) { - filter: blur(2px); - pointer-events: none; -} - -.terminal-disconnect-overlay { - position: absolute; - inset: 0; - display: flex; - align-items: center; - justify-content: center; - background: rgba(10, 14, 18, 0.68); - z-index: 6; - opacity: 0; - visibility: hidden; - transition: - opacity var(--transition-fast), - visibility var(--transition-fast); -} - -.terminal-disconnect-overlay.is-visible { - opacity: 1; - visibility: visible; -} - -.terminal-disconnect-content { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--space-3); - text-align: center; - padding: var(--space-4) var(--space-5); - background: var(--bg-elevated); - border: 1px solid var(--border-default); - border-radius: var(--radius-lg); - box-shadow: var(--shadow-lg); -} - -.terminal-disconnect-title { - font-size: var(--text-lg); - letter-spacing: 0.45em; - text-indent: 0.45em; - text-transform: uppercase; - color: var(--text-primary); - font-weight: var(--weight-semibold); -} - -.terminal-disconnect-message { - font-size: var(--text-sm); - color: var(--text-secondary); - max-width: 360px; -} - -/* xterm.js overrides for consistent look */ -.terminal-container .xterm, -.session-panel .xterm { - flex: 1; - height: 100%; - min-height: 0; - padding: 3px 0 0 3px; -} - -.terminal-container .xterm-screen, -.session-panel .xterm-screen { - height: 100%; -} - -.terminal-container .xterm-viewport, -.session-panel .xterm-viewport { - /* Allow xterm's own scrollbar to work */ - overflow-y: auto !important; -} - -.terminal-container .xterm-viewport::-webkit-scrollbar, -.session-panel .xterm-viewport::-webkit-scrollbar { - width: 6px; -} -.terminal-container .xterm-viewport::-webkit-scrollbar-track, -.session-panel .xterm-viewport::-webkit-scrollbar-track { - background: transparent; -} -.terminal-container .xterm-viewport::-webkit-scrollbar-thumb, -.session-panel .xterm-viewport::-webkit-scrollbar-thumb { - background: var(--border-strong); - border-radius: var(--radius-full); -} -.terminal-container .xterm-viewport::-webkit-scrollbar-thumb:hover, -.session-panel .xterm-viewport::-webkit-scrollbar-thumb:hover { - background: var(--text-tertiary); -} - -/* Ensure cursor blink is visible */ -.terminal-container .xterm-cursor-layer, -.session-panel .xterm-cursor-layer { - z-index: 1; -} - -/* Selection styling within terminal */ -.terminal-container .xterm .xterm-selection div, -.session-panel .xterm .xterm-selection div { - background: var(--accent-strong) !important; -} - -/* ----------------------------------------------------------------------- - Terminal search overlay - ----------------------------------------------------------------------- */ -.terminal-search { - position: absolute; - top: var(--space-2); - right: var(--space-4); - display: flex; - align-items: center; - gap: var(--space-1); - padding: var(--space-1); - background: var(--bg-overlay); - border: 1px solid var(--border-default); - border-radius: var(--radius-md); - box-shadow: var(--shadow-md); - z-index: 10; - opacity: 0; - visibility: hidden; - transform: translateY(-8px); - transition: - opacity var(--transition-fast), - visibility var(--transition-fast), - transform var(--transition-fast); -} - -.terminal-search.is-visible { - opacity: 1; - visibility: visible; - transform: translateY(0); -} - -.terminal-search input { - width: 200px; - height: 24px; - padding: 0 var(--space-2); - font-size: var(--text-sm); - font-family: var(--font-mono); - color: var(--text-primary); - background: var(--bg-input); - border: 1px solid var(--border-subtle); - border-radius: var(--radius-sm); -} - -.terminal-search input:focus { - border-color: var(--accent); - outline: none; -} - -.terminal-search-count { - font-size: var(--text-xs); - color: var(--text-tertiary); - padding: 0 var(--space-1); - white-space: nowrap; - min-width: 70px; -} - -.terminal-search-toggle { - font-family: var(--font-mono); - font-size: var(--text-xs); - opacity: 0.5; - transition: - opacity var(--transition-fast), - color var(--transition-fast); -} - -.terminal-search-toggle.is-active { - opacity: 1; - color: var(--accent); -} - -/* ----------------------------------------------------------------------- - RAW mode containers - ----------------------------------------------------------------------- */ - -/* RAW text view — scrollable, monospace output */ -.raw-text-view { - flex: 1; - overflow-y: auto; - padding: var(--space-3); - font-family: var(--font-mono); - font-size: var(--text-sm); - line-height: var(--leading-relaxed); - color: var(--text-primary); - background: var(--bg-base); - white-space: pre-wrap; - word-break: break-all; -} - -/* RAW hex view */ -.raw-hex-view { - flex: 1; - overflow-y: auto; - padding: var(--space-3); - font-family: var(--font-mono); - font-size: var(--text-xs); - line-height: var(--leading-relaxed); - color: var(--text-primary); - background: var(--bg-base); -} - -.raw-hex-row { - display: flex; - gap: var(--space-4); -} - -.raw-hex-offset { - color: var(--text-tertiary); - min-width: 60px; -} - -.raw-hex-bytes { - min-width: 350px; - letter-spacing: 0.08em; -} - -.raw-hex-bytes .byte-highlight { - color: var(--accent); -} - -.raw-hex-ascii { - color: var(--text-secondary); -} - -.raw-hex-ascii .non-printable { - color: var(--text-disabled); -} - -/* RAW split view (text on left, hex on right) */ -.raw-split-view { - flex: 1; - display: flex; - overflow: hidden; -} - -.raw-split-view .raw-text-view, -.raw-split-view .raw-hex-view { - flex: 1; -} - -.raw-split-divider { - width: 1px; - background: var(--border-subtle); - flex-shrink: 0; -} - -/* ----------------------------------------------------------------------- - RAW mode view toggle buttons - ----------------------------------------------------------------------- */ -.raw-view-toggle { - display: flex; - gap: 1px; - background: var(--border-subtle); - border-radius: var(--radius-md); - overflow: hidden; -} - -.raw-view-toggle-btn { - padding: 0 var(--space-2); - height: 22px; - font-size: var(--text-xs); - font-weight: var(--weight-medium); - color: var(--text-tertiary); - background: var(--bg-surface); - border: none; - cursor: pointer; - transition: - background var(--transition-fast), - color var(--transition-fast); -} - -.raw-view-toggle-btn:hover { - color: var(--text-secondary); -} - -.raw-view-toggle-btn.active { - background: var(--bg-active); - color: var(--text-primary); -} - -/* ----------------------------------------------------------------------- - Send bar (RAW TCP mode — manual data entry) - ----------------------------------------------------------------------- */ -.send-bar { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-3); - background: var(--bg-surface); - border-top: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.send-bar-input { - flex: 1; - height: 28px; - padding: 0 var(--space-3); - font-family: var(--font-mono); - font-size: var(--text-sm); - color: var(--text-primary); - background: var(--bg-input); - border: 1px solid var(--border-default); - border-radius: var(--radius-md); - transition: border-color var(--transition-fast); -} - -.send-bar-input:focus { - border-color: var(--accent); - outline: none; -} - -.send-bar-input::placeholder { - color: var(--text-disabled); -} - -/* Line ending selector */ -.send-bar-eol { - height: 28px; - padding: 0 var(--space-2); - font-size: var(--text-xs); - color: var(--text-secondary); - background: var(--bg-input); - border: 1px solid var(--border-default); - border-radius: var(--radius-md); - appearance: none; - cursor: pointer; - min-width: 60px; -} - -.send-bar-eol:focus { - border-color: var(--accent); - outline: none; -} - -/* Encoding selector */ -.send-bar-encoding { - font-size: var(--text-xs); - color: var(--text-tertiary); - padding: 0 var(--space-1); -} - -/* ----------------------------------------------------------------------- - Session overlay (connecting / error states) - ----------------------------------------------------------------------- */ -.session-overlay { - position: absolute; - inset: 0; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - background: rgba(28, 28, 28, 0.85); - z-index: 5; -} - -.session-overlay-connecting { - text-align: center; -} - -.session-overlay-connecting .spinner { - width: 24px; - height: 24px; - margin: 0 auto var(--space-4); -} - -.session-overlay-connecting p { - font-size: var(--text-sm); - color: var(--text-secondary); -} - -.session-overlay-error { - text-align: center; - max-width: 360px; -} - -.session-overlay-error h3 { - font-size: var(--text-md); - font-weight: var(--weight-semibold); - color: var(--error); - margin-bottom: var(--space-2); -} - -.session-overlay-error p { - font-size: var(--text-sm); - color: var(--text-tertiary); - line-height: var(--leading-relaxed); - margin-bottom: var(--space-4); -} - -.session-overlay-error .session-error-detail { - font-family: var(--font-mono); - font-size: var(--text-xs); - color: var(--text-disabled); - background: var(--bg-input); - padding: var(--space-2) var(--space-3); - border-radius: var(--radius-md); - margin-bottom: var(--space-4); - text-align: left; - max-height: 120px; - overflow-y: auto; - word-break: break-all; -} - -.session-overlay-actions { - display: flex; - gap: var(--space-2); -} diff --git a/frontend/src/css/tokens.css b/frontend/src/css/tokens.css deleted file mode 100644 index 287de7f..0000000 --- a/frontend/src/css/tokens.css +++ /dev/null @@ -1,159 +0,0 @@ -/* ========================================================================== - Alchementary Link — Design Tokens - Central palette, spacing, typography, and layout constants. - All values consumed via var(--token-name) throughout the application. - ========================================================================== */ - -:root { - /* ----------------------------------------------------------------------- - Colour palette — dark charcoal theme - ----------------------------------------------------------------------- */ - --bg-base: #1c1c1c; /* deepest background */ - --bg-surface: #232323; /* primary surface (sidebar, bars) */ - --bg-elevated: #2a2a2a; /* elevated panels, cards */ - --bg-overlay: #303030; /* overlays, dropdowns */ - --bg-hover: #363636; /* hover state backgrounds */ - --bg-active: #3c3c3c; /* active / pressed state */ - --bg-input: #1e1e1e; /* input field backgrounds */ - - /* ----------------------------------------------------------------------- - Border colours - ----------------------------------------------------------------------- */ - --border-subtle: rgba(255, 255, 255, 0.07); - --border-default: rgba(255, 255, 255, 0.12); - --border-strong: rgba(255, 255, 255, 0.18); - --border-focus: var(--accent); - - /* ----------------------------------------------------------------------- - Text colours - ----------------------------------------------------------------------- */ - --text-primary: #e4e4e4; - --text-secondary: #a0a0a0; - --text-tertiary: #707070; - --text-disabled: #4a4a4a; - --text-inverse: #1c1c1c; - --sidebar-group-title: #b89d5f; - - /* ----------------------------------------------------------------------- - Accent colours — teal / cyan - ----------------------------------------------------------------------- */ - --accent: #4ecdc4; - --accent-hover: #45b7af; - --accent-muted: rgba(78, 205, 196, 0.12); - --accent-strong: rgba(78, 205, 196, 0.22); - - /* ----------------------------------------------------------------------- - Status colours - ----------------------------------------------------------------------- */ - --success: #66bb6a; - --warning: #ffa726; - --error: #ef5350; - --info: #42a5f5; - - /* ----------------------------------------------------------------------- - Protocol badge colours - ----------------------------------------------------------------------- */ - --protocol-ssh: #4ecdc4; - --protocol-telnet: #ffa726; - --protocol-raw: #ab47bc; - --protocol-github: #7c8aff; - --protocol-vnc: #e85d4a; - --protocol-irc: #d8b24c; - --protocol-local: #8bc34a; - - /* ----------------------------------------------------------------------- - Spacing scale (4 px base unit) - ----------------------------------------------------------------------- */ - --space-1: 4px; - --space-2: 8px; - --space-3: 12px; - --space-4: 16px; - --space-5: 20px; - --space-6: 24px; - --space-8: 32px; - --space-10: 40px; - --space-12: 48px; - --space-16: 64px; - - /* ----------------------------------------------------------------------- - Typography - ----------------------------------------------------------------------- */ - --font-sans: - "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, - sans-serif; - --font-mono: - "JetBrains Mono", "Fira Code", "SF Mono", Monaco, "Cascadia Code", monospace; - - --text-xs: 11px; - --text-sm: 12px; - --text-base: 13px; - --text-md: 14px; - --text-lg: 16px; - --text-xl: 18px; - --text-2xl: 22px; - - --leading-tight: 1.25; - --leading-normal: 1.5; - --leading-relaxed: 1.625; - - --weight-normal: 400; - --weight-medium: 500; - --weight-semibold: 600; - - /* ----------------------------------------------------------------------- - Border radius - ----------------------------------------------------------------------- */ - --radius-sm: 3px; - --radius-md: 5px; - --radius-lg: 8px; - --radius-xl: 12px; - --radius-full: 9999px; - - /* ----------------------------------------------------------------------- - Shadows - ----------------------------------------------------------------------- */ - --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); - --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4); - --shadow-lg: 0 4px 16px rgba(0, 0, 0, 0.5); - --shadow-xl: 0 8px 32px rgba(0, 0, 0, 0.6); - - /* ----------------------------------------------------------------------- - Transitions - ----------------------------------------------------------------------- */ - --transition-fast: 100ms ease; - --transition-normal: 200ms ease; - --transition-slow: 300ms ease; - - /* ----------------------------------------------------------------------- - Z-index layers - ----------------------------------------------------------------------- */ - --z-base: 0; - --z-sticky: 100; - --z-dropdown: 200; - --z-modal-backdrop: 300; - --z-modal: 400; - --z-notification: 500; - --z-tooltip: 600; - - /* ----------------------------------------------------------------------- - Layout - ----------------------------------------------------------------------- */ - --sidebar-width: 260px; - --sidebar-width-collapsed: 48px; - --tab-bar-height: 44px; - --toolbar-height: 40px; - --status-bar-height: 24px; -} - -/* ----------------------------------------------------------------------- - Global input overrides — disable macOS/WebKit auto-capitalisation, - autocorrect, and spellcheck on all text fields. - ----------------------------------------------------------------------- */ -input, -textarea, -select { - autocapitalize: none; - autocorrect: off; - spellcheck: false; - text-transform: none; -} diff --git a/frontend/src/css/vnc.css b/frontend/src/css/vnc.css deleted file mode 100644 index 64016aa..0000000 --- a/frontend/src/css/vnc.css +++ /dev/null @@ -1,78 +0,0 @@ -/* VNC viewer styles */ - -.vnc-viewer-panel { - display: flex; - flex-direction: column; - height: 100%; - overflow: hidden; - background: var(--bg-base); -} - -.vnc-toolbar { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.375rem 0.75rem; - background: var(--bg-elevated); - border-bottom: 1px solid var(--border-subtle); - flex-shrink: 0; -} - -.vnc-status { - font-size: 0.75rem; - color: var(--text-secondary); - margin-right: auto; -} - -.vnc-status-connected { - color: var(--protocol-ssh); -} - -.vnc-status-error { - color: var(--danger); -} - -.btn-vnc-action { - font-size: 0.7rem; - padding: 0.2rem 0.5rem; - border-radius: var(--radius-sm); - background: var(--bg-surface); - border: 1px solid var(--border-subtle); - color: var(--text-secondary); - cursor: pointer; - transition: - background 0.15s, - color 0.15s; -} - -.btn-vnc-action:hover { - background: var(--bg-hover); - color: var(--text-primary); -} - -.vnc-display { - flex: 1; - position: relative; - overflow: hidden; - background: #000; -} - -.vnc-display canvas { - width: 100% !important; - height: 100% !important; - object-fit: contain; -} - -/* Protocol colour for VNC */ -.proto-vnc { - color: var(--protocol-vnc); -} - -.badge-vnc { - background: var(--protocol-vnc); - color: #fff; -} - -.sidebar-item-indicator--vnc { - background: var(--protocol-vnc); -} diff --git a/frontend/src/js/app.js b/frontend/src/js/app.js deleted file mode 100644 index 5f758a4..0000000 --- a/frontend/src/js/app.js +++ /dev/null @@ -1,1958 +0,0 @@ -(function () { - /** - * Alchementary Link — main application entry point. - * Initialises the application shell, loads profiles, and sets up event listeners. - */ - - const { - State, - Sidebar, - TabManager, - TerminalManager, - ProfileEditor, - RawViewer, - Notifications, - Settings, - CommandPalette, - SecretPrompt, - SplitPaneManager, - RecordingManager, - ExportImportUI, - SFTPBrowser, - SnippetLibrary, - SSHKeyManager, - TriggerEngine, - LeviathanWorkbench, - stateLabel, - } = window.AlchementaryLink; - - class App { - constructor() { - this.state = new State(); - this.notifications = new Notifications(); - this.sidebar = new Sidebar(this); - this.tabs = new TabManager(this); - this.terminals = new TerminalManager(this); - this.profileEditor = new ProfileEditor(this); - this.rawViewer = new RawViewer(this); - window.AlchementaryLink.IRCViewer?.setApp(this); - this.settings = new Settings(this); - this.commandPalette = new CommandPalette(this); - this.secretPrompt = new SecretPrompt(this); - this.splitPanes = new SplitPaneManager(this); - this.recordings = new RecordingManager(this); - this.exportImport = new ExportImportUI(this); - this.sftp = new SFTPBrowser(this); - this.snippets = new SnippetLibrary(this); - this.sshKeys = new SSHKeyManager(this); - this.triggers = new TriggerEngine(this); - this.leviathan = new LeviathanWorkbench(this); - /** @type {Map>} Idempotent in-flight session closes. */ - this._closingSessions = new Map(); - this._latencySessionID = null; - this._latencyTimer = null; - this._latencyGeneration = 0; - this._latencyPingInFlight = false; - this.updateInfo = null; - this.mode = "link"; - } - - /** - * Bootstraps the application after the DOM is ready. - * UI event binding happens synchronously so buttons always work. - * Backend data loading waits for the Wails runtime to be available. - */ - async init() { - // Phase 1 — bind all UI event handlers immediately (DOM-only, no backend). - this.setupUI(); - this.setupKeyboard(); - this.updateStatusBar(); - - // Phase 2 — wait for the Wails runtime, then load backend data and bind events. - await this._waitForWailsRuntime(); - this.setupEvents(); - - await this.loadProfiles(); - await this.loadPreferences(); - await this.checkSecretStore(); - this.updateStatusBar(); - this.checkForUpdates(true); - } - - /** - * Polls for the Wails runtime and generated binding bridge. - * Application code uses wailsService as a small compatibility layer over - * the normal Wails backend global. - * @returns {Promise} - * @private - */ - _waitForWailsRuntime() { - return new Promise((resolve) => { - const maxWait = 5000; - const start = Date.now(); - const check = () => { - const hasBridge = - typeof window.AlchementaryLink?.hasWailsService === "function" - ? window.AlchementaryLink.hasWailsService() - : Boolean(window.AlchementaryLink?.wailsService && window.runtime); - if (window.AlchementaryLink?.wailsService && hasBridge) { - resolve(); - } else if (Date.now() - start < maxWait) { - setTimeout(check, 50); - } else { - console.warn( - "Wails runtime not detected after " + - maxWait + - "ms — proceeding anyway", - ); - resolve(); - } - }; - Promise.resolve(window.AlchementaryLink?.wailsServiceReady) - .catch(() => null) - .finally(check); - }); - } - - // ── Data loading ───────────────────────────────────────────────── - - /** - * Fetches all connection profiles from the backend and renders the sidebar. - */ - async loadProfiles() { - try { - const profiles = await window.AlchementaryLink.wailsService.ListProfiles(); - this.state.setProfiles(profiles || []); - this.sidebar.render(); - } catch (err) { - this.notifications.error("Failed to load profiles: " + err); - } - } - - /** - * Loads user preferences from the backend. - */ - async loadPreferences() { - try { - const prefs = await window.AlchementaryLink.wailsService.GetPreferences(); - this.state.setPreferences(prefs || {}); - this._applyPreferences(); - } catch (err) { - // Non-fatal — fall back to defaults - console.warn("Failed to load preferences:", err); - } - } - - /** - * Queries the secret store status (initialised / unlocked). - */ - async checkSecretStore() { - try { - const initialised = - await window.AlchementaryLink.wailsService.IsSecretStoreInitialised(); - this.state.setSecretStoreInitialised(!!initialised); - if (initialised) { - const unlocked = await window.AlchementaryLink.wailsService.IsSecretStoreUnlocked(); - this.state.setSecretStoreState(!!unlocked); - } else { - this.state.setSecretStoreState(false); - } - } catch (_) { - // Secret store may not be available yet - } - } - - // ── Wails event listeners ──────────────────────────────────────── - - /** - * Registers listeners for backend events emitted via the Wails runtime. - * Guarded: silently skips if the runtime is unavailable. - */ - setupEvents() { - if (!window.runtime || typeof window.runtime.EventsOn !== "function") { - console.warn( - "window.runtime not available — Wails event listeners not bound", - ); - return; - } - // Incoming session data (terminal output) - window.runtime.EventsOn("session:data", (payload) => { - this.terminals.handleData(payload.sessionID, payload.data); - this.tabs.markActivity(payload.sessionID); - // Check trigger rules against decoded text - try { - const raw = atob(payload.data); - this.triggers.check(payload.sessionID, raw); - } catch (_) { - /* noop */ - } - }); - - // Session state transitions (connecting → connected → reconnecting → disconnected) - window.runtime.EventsOn("session:state", (payload) => { - this.tabs.updateSessionState( - payload.sessionID, - payload.state, - payload.error, - ); - this.updateStatusBar(); - - const session = this.state.getSession(payload.sessionID); - const isLocal = session?.protocol === "local"; - const localExited = - isLocal && payload.state === "disconnected"; - const showDisconnectedOverlay = - !localExited && - (payload.state === "disconnected" || payload.state === "error"); - this.terminals.setDisconnectedOverlay( - payload.sessionID, - showDisconnectedOverlay, - payload.error, - ); - if (localExited) { - this.terminals.setLocalExited(payload.sessionID, true); - } else if (isLocal && payload.state === "connected") { - this.terminals.setLocalExited(payload.sessionID, false); - } - - if (payload.state === "error" && payload.error) { - this._handleSessionError(payload.sessionID, payload.error); - } - - // Show reconnecting notification - if (payload.state === "reconnecting") { - const attempt = payload.attempt || 1; - const maxRetries = payload.maxRetries || 5; - const delaySec = payload.delaySec || 1; - this.notifications.info( - `Reconnecting (attempt ${attempt}/${maxRetries}) in ${delaySec}s…`, - ); - } - - // Once connected, ensure the backend PTY has the correct dimensions. - // The initial fit happens before the SSH handshake completes, so the - // onResize callback fires when the backend isn't ready. By the time - // the connection is established, xterm already has the right cols/rows - // and fitAddon.fit() won't trigger a new resize event. We must - // explicitly send the current terminal size to the backend. - if (payload.state === "connected") { - const entry = this.terminals.terminals.get(payload.sessionID); - if (entry) { - const { cols, rows } = entry.term; - try { - window.AlchementaryLink.wailsService.ResizeSession( - payload.sessionID, - cols, - rows, - ); - } catch (_) { - /* noop */ - } - } - // Also re-fit to catch any layout changes that occurred while connecting. - // Multiple delays help byobu/tmux render correctly after its status - // bar initialisation completes. - const fit = () => this.terminals.fitSession(payload.sessionID); - setTimeout(fit, 50); - setTimeout(fit, 300); - setTimeout(fit, 800); - setTimeout(() => window.AlchementaryLink.IRCViewer?.fit(payload.sessionID), 50); - } - }); - - // Session closed by backend - window.runtime.EventsOn("session:closed", (payload) => { - this.sftp.close(payload.sessionID); - this.triggers.cleanup(payload.sessionID); - this.tabs.handleSessionClosed(payload.sessionID); - this.updateStatusBar(); - }); - - // RAW TCP data (separate channel for the raw viewer) - window.runtime.EventsOn("session:raw:data", (payload) => { - this.rawViewer.handleData(payload); - }); - - // GitHub repository data - window.runtime.EventsOn("session:github:repos", (payload) => { - window.AlchementaryLink.GitHubViewer.updateRepos( - payload.sessionID, - payload.login, - payload.repos, - ); - }); - - // GitHub refresh errors - window.runtime.EventsOn("session:github:error", (payload) => { - window.AlchementaryLink.GitHubViewer.showError( - payload.sessionID, - payload.error, - ); - }); - - // GitHub status updates - window.runtime.EventsOn("session:github:status", (payload) => { - window.AlchementaryLink.GitHubViewer.setStatus( - payload.sessionID, - payload.status, - ); - }); - - // VNC WebSocket proxy ready — connect the viewer - window.runtime.EventsOn("session:vnc:ready", (payload) => { - window.AlchementaryLink.VNCViewer.connect( - payload.sessionID, - payload.wsURL, - ); - }); - - // IRC events - window.runtime.EventsOn("session:irc:event", (payload) => { - window.AlchementaryLink.IRCViewer.handleEvent(payload); - this.tabs.markActivity(payload.sessionID); - }); - - // App-wide commands from the native menu - window.runtime.EventsOn("app:open-about", () => { - this.settings.openTab("about"); - }); - - // Port-forwarding events - window.runtime.EventsOn("session:forward:started", (payload) => { - const fwd = payload.forward || {}; - const label = - fwd.label || - `${fwd.bindHost || ""}:${fwd.bindPort} → ${fwd.destHost}:${fwd.destPort}`; - this.notifications.success(`Forward started: ${label}`); - }); - - window.runtime.EventsOn("session:forward:stopped", (payload) => { - this.notifications.info(`Forward stopped: ${payload.forwardID}`); - }); - - window.runtime.EventsOn("session:forward:error", (payload) => { - this.notifications.error(`Forward error: ${payload.error}`); - }); - - // Recording events - window.runtime.EventsOn("session:recording:started", (payload) => { - this.recordings.activeSessions.add(payload.sessionID); - this.recordings._updateRecordingIndicator(payload.sessionID, true); - }); - - window.runtime.EventsOn("session:recording:stopped", (payload) => { - this.recordings.activeSessions.delete(payload.sessionID); - this.recordings._updateRecordingIndicator(payload.sessionID, false); - }); - - // Telnet negotiation debug messages are intentionally ignored in release UI. - window.runtime.EventsOn("session:debug", () => {}); - - // Backend ready event (emitted from DomReady) - window.runtime.EventsOn("backend:ready", () => { - // Lifecycle signal only. - }); - - // Host key verification requests - window.runtime.EventsOn("session:hostkey:verify", async (payload) => { - try { - const accepted = await this.secretPrompt.promptHostKeyVerification( - payload.host, - payload.keyType, - payload.fingerprint, - ); - window.runtime.EventsEmit("session:hostkey:response", { - requestID: payload.requestID, - sessionID: payload.sessionID, - accepted, - }); - } catch (_) { - window.runtime.EventsEmit("session:hostkey:response", { - requestID: payload.requestID, - sessionID: payload.sessionID, - accepted: false, - }); - } - }); - - // SFTP transfer progress events - window.runtime.EventsOn("session:sftp:progress", (payload) => { - this.sftp.handleTransferProgress(payload); - }); - - // Password/passphrase prompt requests from the backend - window.runtime.EventsOn("session:secret:prompt", async (payload) => { - try { - const value = await this.secretPrompt.prompt( - payload.title || "Credentials Required", - payload.message || "Enter your password or passphrase.", - ); - window.runtime.EventsEmit("session:secret:response", { - requestID: payload.requestID, - value, - }); - } catch (_) { - window.runtime.EventsEmit("session:secret:response", { - requestID: payload.requestID, - value: "", - cancelled: true, - }); - } - }); - } - - /** - * Disconnects and removes a session without confirmation prompts. - * @param {string} sessionId - */ - closeSessionImmediate(sessionId, options = {}) { - const existing = this._closingSessions.get(sessionId); - if (existing) return existing; - - let resolveOperation; - let rejectOperation; - const operation = new Promise((resolve, reject) => { - resolveOperation = resolve; - rejectOperation = reject; - }); - this._closingSessions.set(sessionId, operation); - - try { - this._closeSessionResources(sessionId, options).then( - resolveOperation, - rejectOperation, - ); - } catch (err) { - rejectOperation(err); - } - - const clearClosing = () => { - if (this._closingSessions.get(sessionId) === operation) { - this._closingSessions.delete(sessionId); - } - }; - operation.then(clearClosing, clearClosing); - return operation; - } - - /** - * Releases every frontend and backend resource owned by a session. - * Local objects are dropped before awaiting the backend so a slow close - * cannot leave renderers, observers or viewers running invisibly. - * @param {string} sessionId - * @param {{removeTab?: boolean, cleanupSplits?: boolean}} options - * @private - */ - async _closeSessionResources(sessionId, options = {}) { - const removeTab = options.removeTab !== false; - const cleanupSplits = options.cleanupSplits !== false; - - let splitCleanup = Promise.resolve(); - if (cleanupSplits) { - try { - splitCleanup = Promise.resolve(this.splitPanes.cleanup(sessionId)); - } catch (_) { - // Continue with the rest of the idempotent local cleanup. - } - } - - const localCleanups = [ - () => this.sftp.close(sessionId), - () => this.triggers.cleanup(sessionId), - () => this.recordings.cleanup(sessionId), - () => this.terminals.destroyTerminal(sessionId), - () => this.rawViewer.destroyViewer(sessionId), - () => window.AlchementaryLink.GitHubViewer?.destroyViewer(sessionId), - () => window.AlchementaryLink.VNCViewer?.destroyViewer(sessionId), - () => window.AlchementaryLink.IRCViewer?.destroyViewer(sessionId), - () => this.leviathan?.destroyPanel(sessionId), - ]; - for (const cleanup of localCleanups) { - try { - cleanup(); - } catch (_) { - // One faulty viewer must not prevent the remaining resources closing. - } - } - - if (removeTab && this.tabs.order.includes(sessionId)) { - this.tabs.removeTab(sessionId); - } - if (this.state.getSession(sessionId)) { - this.state.removeSession(sessionId); - } - this.updateStatusBar(); - - let backendCleanup = Promise.resolve(); - try { - backendCleanup = Promise.resolve( - window.AlchementaryLink.wailsService.DisconnectSession(sessionId), - ); - } catch (_) { - // Ignore failures; all local resources have already been released. - } - - await Promise.allSettled([splitCleanup, backendCleanup]); - } - - // ── Keyboard shortcuts ─────────────────────────────────────────── - - /** - * Registers global keyboard shortcuts. - */ - setupKeyboard() { - document.addEventListener("keydown", (e) => { - const mod = e.ctrlKey || e.metaKey; - - // Ctrl/Cmd+K — command palette - if (mod && e.key === "k") { - e.preventDefault(); - this.commandPalette.toggle(); - return; - } - - // Ctrl/Cmd+N — new connection - if (mod && e.key === "n") { - e.preventDefault(); - this.profileEditor.openNew(); - return; - } - - // Ctrl/Cmd+W — close active tab - if (mod && e.key === "w") { - e.preventDefault(); - this.tabs.closeActive(); - return; - } - - // Ctrl/Cmd+Tab — next tab - if (mod && e.key === "Tab" && !e.shiftKey) { - e.preventDefault(); - const next = this.tabs.getNextTab(); - if (next) this.tabs.setActive(next); - return; - } - - // Ctrl/Cmd+Shift+Tab — previous tab - if (mod && e.key === "Tab" && e.shiftKey) { - e.preventDefault(); - const prev = this.tabs.getPrevTab(); - if (prev) this.tabs.setActive(prev); - return; - } - - // Ctrl/Cmd+1-9 — switch to tab by index - if (mod && e.key >= "1" && e.key <= "9") { - e.preventDefault(); - const idx = parseInt(e.key, 10) - 1; - const order = this.tabs.getTabOrder(); - if (idx < order.length) { - this.tabs.setActive(order[idx]); - } - return; - } - - // Ctrl/Cmd+, — settings - if (mod && e.key === ",") { - e.preventDefault(); - this.settings.open(); - return; - } - - // Ctrl/Cmd+F — find in terminal - if (mod && e.key === "f") { - e.preventDefault(); - this._openTerminalSearch(); - return; - } - - // F11 — toggle fullscreen (Windows/Linux convention) - if (e.key === "F11") { - e.preventDefault(); - if ( - window.runtime && - typeof window.runtime.WindowFullscreen === "function" - ) { - window.runtime.WindowFullscreen(); - } - return; - } - - // Escape — close any open overlays - if (e.key === "Escape") { - this.commandPalette.close(); - // Also exit fullscreen if active (Windows/Linux) - if ( - window.runtime && - typeof window.runtime.WindowUnfullscreen === "function" - ) { - window.runtime.WindowUnfullscreen(); - } - } - - // Ctrl/Cmd+Shift+D — split pane vertically (left/right) - if (mod && e.shiftKey && e.key === "D") { - e.preventDefault(); - this.splitPane("vertical"); - return; - } - - // Ctrl/Cmd+Shift+E — split pane horizontally (top/bottom) - if (mod && e.shiftKey && e.key === "E") { - e.preventDefault(); - this.splitPane("horizontal"); - return; - } - - // Ctrl/Cmd+Shift+R — toggle recording on active session - if (mod && e.shiftKey && e.key === "R") { - e.preventDefault(); - if (this.state.activeSessionId) { - this.recordings.toggle(this.state.activeSessionId); - } - return; - } - - // Ctrl/Cmd+Shift+B — toggle broadcast mode - if (mod && e.shiftKey && e.key === "B") { - e.preventDefault(); - this.toggleBroadcast(); - return; - } - - // Ctrl/Cmd+Shift+S — open snippet library - if (mod && e.shiftKey && e.key === "S") { - e.preventDefault(); - this.snippets.open(); - return; - } - }); - } - - // ── UI event wiring ────────────────────────────────────────────── - - /** - * Binds click handlers to static UI elements. - */ - setupUI() { - var btnEl = document.getElementById("btn-new-connection"); - this._bindClick("btn-new-connection", () => this.profileEditor.openNew()); - this._bindClick("btn-command-palette", () => - this.commandPalette.toggle(), - ); - this._bindClick("btn-settings", () => this.settings.open()); - this._bindClick("btn-import", () => this.importProfiles()); - this._bindClick("btn-export", () => this.exportProfiles()); - this._bindClick("btn-recordings", () => this.recordings.openBrowser()); - this._bindClick("btn-new-tab", () => this.profileEditor.openNew()); - this._bindClick("btn-broadcast", () => this.toggleBroadcast()); - this._bindClick("mode-link", () => this.setMode("link")); - this._bindClick("mode-leviathan", () => this.setMode("leviathan")); - - // Wire the "Local Machine" sidebar button - this.sidebar.onConnectLocal = () => this.connectLocal(); - - // Right-click on broadcast button opens target selector - const broadcastBtn = document.getElementById("btn-broadcast"); - if (broadcastBtn) { - broadcastBtn.addEventListener("contextmenu", (e) => { - e.preventDefault(); - this.selectBroadcastTargets(); - }); - } - this._bindClick("btn-empty-connect", () => this.profileEditor.openNew()); - this._bindClick("btn-empty-quick", () => this.quickConnect()); - - const updateBtn = document.getElementById("status-update"); - if (updateBtn) { - updateBtn.addEventListener("mousedown", (e) => e.preventDefault()); - updateBtn.addEventListener("click", () => - this.settings.openTab("about"), - ); - } - - const searchInput = document.getElementById("profile-search"); - if (searchInput) { - searchInput.addEventListener("input", (e) => - this.sidebar.search(e.target.value), - ); - } - - // Window resize → re-fit active terminal (debounced) - const { debounce } = window.AlchementaryLink; - window.addEventListener( - "resize", - debounce(() => { - this.terminals.fitActive(); - this.splitPanes.refitAll(); - }, 100), - ); - - window.addEventListener("focus", () => { - requestAnimationFrame(() => { - this._focusVisibleTerminal(); - }); - }); - } - - // ── App mode switching ───────────────────────────────────────── - - /** - * Switches the sidebar between Link profiles and Leviathan tools. - * @param {"link"|"leviathan"} mode - */ - setMode(mode) { - const leviathanEnabled = - this.state.preferences?.leviathanEnabled === true; - this.mode = - mode === "leviathan" && leviathanEnabled ? "leviathan" : "link"; - const root = document.getElementById("app"); - if (root) { - root.classList.toggle("mode-leviathan", this.mode === "leviathan"); - } - document - .getElementById("mode-link") - ?.classList.toggle("is-active", this.mode === "link"); - document - .getElementById("mode-leviathan") - ?.classList.toggle("is-active", this.mode === "leviathan"); - - if (this.mode === "leviathan") { - this.leviathan.renderSidebar(); - this.leviathan.openTool("hex", { reuseExisting: true }); - } else { - this.sidebar.render(); - } - } - - // ── Split pane ───────────────────────────────────────────────── - - /** - * Splits the active session panel in the given direction. - * @param {'horizontal'|'vertical'} direction - */ - splitPane(direction) { - if (!this.state.activeSessionId) return; - if (direction === "horizontal") { - this.splitPanes.splitHorizontal(); - } else { - this.splitPanes.splitVertical(); - } - } - - // ── Broadcast mode ───────────────────────────────────────────── - - /** - * Toggles broadcast mode — when active, keystrokes are sent to all - * connected sessions simultaneously. - */ - toggleBroadcast() { - this.state.broadcastMode = !this.state.broadcastMode; - if (!this.state.broadcastMode) { - this.state.broadcastTargets = null; - } - const btn = document.getElementById("btn-broadcast"); - if (btn) { - btn.classList.toggle("is-active", this.state.broadcastMode); - btn.title = this.state.broadcastMode - ? "Broadcast Mode ON — keystrokes sent to all sessions (Ctrl+Shift+B)" - : "Toggle Broadcast Mode (Ctrl+Shift+B)"; - } - this.notifications.info( - this.state.broadcastMode - ? "Broadcast mode enabled — input sent to all sessions" - : "Broadcast mode disabled", - ); - } - - /** - * Opens a modal to select which sessions receive broadcast input. - */ - selectBroadcastTargets() { - const { createElement, escapeHtml } = window.AlchementaryLink; - const sessions = this.state - .getAllSessions() - .filter( - (s) => s.state === "connected" && s.id !== this.state.activeSessionId, - ); - - if (sessions.length === 0) { - this.notifications.error("No other connected sessions to broadcast to"); - return; - } - - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - const currentTargets = this.state.broadcastTargets || new Set(); - - let listHtml = ""; - for (const s of sessions) { - const profile = this.state.getProfile(s.profileId); - const label = profile - ? profile.displayName || profile.host - : s.id.substring(0, 8); - const checked = - currentTargets.size === 0 || currentTargets.has(s.id) - ? "checked" - : ""; - listHtml += ` - - `; - } - - modal.innerHTML = ` - - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = () => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - }; - - modal.querySelector(".modal-close").addEventListener("click", close); - modal.querySelector(".broadcast-cancel").addEventListener("click", close); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(); - }); - - modal.querySelector(".broadcast-all").addEventListener("click", () => { - modal.querySelectorAll(".broadcast-target-check").forEach((cb) => { - cb.checked = true; - }); - }); - - modal.querySelector(".broadcast-apply").addEventListener("click", () => { - const checks = modal.querySelectorAll(".broadcast-target-check"); - const selected = new Set(); - checks.forEach((cb) => { - if (cb.checked) selected.add(cb.dataset.sessionId); - }); - - if (selected.size === 0) { - this.notifications.error("Select at least one target session"); - return; - } - - // If all are selected, use null (broadcast to all) - this.state.broadcastTargets = - selected.size === sessions.length ? null : selected; - if (!this.state.broadcastMode) { - this.toggleBroadcast(); - } else { - const targetLabel = - selected.size === sessions.length - ? "all sessions" - : selected.size + " sessions"; - this.notifications.info("Broadcasting to " + targetLabel); - } - close(); - }); - } - - // ── Connection lifecycle ───────────────────────────────────────── - - /** - * Opens a new session for the given profile ID. - * @param {string} profileId - */ - async connectProfile(profileId, options = {}) { - const profile = this.state.getProfile(profileId); - if (!profile) { - this.notifications.error("Profile not found"); - return; - } - - const existingSessions = this.state.getAllSessions().filter((session) => { - if (session.profileId !== profileId) return false; - return ( - session.state === "connected" || - session.state === "connecting" || - session.state === "reconnecting" - ); - }); - if (existingSessions.length > 0) { - const preferred = - existingSessions.find((s) => s.id === this.state.activeSessionId) || - existingSessions[0]; - const decision = await this._confirmReuseSession(profile, preferred); - if (decision === "existing") { - this.tabs.setActive(preferred.id); - if ( - preferred.protocol !== "raw" && - preferred.protocol !== "github" && - preferred.protocol !== "vnc" && - preferred.protocol !== "irc" - ) { - this.terminals.focusTerminal(preferred.id); - } - return; - } - if (decision !== "new") return; - } - - if ( - !options.skipDirectVNCWarning && - this._requiresDirectVNCWarning(profile) - ) { - const ok = await this.confirmDirectVNCProfile(profile, "connect"); - if (!ok) return; - } - - try { - const sessionInfo = - await window.AlchementaryLink.wailsService.ConnectSession(profileId); - this.state.addSession(sessionInfo); - this.tabs.addTab(sessionInfo); - - // Create the appropriate viewer - const panelId = `panel-${sessionInfo.id}`; - if (profile.protocol === "raw") { - this.rawViewer.createViewer(sessionInfo.id, panelId, profile); - } else if (profile.protocol === "github") { - const container = document.getElementById(panelId); - if (container) { - window.AlchementaryLink.GitHubViewer.createViewer( - sessionInfo.id, - container, - profile.displayName || profile.host, - ); - } - } else if (profile.protocol === "vnc") { - const container = document.getElementById(panelId); - if (container) { - window.AlchementaryLink.VNCViewer.createViewer( - sessionInfo.id, - container, - profile, - ); - } - } else if (profile.protocol === "irc") { - const container = document.getElementById(panelId); - if (container) { - window.AlchementaryLink.IRCViewer.createViewer( - sessionInfo.id, - container, - profile, - ); - } - } else { - this.terminals.createTerminal(sessionInfo.id, panelId, profile); - } - - this.notifications.info( - `Connecting to ${profile.displayName || profile.host}…`, - ); - } catch (err) { - this.notifications.error("Connection failed: " + err); - } - } - - /** - * Opens a local terminal session. - */ - async connectLocal() { - try { - const sessionInfo = await window.AlchementaryLink.wailsService.ConnectLocal(); - this.state.addSession(sessionInfo); - this.tabs.addTab(sessionInfo); - - const panelId = `panel-${sessionInfo.id}`; - const prefs = this.state.preferences || {}; - this.terminals.createTerminal(sessionInfo.id, panelId, { - terminalFontFamily: prefs.defaultFontFamily, - terminalFontSize: prefs.defaultFontSize, - terminalScrollback: prefs.defaultScrollback, - terminalCursorStyle: prefs.defaultCursorStyle, - }); - - this.notifications.info("Opening local terminal…"); - } catch (err) { - this.notifications.error("Local terminal failed: " + err); - } - } - - /** - * Opens a quick connect dialog for ad-hoc connections without saving a profile. - */ - async quickConnect() { - const createElement = window.AlchementaryLink.createElement; - const result = await new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const protocolSelect = modal.querySelector(".qc-protocol"); - const portInput = modal.querySelector(".qc-port"); - const usernameGroup = modal.querySelector(".qc-username-group"); - const nickGroup = modal.querySelector(".qc-nick-group"); - const hostInput = modal.querySelector(".qc-host"); - - // Update port default and username visibility when protocol changes - protocolSelect.addEventListener("change", () => { - const proto = protocolSelect.value; - const defaultPorts = { ssh: 22, telnet: 23, raw: 23, irc: 6667 }; - portInput.value = defaultPorts[proto] || 22; - usernameGroup.style.display = - proto === "ssh" || proto === "irc" ? "" : "none"; - nickGroup.style.display = proto === "irc" ? "" : "none"; - }); - - const close = (value) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(value); - }; - - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(null)); - modal - .querySelector(".qc-cancel") - .addEventListener("click", () => close(null)); - modal.querySelector(".qc-connect").addEventListener("click", () => { - const host = hostInput.value.trim(); - if (!host) { - hostInput.focus(); - return; - } - close({ - protocol: protocolSelect.value, - host, - port: parseInt(portInput.value, 10) || 22, - username: modal.querySelector(".qc-username").value.trim(), - nick: modal.querySelector(".qc-nick").value.trim(), - }); - }); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(null); - }); - hostInput.addEventListener("keydown", (e) => { - if (e.key === "Enter") modal.querySelector(".qc-connect").click(); - if (e.key === "Escape") close(null); - }); - - setTimeout(() => hostInput.focus(), 50); - }); - - if (!result) return; - - try { - const profile = { - protocol: result.protocol, - host: result.host, - port: result.port, - username: result.username || "", - displayName: result.host, - tags: [], - }; - if (result.protocol === "irc") { - profile.irc = { - nick: result.nick || "link", - realName: "Alchementary Link", - autoJoinRooms: [], - }; - } - const sessionInfo = await window.AlchementaryLink.wailsService.ConnectQuick(profile); - this.state.addSession(sessionInfo); - this.tabs.addTab(sessionInfo); - - const panelId = `panel-${sessionInfo.id}`; - if (result.protocol === "raw") { - this.rawViewer.createViewer(sessionInfo.id, panelId, { - protocol: "raw", - raw: {}, - }); - } else if (result.protocol === "irc") { - const container = document.getElementById(panelId); - if (container) { - window.AlchementaryLink.IRCViewer.createViewer( - sessionInfo.id, - container, - profile, - ); - } - } else { - this.terminals.createTerminal(sessionInfo.id, panelId, { - protocol: result.protocol, - }); - } - - this.notifications.info(`Connecting to ${result.host}…`); - } catch (err) { - this.notifications.error("Quick connect failed: " + err); - } - } - - /** - * Prompts to reuse an existing session for a profile. - * @param {Object} profile - * @param {Object} session - * @returns {Promise<'existing'|'new'|null>} - * @private - */ - _confirmReuseSession(profile, session) { - const createElement = window.AlchementaryLink.createElement; - const label = window.AlchementaryLink.escapeHtml( - (profile.displayName || profile.host || "Untitled").trim(), - ); - const stateLabel = - session.state === "connected" ? "connected" : "connecting"; - - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = (val) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(val); - }; - - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(null)); - modal - .querySelector(".confirm-cancel") - .addEventListener("click", () => close(null)); - modal - .querySelector(".confirm-ok") - .addEventListener("click", () => close("existing")); - modal - .querySelector(".confirm-new") - .addEventListener("click", () => close("new")); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(null); - }); - }); - } - - /** - * Reports whether a profile connects directly to VNC without SSH tunnelling. - * @param {Object} profile - * @returns {boolean} - * @private - */ - _requiresDirectVNCWarning(profile) { - return !!( - profile && - profile.protocol === "vnc" && - profile.vnc && - !profile.vnc.sshTunnel - ); - } - - /** - * Warns before saving or connecting direct VNC profiles. - * @param {Object} profile - * @param {"save"|"connect"} action - * @returns {Promise} - */ - confirmDirectVNCProfile(profile, action) { - const { createElement, createModalShell } = window.AlchementaryLink; - const label = ( - profile.displayName || - profile.vnc?.vncHost || - profile.host || - "VNC" - ).trim(); - const actionLabel = action === "connect" ? "Connect" : "Save"; - - return new Promise((resolve) => { - const shell = createModalShell("Direct VNC Connection", "modal-sm"); - const bodyText = createElement("p", "", { - textContent: - label + - " is configured without an SSH tunnel. VNC traffic may be visible to the network unless the server is otherwise protected.", - }); - bodyText.style.fontSize = "var(--text-sm)"; - bodyText.style.color = "var(--text-secondary)"; - shell.body.appendChild(bodyText); - - const cancelBtn = createElement("button", "btn btn-sm", { - textContent: "Cancel", - }); - const okBtn = createElement( - "button", - "btn btn-sm btn-warning direct-vnc-confirm", - { textContent: actionLabel }, - ); - shell.footer.appendChild(cancelBtn); - shell.footer.appendChild(okBtn); - - const close = (value) => { - shell.remove(); - resolve(value); - }; - - shell.closeButton.addEventListener("click", () => close(false)); - cancelBtn.addEventListener("click", () => close(false)); - okBtn.addEventListener("click", () => close(true)); - shell.backdrop.addEventListener("click", (e) => { - if (e.target === shell.backdrop) close(false); - }); - shell.show(); - }); - } - - // ── Import / Export ────────────────────────────────────────────── - - /** - * Triggers the encrypted profile import dialog. - */ - async importProfiles() { - this.exportImport.showImportModal(); - } - - /** - * Triggers the encrypted profile export dialog. - */ - async exportProfiles() { - this.exportImport.showExportModal(); - } - - // ── Status bar ─────────────────────────────────────────────────── - - /** - * Updates the status bar with current session info. - */ - updateStatusBar() { - const connEl = document.getElementById("status-connection"); - const countEl = document.getElementById("status-session-count"); - const latencyEl = document.getElementById("status-latency"); - const vaultEl = document.getElementById("status-vault"); - const updateEl = document.getElementById("status-update"); - if (!connEl) return; - - const sessions = this.state.getAllSessions(); - const connected = sessions.filter((s) => s.state === "connected").length; - const total = sessions.length; - - const activeSession = this.state.activeSessionId - ? this.state.getSession(this.state.activeSessionId) - : null; - - if (activeSession) { - connEl.innerHTML = ` ${stateLabel(activeSession.state)}`; - } else { - connEl.innerHTML = - ' No active session'; - } - - // Latency indicator — only show for connected SSH sessions. - if (latencyEl) { - if ( - activeSession && - activeSession.state === "connected" && - activeSession.protocol === "ssh" - ) { - this._startLatencyPing(activeSession.id); - } else { - this._stopLatencyPing(); - latencyEl.textContent = ""; - } - } - - if (countEl) countEl.textContent = `${connected}/${total} sessions`; - if (vaultEl) { - vaultEl.textContent = this.state.secretStoreUnlocked - ? "🔓 Unlocked" - : "🔒 Locked"; - } - - if (updateEl) { - if (this.updateInfo && this.updateInfo.available) { - const version = this.updateInfo.latestVersion - ? ` (${this.updateInfo.latestVersion})` - : ""; - updateEl.textContent = `Update available${version}`; - updateEl.classList.remove("is-hidden"); - } else { - updateEl.textContent = "Update available"; - updateEl.classList.add("is-hidden"); - } - } - } - - _startLatencyPing(sessionID) { - // Do not queue overlapping backend requests for the active session. - if ( - this._latencySessionID === sessionID && - (this._latencyTimer || this._latencyPingInFlight) - ) { - return; - } - this._stopLatencyPing(); - this._latencySessionID = sessionID; - const generation = this._latencyGeneration; - - const isCurrent = () => - this._latencyGeneration === generation && - this._latencySessionID === sessionID; - - const scheduleNext = () => { - if (!isCurrent() || this._latencyTimer) return; - this._latencyTimer = setTimeout(() => { - this._latencyTimer = null; - void doPing(); - }, 10000); - }; - - const doPing = async () => { - if (!isCurrent()) return; - if (!window.AlchementaryLink?.wailsService) { - scheduleNext(); - return; - } - - this._latencyPingInFlight = true; - let succeeded = false; - try { - const ms = await window.AlchementaryLink.wailsService.PingSession(sessionID); - if (!isCurrent()) return; - succeeded = true; - const el = document.getElementById("status-latency"); - if (el) { - el.textContent = ms + " ms"; - el.className = - ms < 100 - ? "latency-good" - : ms < 250 - ? "latency-warn" - : "latency-bad"; - } - } catch (_) { - if (!isCurrent()) return; - // Session may have disconnected — clear indicator. - const el = document.getElementById("status-latency"); - if (el) { - el.textContent = ""; - el.className = ""; - } - this._stopLatencyPing(); - } finally { - if (isCurrent()) { - this._latencyPingInFlight = false; - if (succeeded) scheduleNext(); - } - } - }; - - void doPing(); - } - - _stopLatencyPing() { - this._latencyGeneration = (this._latencyGeneration || 0) + 1; - if (this._latencyTimer) { - clearTimeout(this._latencyTimer); - this._latencyTimer = null; - } - this._latencyPingInFlight = false; - this._latencySessionID = null; - } - - async checkForUpdates(silent) { - if (!window.AlchementaryLink?.wailsService) { - return; - } - - try { - const info = await window.AlchementaryLink.wailsService.CheckForUpdates(); - this.setUpdateInfo(info); - if (!silent && info && info.available) { - this.notifications.info( - `Update available: ${info.latestVersion || "new version"}`, - ); - } - } catch (err) { - if (!silent) { - this.notifications.error("Update check failed: " + err); - } - } - } - - setUpdateInfo(info) { - this.updateInfo = info || null; - this.updateStatusBar(); - } - - // ── Internal helpers ───────────────────────────────────────────── - - /** - * Applies loaded preferences to the UI. - * @private - */ - _applyPreferences() { - const prefs = this.state.preferences; - - // Sidebar width (drives the grid column width). - // Use the CSS variable so the workspace never sits underneath the sidebar. - const root = document.documentElement; - const sidebar = document.getElementById("sidebar"); - const parsedSidebarWidth = Number(prefs.sidebarWidth); - if (Number.isFinite(parsedSidebarWidth) && parsedSidebarWidth > 0) { - const clampedWidth = Math.max( - 180, - Math.min(500, Math.round(parsedSidebarWidth)), - ); - root.style.setProperty("--sidebar-width", `${clampedWidth}px`); - } else { - root.style.removeProperty("--sidebar-width"); - } - if (sidebar) { - sidebar.style.width = ""; - } - - // Status bar visibility - const statusBar = document.getElementById("status-bar"); - if (statusBar) { - statusBar.style.display = - prefs.showStatusBar === false ? "none" : "flex"; - } - - // Leviathan is an opt-in mode, revealed from the About window. - const leviathanEnabled = prefs.leviathanEnabled === true; - const leviathanButton = document.getElementById("mode-leviathan"); - if (leviathanButton) { - leviathanButton.hidden = !leviathanEnabled; - } - if (!leviathanEnabled && this.mode === "leviathan") { - this.setMode("link"); - } - - // Refit any visible terminal after geometry changes. - requestAnimationFrame(() => { - this.terminals.fitActive(); - this.splitPanes.refitAll(); - }); - } - - /** - * Determines whether an error is a common authentication or host key issue. - * @param {string} message - * @returns {Object|null} - * @private - */ - _classifySessionError(message) { - const msg = (message || "").toLowerCase(); - - const authPatterns = [ - "unable to authenticate", - "authentication failed", - "permission denied", - "no supported methods", - ]; - const hostKeyPatterns = ["host key", "fingerprint"]; - - if (authPatterns.some((p) => msg.includes(p))) { - return { - type: "auth", - title: "Authentication Failed", - message: - "The server rejected the provided credentials. Check username, password, keys, and auth methods.", - actions: ["edit", "retry"], - }; - } - - if (hostKeyPatterns.some((p) => msg.includes(p))) { - return { - type: "hostkey", - title: "Host Key Verification Failed", - message: - "The server's host key could not be verified. Manage known hosts or update the profile's policy.", - actions: ["knownhosts", "edit", "retry"], - }; - } - - return null; - } - - /** - * Shows a guided action modal for common session errors. - * @param {string} sessionId - * @param {string} message - * @private - */ - _handleSessionError(sessionId, message) { - const info = this._classifySessionError(message); - if (!info) return; - - const session = this.state.getSession(sessionId); - const label = session?.tabName || session?.host || "Session"; - const body = `${info.message}\n\n${message}`; - this._showGuidedErrorModal( - info.title, - label, - body, - (action) => { - if (action === "edit" && session?.profileId) { - this.profileEditor.openEdit(session.profileId); - } - if (action === "retry") { - this.tabs._reconnect(sessionId); - } - if (action === "knownhosts") { - this.settings.openTab("knownhosts"); - } - }, - info.actions, - ); - } - - /** - * Builds and displays a modal with guided action buttons. - * @param {string} title - * @param {string} label - * @param {string} message - * @param {Function} onAction - * @param {Array} actions - * @private - */ - _showGuidedErrorModal(title, label, message, onAction, actions) { - const createElement = window.AlchementaryLink.createElement; - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - const safeLabel = window.AlchementaryLink.escapeHtml(label || "Session"); - - modal.innerHTML = ` - - - - `; - - const footer = modal.querySelector(".modal-footer"); - const addActionBtn = (label, action, primary) => { - const btn = createElement( - "button", - `btn btn-sm${primary ? " btn-primary" : ""}`, - { - textContent: label, - }, - ); - btn.addEventListener("click", () => close(action)); - footer.appendChild(btn); - }; - - addActionBtn("Close", null, false); - if (actions.includes("knownhosts")) - addActionBtn("Known Hosts", "knownhosts", false); - if (actions.includes("edit")) addActionBtn("Edit Profile", "edit", false); - if (actions.includes("retry")) addActionBtn("Retry", "retry", true); - - const close = (action) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - if (action) onAction(action); - }; - - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(null)); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(null); - }); - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - } - - /** - * Opens an inline search bar for the active terminal. - * @private - */ - _openTerminalSearch() { - const sessionId = this.state.activeSessionId; - if (!sessionId) return; - - const panel = document.getElementById(`panel-${sessionId}`); - if (!panel) return; - - // If search bar already exists, just focus the input - const existing = panel.querySelector(".terminal-search"); - if (existing) { - const input = existing.querySelector("input"); - if (input) input.focus(); - return; - } - - const bar = document.createElement("div"); - bar.className = "terminal-search"; - - const input = document.createElement("input"); - input.type = "text"; - input.placeholder = "Find in terminal…"; - input.className = "terminal-search-input"; - - const count = document.createElement("span"); - count.className = "terminal-search-count"; - count.textContent = ""; - - const prevBtn = document.createElement("button"); - prevBtn.className = "btn btn-xs btn-ghost"; - prevBtn.textContent = "▲"; - prevBtn.title = "Previous match (Shift+Enter)"; - - const nextBtn = document.createElement("button"); - nextBtn.className = "btn btn-xs btn-ghost"; - nextBtn.textContent = "▼"; - nextBtn.title = "Next match (Enter)"; - - const caseSensBtn = document.createElement("button"); - caseSensBtn.className = "btn btn-xs btn-ghost terminal-search-toggle"; - caseSensBtn.textContent = "Aa"; - caseSensBtn.title = "Toggle case sensitivity"; - let caseSensitive = false; - - const regexBtn = document.createElement("button"); - regexBtn.className = "btn btn-xs btn-ghost terminal-search-toggle"; - regexBtn.textContent = ".*"; - regexBtn.title = "Toggle regular expression"; - let useRegex = false; - - const closeBtn = document.createElement("button"); - closeBtn.className = "btn btn-xs btn-ghost"; - closeBtn.textContent = "✕"; - closeBtn.title = "Close (Escape)"; - - bar.appendChild(input); - bar.appendChild(count); - bar.appendChild(prevBtn); - bar.appendChild(nextBtn); - bar.appendChild(caseSensBtn); - bar.appendChild(regexBtn); - bar.appendChild(closeBtn); - - const searchOpts = () => ({ caseSensitive, regex: useRegex }); - - let matchIndex = 0; - let totalMatches = 0; - - const updateCount = (found) => { - if (!input.value) { - count.textContent = ""; - matchIndex = 0; - totalMatches = 0; - return; - } - if (found) { - matchIndex++; - // Count total matches by searching the full buffer - const entry = this.terminals.terminals.get(sessionId); - if (entry) { - const buffer = entry.term.buffer.active; - let total = 0; - const query = input.value; - for (let i = 0; i < buffer.length; i++) { - const line = buffer.getLine(i); - if (!line) continue; - const text = line.translateToString(true); - if (useRegex) { - try { - const re = new RegExp(query, caseSensitive ? "g" : "gi"); - const m = text.match(re); - if (m) total += m.length; - } catch (_) { - /* invalid regex */ - } - } else { - const searchText = caseSensitive ? text : text.toLowerCase(); - const searchQuery = caseSensitive ? query : query.toLowerCase(); - let pos = 0; - while ((pos = searchText.indexOf(searchQuery, pos)) !== -1) { - total++; - pos += searchQuery.length; - } - } - } - totalMatches = total; - if (matchIndex > totalMatches) matchIndex = 1; - count.textContent = matchIndex + " of " + totalMatches; - } else { - count.textContent = "Match found"; - } - } else { - matchIndex = 0; - totalMatches = 0; - count.textContent = "No matches"; - } - count.style.color = found ? "" : "var(--error)"; - }; - - const doSearch = () => { - const found = this.terminals.search( - sessionId, - input.value, - searchOpts(), - ); - updateCount(found); - }; - const doPrev = () => { - matchIndex = Math.max(0, matchIndex - 2); - const found = this.terminals.searchPrevious( - sessionId, - input.value, - searchOpts(), - ); - updateCount(found); - }; - - const closeSearch = () => { - // Clear search decorations - const entry = this.terminals.terminals.get(sessionId); - if (entry && entry.searchAddon) { - entry.searchAddon.clearDecorations(); - } - bar.classList.remove("is-visible"); - setTimeout(() => bar.remove(), 200); - this.terminals.focusTerminal(sessionId); - }; - - input.addEventListener("input", doSearch); - input.addEventListener("keydown", (e) => { - if (e.key === "Enter") { - e.preventDefault(); - e.shiftKey ? doPrev() : doSearch(); - } - if (e.key === "Escape") { - e.preventDefault(); - closeSearch(); - } - }); - - nextBtn.addEventListener("click", doSearch); - prevBtn.addEventListener("click", doPrev); - closeBtn.addEventListener("click", closeSearch); - - caseSensBtn.addEventListener("click", () => { - caseSensitive = !caseSensitive; - caseSensBtn.classList.toggle("is-active", caseSensitive); - doSearch(); - }); - - regexBtn.addEventListener("click", () => { - useRegex = !useRegex; - regexBtn.classList.toggle("is-active", useRegex); - doSearch(); - }); - - panel.appendChild(bar); - // Trigger the entrance animation - requestAnimationFrame(() => { - bar.classList.add("is-visible"); - input.focus(); - }); - } - - /** - * Safely binds a click handler to an element by ID (no-op if the element does not exist). - * @param {string} id - Element ID. - * @param {Function} handler - * @private - */ - _bindClick(id, handler) { - const el = document.getElementById(id); - if (el) { - el.addEventListener("click", (e) => { - try { - handler(e); - } catch (err) { - console.error("Click handler error for", id, err); - } - }); - } - } - - /** - * Focuses the currently visible xterm instance if one is active. - * @private - */ - _focusVisibleTerminal() { - const sessionId = this.state.activeSessionId; - if (!sessionId) return; - this.terminals.focusTerminal(sessionId); - } - - /** - * Shows a simple text-input prompt modal (replaces window.prompt which - * is unavailable in WKWebView). - * @param {string} title - Modal heading. - * @param {string} label - Label above the input. - * @param {string} [placeholder] - Placeholder text. - * @returns {Promise} Resolves with the value or null if cancelled. - * @private - */ - _inputPrompt(title, label, placeholder) { - const createElement = window.AlchementaryLink.createElement; - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => backdrop.classList.add("is-visible")); - - const input = modal.querySelector("input"); - const close = (value) => { - backdrop.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(value); - }; - - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(null)); - modal - .querySelector(".prompt-cancel") - .addEventListener("click", () => close(null)); - modal - .querySelector(".prompt-ok") - .addEventListener("click", () => close(input.value)); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(null); - }); - input.addEventListener("keydown", (e) => { - if (e.key === "Enter") close(input.value); - if (e.key === "Escape") close(null); - }); - - setTimeout(() => input.focus(), 50); - }); - } - } - - // ── Bootstrap ──────────────────────────────────────────────────────── - - window.AlchementaryLink.App = App; - - document.addEventListener("DOMContentLoaded", () => { - try { - const app = new App(); - app.init().catch((err) => { - console.error("App initialisation failed:", err); - }); - } catch (err) { - console.error("App constructor failed:", err); - } - }); -})(); diff --git a/frontend/src/js/commands.js b/frontend/src/js/commands.js deleted file mode 100644 index 0ad9a38..0000000 --- a/frontend/src/js/commands.js +++ /dev/null @@ -1,446 +0,0 @@ -(function () { - /** - * Alchementary Link — command palette (Ctrl+K). - * Provides a searchable overlay for quick access to application commands, - * profile connections, and utility actions. - */ - - const { createElement, escapeHtml, fuzzyMatch, $ } = window.AlchementaryLink; - - class CommandPalette { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - - /** @type {HTMLElement|null} */ - this._overlay = null; - - /** @type {string} Current filter query. */ - this._query = ""; - - /** @type {number} Index of the highlighted command. */ - this._selectedIndex = 0; - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Toggles the command palette open/closed. - */ - toggle() { - if (this._overlay) { - this.close(); - } else { - this.open(); - } - } - - /** - * Opens the command palette. - */ - open() { - if (this._overlay) return; - this._query = ""; - this._selectedIndex = 0; - this._render(); - } - - /** - * Closes the command palette. - */ - close() { - if (this._overlay) { - this._overlay.remove(); - this._overlay = null; - } - } - - // ── Command definitions ────────────────────────────────────────── - - /** - * Returns the full list of available commands. - * @returns {Array<{id: string, label: string, shortcut?: string, section?: string, action: Function}>} - */ - getCommands() { - const cmds = []; - - // Profile connections - const profiles = this.app.state.profiles || []; - for (const p of profiles) { - cmds.push({ - id: `connect-${p.id}`, - label: `Connect to ${p.displayName || p.host}`, - section: "Connections", - detail: `${p.protocol.toUpperCase()} — ${p.host}:${p.port}`, - action: () => this.app.connectProfile(p.id), - }); - } - - // New connections - cmds.push({ - id: "new-ssh", - label: "New SSH Connection", - section: "Actions", - shortcut: "⌘N", - action: () => this.app.profileEditor.openNew("ssh"), - }); - cmds.push({ - id: "new-telnet", - label: "New Telnet Connection", - section: "Actions", - action: () => this.app.profileEditor.openNew("telnet"), - }); - cmds.push({ - id: "new-raw", - label: "New RAW Connection", - section: "Actions", - action: () => this.app.profileEditor.openNew("raw"), - }); - - // Quick connect - cmds.push({ - id: "quick-connect", - label: "Quick Connect…", - section: "Actions", - action: () => this.app.quickConnect(), - }); - - // Local terminal - cmds.push({ - id: "local-terminal", - label: "Open Local Terminal", - section: "Actions", - action: () => this.app.connectLocal(), - }); - - // Tab actions - cmds.push({ - id: "close-tab", - label: "Close Tab", - section: "Tabs", - shortcut: "⌘W", - action: () => this.app.tabs.closeActive(), - }); - cmds.push({ - id: "next-tab", - label: "Next Tab", - section: "Tabs", - shortcut: "⌘Tab", - action: () => { - const next = this.app.tabs.getNextTab(); - if (next) this.app.tabs.setActive(next); - }, - }); - cmds.push({ - id: "prev-tab", - label: "Previous Tab", - section: "Tabs", - shortcut: "⌘⇧Tab", - action: () => { - const prev = this.app.tabs.getPrevTab(); - if (prev) this.app.tabs.setActive(prev); - }, - }); - - // Settings & utility - cmds.push({ - id: "settings", - label: "Settings", - section: "Application", - action: () => this.app.settings.open(), - }); - cmds.push({ - id: "import-profiles", - label: "Import Profiles", - section: "Application", - action: () => this.app.importProfiles(), - }); - cmds.push({ - id: "export-profiles", - label: "Export Profiles", - section: "Application", - action: () => this.app.exportProfiles(), - }); - - // Secret store - if (this.app.state.secretStoreUnlocked) { - cmds.push({ - id: "lock-secrets", - label: "Lock Secret Store", - section: "Security", - action: async () => { - try { - await window.AlchementaryLink.wailsService.LockSecretStore(); - this.app.state.setSecretStoreState(false); - this.app.notifications.info("Secret store locked"); - } catch (err) { - this.app.notifications.error("Failed to lock: " + err); - } - }, - }); - } else { - cmds.push({ - id: "unlock-secrets", - label: "Unlock Secret Store", - section: "Security", - action: async () => { - try { - const pw = - await this.app.secretPrompt.promptMasterPassword(false); - await window.AlchementaryLink.wailsService.UnlockSecretStore(pw); - this.app.state.setSecretStoreState(true); - this.app.notifications.success("Secret store unlocked"); - } catch (err) { - if (err.message !== "User cancelled") { - this.app.notifications.error("Unlock failed: " + err); - } - } - }, - }); - } - - // SSH Key Manager - cmds.push({ - id: "ssh-keys", - label: "SSH Key Manager", - section: "Security", - action: () => this.app.sshKeys.open(), - }); - - // Trigger Rules - cmds.push({ - id: "trigger-rules", - label: "Trigger Rules", - section: "Terminal", - action: () => this.app.triggers.open(), - }); - - // Terminal actions (only when a session is active) - if (this.app.state.activeSessionId) { - cmds.push({ - id: "split-vertical", - label: "Split Pane Right", - section: "Terminal", - shortcut: "⌘⇧D", - action: () => this.app.splitPane("vertical"), - }); - cmds.push({ - id: "split-horizontal", - label: "Split Pane Down", - section: "Terminal", - shortcut: "⌘⇧E", - action: () => this.app.splitPane("horizontal"), - }); - cmds.push({ - id: "clear-terminal", - label: "Clear Terminal", - section: "Terminal", - action: () => - this.app.terminals.clearTerminal(this.app.state.activeSessionId), - }); - cmds.push({ - id: "reset-terminal", - label: "Reset Terminal", - section: "Terminal", - action: () => - this.app.terminals.resetTerminal(this.app.state.activeSessionId), - }); - } - - return cmds; - } - - /** - * Returns commands filtered and sorted by the current query. - * @param {string} query - * @returns {Array} - */ - filter(query) { - if (!query) return this.getCommands(); - return this.getCommands() - .map((cmd) => { - const result = fuzzyMatch( - cmd.label + " " + (cmd.detail || ""), - query, - ); - return { ...cmd, ...result }; - }) - .filter((c) => c.match) - .sort((a, b) => b.score - a.score); - } - - /** - * Executes a command and closes the palette. - * @param {Object} command - */ - execute(command) { - this.close(); - if (command && typeof command.action === "function") { - command.action(); - } - } - - // ── Rendering ──────────────────────────────────────────────────── - - /** @private */ - _render() { - const overlay = createElement("div", "palette-overlay"); - const palette = createElement("div", "palette"); - - // Search input - const input = createElement("input", "palette-input", { - type: "text", - placeholder: "Type a command…", - autocomplete: "off", - spellcheck: "false", - }); - - palette.appendChild(input); - - // Results list - const list = createElement("div", "palette-list"); - palette.appendChild(list); - - overlay.appendChild(palette); - document.body.appendChild(overlay); - this._overlay = overlay; - - // Initial render - this._renderList(list, this.filter("")); - - // Focus the input - input.focus(); - - // Event handlers - input.addEventListener("input", () => { - this._query = input.value; - this._selectedIndex = 0; - this._renderList(list, this.filter(this._query)); - }); - - input.addEventListener("keydown", (e) => { - const items = list.querySelectorAll(".palette-item"); - switch (e.key) { - case "ArrowDown": - e.preventDefault(); - this._selectedIndex = Math.min( - this._selectedIndex + 1, - items.length - 1, - ); - this._highlightItem(items); - break; - case "ArrowUp": - e.preventDefault(); - this._selectedIndex = Math.max(this._selectedIndex - 1, 0); - this._highlightItem(items); - break; - case "Enter": { - e.preventDefault(); - const selected = items[this._selectedIndex]; - if (selected && selected._command) { - this.execute(selected._command); - } - break; - } - case "Escape": - e.preventDefault(); - this.close(); - break; - } - }); - - // Click outside to close - overlay.addEventListener("click", (e) => { - if (e.target === overlay) this.close(); - }); - } - - /** - * Renders the filtered command list. - * @param {HTMLElement} list - * @param {Array} commands - * @private - */ - _renderList(list, commands) { - list.innerHTML = ""; - let currentSection = null; - - if (commands.length === 0) { - const empty = createElement("div", "palette-empty", { - textContent: "No commands found", - }); - list.appendChild(empty); - return; - } - - for (let i = 0; i < commands.length; i++) { - const cmd = commands[i]; - - // Section header - if (cmd.section && cmd.section !== currentSection) { - currentSection = cmd.section; - const sectionEl = createElement("div", "palette-section", { - textContent: cmd.section, - }); - list.appendChild(sectionEl); - } - - const item = createElement( - "div", - `palette-item ${i === this._selectedIndex ? "palette-item-active" : ""}`, - ); - item._command = cmd; - - // Label - const label = createElement("span", "palette-item-label", { - textContent: cmd.label, - }); - item.appendChild(label); - - // Detail - if (cmd.detail) { - const detail = createElement("span", "palette-item-detail", { - textContent: cmd.detail, - }); - item.appendChild(detail); - } - - // Shortcut badge - if (cmd.shortcut) { - const badge = createElement("span", "palette-item-shortcut", { - textContent: cmd.shortcut, - }); - item.appendChild(badge); - } - - item.addEventListener("click", () => this.execute(cmd)); - item.addEventListener("mouseenter", () => { - this._selectedIndex = i; - this._highlightItem(list.querySelectorAll(".palette-item")); - }); - - list.appendChild(item); - } - } - - /** - * Highlights the currently selected item and scrolls it into view. - * @param {NodeListOf} items - * @private - */ - _highlightItem(items) { - items.forEach((el, i) => { - el.classList.toggle("palette-item-active", i === this._selectedIndex); - }); - const active = items[this._selectedIndex]; - if (active) { - active.scrollIntoView({ block: "nearest" }); - } - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.CommandPalette = CommandPalette; -})(); diff --git a/frontend/src/js/github.js b/frontend/src/js/github.js deleted file mode 100644 index 3a6c052..0000000 --- a/frontend/src/js/github.js +++ /dev/null @@ -1,497 +0,0 @@ -(function () { - /** - * Alchementary Link — GitHub repository viewer. - * Renders a scrollable list of repositories grouped by owner/organisation, - * sorted by last commit date (newest first). Automatically refreshes via - * Wails events emitted by the Go backend. - */ - - const { createElement, escapeHtml, relativeTime, $ } = window.AlchementaryLink; - - class GitHubViewer { - constructor() { - /** @type {Map} sessionID → viewer */ - this._viewers = new Map(); - } - - /** - * Creates a viewer panel for a GitHub session inside the given container. - * @param {string} sessionID - * @param {HTMLElement} container - The .session-panel element. - * @param {string} profileName - */ - createViewer(sessionID, container, profileName) { - if (this._viewers.has(sessionID)) return; - - const viewer = new GitHubViewerInstance( - sessionID, - container, - profileName, - ); - this._viewers.set(sessionID, viewer); - viewer.render(); - } - - /** - * Destroys a viewer for the given session. - * @param {string} sessionID - */ - destroyViewer(sessionID) { - const viewer = this._viewers.get(sessionID); - if (viewer) { - viewer.destroy(); - this._viewers.delete(sessionID); - } - } - - /** - * Handles incoming repo data from the backend event. - * @param {string} sessionID - * @param {string} login - The authenticated GitHub username. - * @param {Array} repos - */ - updateRepos(sessionID, login, repos) { - const viewer = this._viewers.get(sessionID); - if (viewer) { - viewer.setData(login, repos); - } - } - - /** - * Updates the status line for a session. - * @param {string} sessionID - * @param {string} message - */ - setStatus(sessionID, message) { - const viewer = this._viewers.get(sessionID); - if (viewer) { - viewer.setStatus(message); - } - } - - /** - * Handles an error event for a session. - * @param {string} sessionID - * @param {string} message - */ - showError(sessionID, message) { - const viewer = this._viewers.get(sessionID); - if (viewer) { - viewer.showError(message); - } - } - } - - class GitHubViewerInstance { - /** - * @param {string} sessionID - * @param {HTMLElement} container - * @param {string} profileName - */ - constructor(sessionID, container, profileName) { - this.sessionID = sessionID; - this.container = container; - this.profileName = profileName; - - /** @type {string} */ - this._login = ""; - - /** @type {Array} */ - this._repos = []; - - /** @type {string} */ - this._filter = ""; - - /** @type {HTMLElement|null} */ - this._el = null; - - /** @type {HTMLElement|null} */ - this._listEl = null; - - /** @type {HTMLElement|null} */ - this._statusEl = null; - - /** @type {number|null} */ - this._refreshTimer = null; - } - - render() { - this.container.innerHTML = ""; - - const wrapper = createElement("div", "github-viewer"); - - // ── Header bar ────────────────────────────────────────── - const header = createElement("div", "github-viewer-header"); - - const titleArea = createElement("div", "github-viewer-title-area"); - const icon = createElement("span", "github-viewer-icon"); - icon.innerHTML = this._githubSVG(); - titleArea.appendChild(icon); - - const titleText = createElement("span", "github-viewer-title"); - titleText.textContent = this.profileName || "GitHub"; - titleArea.appendChild(titleText); - header.appendChild(titleArea); - - // Search / filter - const searchWrap = createElement("div", "github-viewer-search"); - const searchInput = createElement("input", "github-viewer-search-input", { - type: "text", - placeholder: "Filter repositories…", - }); - searchInput.addEventListener("input", (e) => { - this._filter = e.target.value.toLowerCase(); - this._renderList(); - }); - searchWrap.appendChild(searchInput); - header.appendChild(searchWrap); - - // Refresh button - const refreshBtn = createElement("button", "github-viewer-refresh", { - title: "Refresh now", - }); - refreshBtn.innerHTML = "↻"; - refreshBtn.addEventListener("click", () => this._manualRefresh()); - header.appendChild(refreshBtn); - - wrapper.appendChild(header); - - // ── Status bar ────────────────────────────────────────── - this._statusEl = createElement("div", "github-viewer-status"); - this._statusEl.textContent = "Connecting…"; - wrapper.appendChild(this._statusEl); - - // ── Repo list ─────────────────────────────────────────── - this._listEl = createElement("div", "github-viewer-list"); - wrapper.appendChild(this._listEl); - - this.container.appendChild(wrapper); - this._el = wrapper; - } - - /** - * Updates the viewer with fresh data. - * @param {string} login - * @param {Array} repos - */ - setData(login, repos) { - this._login = login; - this._repos = repos || []; - this._updateStatus(); - this._renderList(); - } - - /** - * Updates the status line with a custom message. - * @param {string} message - */ - setStatus(message) { - if (!this._statusEl) return; - this._statusEl.classList.remove("github-viewer-status-error"); - this._statusEl.textContent = message || ""; - } - - /** - * Displays an error message in the status bar. - * @param {string} message - */ - showError(message) { - if (this._statusEl) { - this._statusEl.textContent = `⚠ ${message}`; - this._statusEl.classList.add("github-viewer-status-error"); - } - } - - destroy() { - if (this._refreshTimer) { - clearInterval(this._refreshTimer); - this._refreshTimer = null; - } - if (this._el) { - this._el.remove(); - this._el = null; - } - } - - // ── Private ────────────────────────────────────────────────── - - _updateStatus() { - if (!this._statusEl) return; - this._statusEl.classList.remove("github-viewer-status-error"); - - const count = this._repos.length; - const now = new Date().toLocaleTimeString(); - this._statusEl.textContent = `${this._login} · ${count} repositor${count === 1 ? "y" : "ies"} · updated ${now}`; - } - - _renderList() { - if (!this._listEl) return; - this._listEl.innerHTML = ""; - - // Filter - let filtered = this._repos; - if (this._filter) { - filtered = this._repos.filter( - (r) => - r.fullName.toLowerCase().includes(this._filter) || - (r.description || "").toLowerCase().includes(this._filter) || - (r.language || "").toLowerCase().includes(this._filter), - ); - } - - if (filtered.length === 0) { - const empty = createElement("div", "github-viewer-empty"); - empty.textContent = this._filter - ? "No matching repositories." - : "No repositories found."; - this._listEl.appendChild(empty); - return; - } - - const commitTimestamp = (repo) => { - const raw = repo.latestCommitAt || repo.pushedAt || repo.updatedAt || ""; - const ts = new Date(raw).getTime(); - return Number.isNaN(ts) ? 0 : ts; - }; - - // Group by owner - const groups = new Map(); - for (const repo of filtered) { - const owner = repo.owner || "unknown"; - if (!groups.has(owner)) groups.set(owner, []); - groups.get(owner).push(repo); - } - - // Sort repos within each group by latest commit (newest first) - for (const repos of groups.values()) { - repos.sort((a, b) => commitTimestamp(b) - commitTimestamp(a)); - } - - // Sort groups by most recent commit in each group (login first on ties) - const sortedKeys = [...groups.keys()].sort((a, b) => { - const aRepos = groups.get(a) || []; - const bRepos = groups.get(b) || []; - const aMax = aRepos.length > 0 ? commitTimestamp(aRepos[0]) : 0; - const bMax = bRepos.length > 0 ? commitTimestamp(bRepos[0]) : 0; - if (aMax !== bMax) return bMax - aMax; - if (a === this._login) return -1; - if (b === this._login) return 1; - return a.localeCompare(b); - }); - - for (const owner of sortedKeys) { - const repos = groups.get(owner); - - // Group header - const groupEl = createElement("div", "github-group"); - const groupHeader = createElement("div", "github-group-header"); - groupHeader.innerHTML = ` - ${escapeHtml(owner)} - ${repos.length} - `; - groupEl.appendChild(groupHeader); - - // Repo cards - for (const repo of repos) { - groupEl.appendChild(this._renderRepoCard(repo)); - } - - this._listEl.appendChild(groupEl); - } - } - - /** - * Renders a single repository card. - * @param {Object} repo - * @returns {HTMLElement} - */ - _renderRepoCard(repo) { - const card = createElement("div", "github-repo-card"); - card.addEventListener("click", () => { - if (repo.htmlURL) window.open(repo.htmlURL, "_blank"); - }); - - // Top row: name + badges - const topRow = createElement("div", "github-repo-top"); - const nameEl = createElement("span", "github-repo-name"); - nameEl.textContent = repo.name; - topRow.appendChild(nameEl); - - if (repo.private) { - const badge = createElement( - "span", - "github-repo-badge github-repo-private", - ); - badge.textContent = "Private"; - topRow.appendChild(badge); - } - if (repo.fork) { - const badge = createElement( - "span", - "github-repo-badge github-repo-fork", - ); - badge.textContent = "Fork"; - topRow.appendChild(badge); - } - if (repo.archived) { - const badge = createElement( - "span", - "github-repo-badge github-repo-archived", - ); - badge.textContent = "Archived"; - topRow.appendChild(badge); - } - - const actions = createElement("div", "github-repo-actions"); - - const downloadBtn = createElement("button", "github-repo-download", { - textContent: "ZIP", - title: "Download repository as zip", - }); - downloadBtn.addEventListener("click", (e) => { - e.stopPropagation(); - this._downloadRepo(repo); - }); - actions.appendChild(downloadBtn); - - const visitLink = createElement("button", "github-repo-visit", { - textContent: "Visit", - title: "Open repository in browser", - }); - visitLink.addEventListener("click", (e) => { - e.stopPropagation(); - if (repo.htmlURL) { - if (window.runtime && window.runtime.BrowserOpenURL) { - window.runtime.BrowserOpenURL(repo.htmlURL); - } else { - window.open(repo.htmlURL, "_blank"); - } - } - }); - actions.appendChild(visitLink); - - topRow.appendChild(actions); - - card.appendChild(topRow); - - if (repo.latestCommitter && repo.latestCommitAt) { - const commitLine = createElement("div", "github-repo-commit"); - const when = new Date(repo.latestCommitAt); - const whenText = isNaN(when.getTime()) - ? repo.latestCommitAt - : when.toLocaleString(); - commitLine.textContent = `Last commit: ${whenText} by ${repo.latestCommitter}`; - card.appendChild(commitLine); - } - - // Description - if (repo.description) { - const desc = createElement("div", "github-repo-desc"); - desc.textContent = repo.description; - card.appendChild(desc); - } - - // Meta row: language, stars, pushed - const meta = createElement("div", "github-repo-meta"); - - if (repo.language) { - const lang = createElement("span", "github-repo-lang"); - lang.innerHTML = ` ${escapeHtml(repo.language)}`; - meta.appendChild(lang); - } - - if (repo.stars > 0) { - const stars = createElement("span", "github-repo-stars"); - stars.textContent = `★ ${repo.stars.toLocaleString()}`; - meta.appendChild(stars); - } - - if (repo.pushedAt) { - const pushed = createElement("span", "github-repo-pushed"); - pushed.textContent = relativeTime(repo.pushedAt); - pushed.title = new Date(repo.pushedAt).toLocaleString(); - meta.appendChild(pushed); - } - - card.appendChild(meta); - - return card; - } - - async _downloadRepo(repo) { - if (!this._statusEl) return; - const ref = repo.defaultBranch || ""; - this._statusEl.classList.remove("github-viewer-status-error"); - this._statusEl.textContent = `Downloading ${repo.fullName}...`; - - try { - await window.AlchementaryLink.wailsService.DownloadGitHubRepoArchive( - this.sessionID, - repo.fullName, - ref, - ); - this._updateStatus(); - } catch (err) { - this._statusEl.textContent = `⚠ Download failed: ${err}`; - this._statusEl.classList.add("github-viewer-status-error"); - } - } - - async _manualRefresh() { - if (!this._statusEl) return; - this._statusEl.textContent = "Refreshing…"; - try { - await window.AlchementaryLink.wailsService.RefreshGitHubRepos(this.sessionID); - } catch (err) { - this.showError(err.message || "Refresh failed"); - } - } - - /** - * Returns a colour for the given programming language. - * Uses a subset of GitHub's linguist colours. - * @param {string} lang - * @returns {string} CSS colour value. - */ - _langColour(lang) { - const colours = { - JavaScript: "#f1e05a", - TypeScript: "#3178c6", - Python: "#3572A5", - Go: "#00ADD8", - Rust: "#dea584", - Java: "#b07219", - C: "#555555", - "C++": "#f34b7d", - "C#": "#178600", - Ruby: "#701516", - PHP: "#4F5D95", - Swift: "#F05138", - Kotlin: "#A97BFF", - Shell: "#89e051", - HTML: "#e34c26", - CSS: "#563d7c", - Dart: "#00B4AB", - Lua: "#000080", - Scala: "#c22d40", - Elixir: "#6e4a7e", - Haskell: "#5e5086", - Zig: "#ec915c", - Vue: "#41b883", - SCSS: "#c6538c", - }; - return colours[lang] || "#8b949e"; - } - - /** - * GitHub Invertocat SVG icon. - * @returns {string} - */ - _githubSVG() { - return ''; - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.GitHubViewer = new GitHubViewer(); -})(); diff --git a/frontend/src/js/irc.js b/frontend/src/js/irc.js deleted file mode 100644 index 3532958..0000000 --- a/frontend/src/js/irc.js +++ /dev/null @@ -1,795 +0,0 @@ -(function () { - /** - * Alchementary Link - IRC viewer. - * Uses xterm.js as the chat stream, with room subtabs, a composer, and - * room/user sidebars. - */ - - /* global Terminal, FitAddon, SearchAddon, WebLinksAddon */ - - const { createElement } = window.AlchementaryLink; - const SERVER_TARGET = "server"; - const MAX_LINES_PER_TARGET = 2000; - const MAX_LISTED_ROOMS = 200; - const ANSI_RESET = "\x1b[0m"; - const ANSI_DIM = "\x1b[2m"; - const ANSI_BOLD = "\x1b[1m"; - const ANSI_ITALIC = "\x1b[3m"; - const ANSI_UNDERLINE = "\x1b[4m"; - const ANSI_REVERSE = "\x1b[7m"; - const ANSI_STRIKE = "\x1b[9m"; - const ANSI_GREY = "\x1b[38;5;244m"; - const ANSI_RED = "\x1b[38;5;203m"; - const ANSI_GREEN = "\x1b[38;5;83m"; - const ANSI_YELLOW = "\x1b[38;5;221m"; - const ANSI_CYAN = "\x1b[38;5;81m"; - const ANSI_MAGENTA = "\x1b[38;5;177m"; - const NICK_COLOURS = [33, 39, 45, 69, 75, 81, 111, 141, 177, 207, 214, 220]; - - // IRC colours 00-98 mapped to the xterm 256-colour palette described by - // the modern IRC formatting specification. Colour 99 means terminal default. - const IRC_ANSI_COLOURS = [ - 15, 0, 4, 2, 9, 1, 5, 130, 11, 10, 6, 14, 12, 13, 8, 7, - 52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89, - 88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125, - 124, 166, 184, 106, 34, 49, 37, 33, 19, 129, 127, 161, - 196, 208, 226, 154, 46, 86, 51, 75, 21, 171, 201, 198, - 203, 215, 227, 191, 83, 122, 87, 111, 63, 177, 207, 205, - 217, 223, 229, 193, 157, 158, 159, 153, 147, 183, 219, 212, - 16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231, - ]; - - function stripTerminalEscapes(value) { - return String(value || "") - .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "") - .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "") - .replace(/\x1b./g, "") - .replace(/[\x80-\x9f]/g, ""); - } - - function readDecimalColour(input, offset) { - const match = input.slice(offset).match(/^\d{1,2}/); - if (!match) return null; - return { value: Number(match[0]), length: match[0].length }; - } - - function readHexColour(input, offset) { - const value = input.slice(offset, offset + 6); - if (!/^[0-9a-f]{6}$/i.test(value)) return null; - return { value, length: 6 }; - } - - function indexedColour(code, background = false) { - if (code === 99 || code < 0 || code >= IRC_ANSI_COLOURS.length) { - return `\x1b[${background ? 49 : 39}m`; - } - return `\x1b[${background ? 48 : 38};5;${IRC_ANSI_COLOURS[code]}m`; - } - - function hexColour(value, background = false) { - const red = parseInt(value.slice(0, 2), 16); - const green = parseInt(value.slice(2, 4), 16); - const blue = parseInt(value.slice(4, 6), 16); - return `\x1b[${background ? 48 : 38};2;${red};${green};${blue}m`; - } - - function formatIRCText(value) { - const input = stripTerminalEscapes(value); - const toggles = { - "\x02": [ANSI_BOLD, "\x1b[22m", false], - "\x1d": [ANSI_ITALIC, "\x1b[23m", false], - "\x1e": [ANSI_STRIKE, "\x1b[29m", false], - "\x1f": [ANSI_UNDERLINE, "\x1b[24m", false], - "\x16": [ANSI_REVERSE, "\x1b[27m", false], - }; - let output = ""; - - for (let index = 0; index < input.length; index += 1) { - const char = input[index]; - if (toggles[char]) { - const toggle = toggles[char]; - toggle[2] = !toggle[2]; - output += toggle[toggle[2] ? 0 : 1]; - continue; - } - if (char === "\x0f") { - output += ANSI_RESET; - for (const toggle of Object.values(toggles)) toggle[2] = false; - continue; - } - if (char === "\x03" || char === "\x04") { - const readColour = char === "\x03" ? readDecimalColour : readHexColour; - const foreground = readColour(input, index + 1); - if (!foreground) { - output += "\x1b[39;49m"; - continue; - } - index += foreground.length; - output += - char === "\x03" - ? indexedColour(foreground.value) - : hexColour(foreground.value); - - if (input[index + 1] !== ",") continue; - const background = readColour(input, index + 2); - if (!background) { - output += ","; - index += 1; - continue; - } - index += background.length + 1; - output += - char === "\x03" - ? indexedColour(background.value, true) - : hexColour(background.value, true); - continue; - } - // xterm is already monospace, so the IRC monospace toggle needs no SGR. - if (char === "\x11") continue; - const code = char.charCodeAt(0); - if (code < 0x20 || code === 0x7f) continue; - output += char; - } - return output; - } - - function plainIRCText(value) { - return formatIRCText(value).replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, ""); - } - - function nickColour(nick) { - let hash = 0; - for (const char of String(nick || "")) { - hash = (hash * 31 + char.codePointAt(0)) >>> 0; - } - return `\x1b[38;5;${NICK_COLOURS[hash % NICK_COLOURS.length]}m`; - } - - function formatIRCEvent(event, time) { - const from = plainIRCText(event?.from || ""); - const text = formatIRCText(event?.text || ""); - const subject = plainIRCText(event?.command || ""); - const timestamp = `${ANSI_DIM}${ANSI_GREY}[${time}]${ANSI_RESET}`; - const styledNick = from - ? `${ANSI_BOLD}${nickColour(from)}${from}${ANSI_RESET}` - : ""; - let body; - - switch (event?.type) { - case "message": - body = `${ANSI_GREY}<${ANSI_RESET}${styledNick}${ANSI_GREY}>${ANSI_RESET} ${text}`; - break; - case "action": - body = `${ANSI_MAGENTA}${ANSI_ITALIC}* ${styledNick}${ANSI_MAGENTA}${ANSI_ITALIC} ${text}`; - break; - case "notice": - body = `${ANSI_YELLOW}-${styledNick || "notice"}${ANSI_YELLOW}-${ANSI_RESET} ${text}`; - break; - case "join": - body = `${ANSI_GREEN}-->${ANSI_RESET} ${styledNick || "user"} joined`; - break; - case "part": - body = `${ANSI_RED}<--${ANSI_RESET} ${styledNick || "user"} left${text ? ` (${text})` : ""}`; - break; - case "kick": - body = `${ANSI_RED}<--${ANSI_RESET} ${ANSI_BOLD}${subject || "user"}${ANSI_RESET} was kicked by ${styledNick || "server"}${text ? ` (${text})` : ""}`; - break; - case "quit": - body = `${ANSI_RED}<--${ANSI_RESET} ${styledNick || "user"} quit${text ? ` (${text})` : ""}`; - break; - case "nick": - body = `${styledNick || "user"} is now known as ${ANSI_BOLD}${plainIRCText(event?.text || "")}${ANSI_RESET}`; - break; - case "topic": - body = `${ANSI_CYAN}${ANSI_BOLD}topic:${ANSI_RESET} ${text}`; - break; - case "error": - body = `${ANSI_RED}${ANSI_BOLD}error:${ANSI_RESET} ${text}`; - break; - case "system": - default: - body = `${ANSI_CYAN}${text}`; - break; - } - return `${timestamp} ${body}${ANSI_RESET}`; - } - - class IRCViewer { - constructor() { - this._viewers = new Map(); - this.app = null; - } - - setApp(app) { - this.app = app; - } - - createViewer(sessionID, container, profile) { - if (this._viewers.has(sessionID) || !container) return; - const viewer = new IRCViewerInstance(sessionID, container, profile, this.app); - this._viewers.set(sessionID, viewer); - viewer.render(); - } - - destroyViewer(sessionID) { - const viewer = this._viewers.get(sessionID); - if (!viewer) return; - viewer.destroy(); - this._viewers.delete(sessionID); - } - - handleEvent(payload) { - const sessionID = payload?.sessionID; - const event = payload?.event || payload; - const viewer = this._viewers.get(sessionID); - if (viewer) viewer.handleEvent(event); - } - - fit(sessionID) { - const viewer = this._viewers.get(sessionID); - if (viewer) viewer.fit(); - } - } - - class IRCViewerInstance { - constructor(sessionID, container, profile, app) { - this.sessionID = sessionID; - this.container = container; - this.profile = profile || {}; - this.app = app || null; - - this.term = null; - this.fitAddon = null; - this.searchAddon = null; - this.observer = null; - - this.activeTarget = SERVER_TARGET; - this.targets = new Map(); - this.users = new Map(); - this.joinedRooms = []; - this.roomList = []; - this.roomFilter = ""; - this.unreadTargets = new Set(); - this.destroyed = false; - - this.tabsEl = null; - this.termHost = null; - this.inputEl = null; - this.roomsEl = null; - this.usersEl = null; - this.roomListEl = null; - this.roomFilterEl = null; - - this._ensureTarget(SERVER_TARGET, "Server"); - } - - render() { - this.container.innerHTML = ""; - this.container.classList.add("irc-viewer-panel"); - - const wrapper = createElement("div", "irc-viewer"); - const main = createElement("div", "irc-main"); - - this.tabsEl = createElement("div", "irc-room-tabs"); - main.appendChild(this.tabsEl); - - this.termHost = createElement("div", "irc-terminal"); - main.appendChild(this.termHost); - - const composer = createElement("form", "irc-composer"); - this.inputEl = createElement("input", "irc-input", { - type: "text", - placeholder: "Message or /command", - autocomplete: "off", - }); - const sendBtn = createElement("button", "btn btn-sm btn-primary irc-send", { - type: "submit", - textContent: "Send", - }); - composer.appendChild(this.inputEl); - composer.appendChild(sendBtn); - composer.addEventListener("submit", (event) => { - event.preventDefault(); - this._sendComposer(); - }); - this.inputEl.addEventListener("keydown", (event) => { - if (event.key === "Tab" && this._completeMention()) { - event.preventDefault(); - } - }); - main.appendChild(composer); - - const sidebar = this._buildSidebar(); - wrapper.appendChild(main); - wrapper.appendChild(sidebar); - this.container.appendChild(wrapper); - - this._openTerminal(); - this._renderTabs(); - this._renderRooms(); - this._renderUsers(); - this._renderRoomList(); - this._appendLine(SERVER_TARGET, { - type: "system", - text: "Connecting...", - timestamp: Date.now(), - }); - void this._refreshJoinedRooms(); - } - - handleEvent(event) { - if (!event || !event.type) return; - - if (event.type === "rooms") { - this.joinedRooms = Array.isArray(event.rooms) ? event.rooms : []; - for (const room of this.joinedRooms) { - this._ensureTarget(room.name, room.name); - } - this._renderTabs(); - this._renderRooms(); - return; - } - - if (event.type === "users") { - const target = event.target; - if (target) { - this.users.set(target, Array.isArray(event.users) ? event.users : []); - if (target === this.activeTarget) this._renderUsers(); - } - return; - } - - if (event.type === "room-list") { - this.roomList = Array.isArray(event.rooms) ? event.rooms : []; - this._renderRoomList(); - return; - } - - const target = this._eventTarget(event); - this._ensureTarget(target, this._targetLabel(target)); - this._appendLine(target, event); - - if (target !== this.activeTarget) { - this.unreadTargets.add(target); - this._renderTabs(); - } - - if (["join", "part", "kick", "topic"].includes(event.type)) { - this._renderRooms(); - } - } - - fit() { - if (!this.fitAddon) return; - requestAnimationFrame(() => { - try { - this.fitAddon.fit(); - } catch (_) { - /* noop */ - } - }); - } - - destroy() { - this.destroyed = true; - if (this.observer) { - this.observer.disconnect(); - this.observer = null; - } - if (this.term) { - this.term.dispose(); - this.term = null; - } - this.fitAddon = null; - this.searchAddon = null; - this.targets.clear(); - this.users.clear(); - this.unreadTargets.clear(); - this.joinedRooms = []; - this.roomList = []; - this.container.classList.remove("irc-viewer-panel"); - this.container.innerHTML = ""; - } - - _buildSidebar() { - const sidebar = createElement("aside", "irc-sidebar"); - - const joinSection = createElement("div", "irc-sidebar-section"); - joinSection.appendChild( - createElement("div", "irc-sidebar-title", { textContent: "Rooms" }), - ); - const joinRow = createElement("form", "irc-join-row"); - const joinInput = createElement("input", "irc-join-input", { - type: "text", - placeholder: "#room", - autocomplete: "off", - }); - const joinBtn = createElement("button", "btn btn-xs btn-secondary", { - type: "submit", - textContent: "Join", - }); - joinRow.appendChild(joinInput); - joinRow.appendChild(joinBtn); - joinRow.addEventListener("submit", async (event) => { - event.preventDefault(); - const room = joinInput.value.trim(); - if (!room) return; - try { - await window.AlchementaryLink.wailsService.IRCJoin(this.sessionID, room); - joinInput.value = ""; - } catch (err) { - this._notifyError("Join failed: " + err); - } - }); - joinSection.appendChild(joinRow); - this.roomsEl = createElement("div", "irc-sidebar-list"); - joinSection.appendChild(this.roomsEl); - sidebar.appendChild(joinSection); - - const listSection = createElement("div", "irc-sidebar-section"); - const listTitleRow = createElement("div", "irc-sidebar-title-row"); - listTitleRow.appendChild( - createElement("div", "irc-sidebar-title", { textContent: "Room List" }), - ); - const listBtn = createElement("button", "btn btn-xs btn-outline", { - type: "button", - textContent: "List", - }); - listBtn.addEventListener("click", async () => { - this.roomList = []; - this._renderRoomList("Loading rooms..."); - try { - await window.AlchementaryLink.wailsService.IRCListRooms(this.sessionID); - } catch (err) { - this._renderRoomList("Room list failed"); - this._notifyError("Room list failed: " + err); - } - }); - listTitleRow.appendChild(listBtn); - listSection.appendChild(listTitleRow); - this.roomFilterEl = createElement("input", "irc-filter-input", { - type: "text", - placeholder: "Filter rooms", - autocomplete: "off", - }); - this.roomFilterEl.addEventListener("input", () => { - this.roomFilter = this.roomFilterEl.value.toLowerCase(); - this._renderRoomList(); - }); - listSection.appendChild(this.roomFilterEl); - this.roomListEl = createElement("div", "irc-room-list"); - listSection.appendChild(this.roomListEl); - sidebar.appendChild(listSection); - - const usersSection = createElement("div", "irc-sidebar-section irc-users-section"); - usersSection.appendChild( - createElement("div", "irc-sidebar-title", { textContent: "Users" }), - ); - this.usersEl = createElement("div", "irc-user-list"); - usersSection.appendChild(this.usersEl); - sidebar.appendChild(usersSection); - - return sidebar; - } - - _openTerminal() { - const themeName = - (this.app?.state?.preferences || {}).terminalTheme || "charcoal"; - this.term = new Terminal({ - theme: window.AlchementaryLink.getTerminalTheme(themeName), - fontFamily: - this.profile.terminalFontFamily || - 'JetBrains Mono, Menlo, Monaco, Consolas, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", monospace', - fontSize: this.profile.terminalFontSize || 14, - scrollback: this.profile.terminalScrollback || 10000, - cursorBlink: false, - convertEol: true, - disableStdin: true, - }); - this.fitAddon = new FitAddon.FitAddon(); - this.searchAddon = new SearchAddon.SearchAddon(); - this.term.loadAddon(this.fitAddon); - this.term.loadAddon(this.searchAddon); - try { - this.term.loadAddon(new WebLinksAddon.WebLinksAddon((_event, uri) => { - window.runtime.BrowserOpenURL(uri); - })); - } catch (_) { - /* optional */ - } - this.term.open(this.termHost); - this.fit(); - this.observer = new ResizeObserver(() => this.fit()); - this.observer.observe(this.termHost); - } - - _renderTabs() { - if (!this.tabsEl) return; - this.tabsEl.innerHTML = ""; - for (const [target, state] of this.targets) { - const btn = createElement( - "button", - `irc-room-tab ${target === this.activeTarget ? "active" : ""} ${ - this.unreadTargets.has(target) ? "has-unread" : "" - }`, - { - type: "button", - textContent: state.label, - title: state.label, - }, - ); - btn.addEventListener("click", () => this._setActiveTarget(target)); - this.tabsEl.appendChild(btn); - } - } - - _renderRooms() { - if (!this.roomsEl) return; - this.roomsEl.innerHTML = ""; - if (this.joinedRooms.length === 0) { - this.roomsEl.appendChild( - createElement("div", "irc-empty", { textContent: "No joined rooms" }), - ); - return; - } - for (const room of this.joinedRooms) { - const row = createElement("button", "irc-room-row", { - type: "button", - title: plainIRCText(room.topic || room.name), - }); - row.classList.toggle("active", room.name === this.activeTarget); - row.appendChild(createElement("span", "irc-room-name", { textContent: room.name })); - row.appendChild( - createElement("span", "irc-room-count", { - textContent: String(room.users || 0), - }), - ); - row.addEventListener("click", () => this._setActiveTarget(room.name)); - this.roomsEl.appendChild(row); - } - } - - _renderUsers() { - if (!this.usersEl) return; - this.usersEl.innerHTML = ""; - const users = this.users.get(this.activeTarget) || []; - if (!this._isRoom(this.activeTarget)) { - this.usersEl.appendChild( - createElement("div", "irc-empty", { textContent: "No room selected" }), - ); - return; - } - if (users.length === 0) { - this.usersEl.appendChild( - createElement("div", "irc-empty", { textContent: "No users yet" }), - ); - return; - } - for (const user of users) { - const row = createElement("button", "irc-user-row", { - type: "button", - textContent: this._displayNick(user), - title: user, - }); - row.addEventListener("click", () => { - this._insertMention(this._displayNick(user)); - }); - this.usersEl.appendChild(row); - } - } - - _renderRoomList(message = "") { - if (!this.roomListEl) return; - this.roomListEl.innerHTML = ""; - if (message) { - this.roomListEl.appendChild( - createElement("div", "irc-empty", { textContent: message }), - ); - return; - } - let rooms = this.roomList; - if (this.roomFilter) { - rooms = rooms.filter((room) => { - const haystack = `${room.name || ""} ${room.topic || ""}`.toLowerCase(); - return haystack.includes(this.roomFilter); - }); - } - rooms = rooms.slice(0, MAX_LISTED_ROOMS); - if (rooms.length === 0) { - this.roomListEl.appendChild( - createElement("div", "irc-empty", { textContent: "No rooms loaded" }), - ); - return; - } - for (const room of rooms) { - const row = createElement("div", "irc-listed-room"); - const meta = createElement("button", "irc-listed-room-main", { - type: "button", - title: plainIRCText(room.topic || room.name), - }); - meta.appendChild( - createElement("span", "irc-listed-room-name", { - textContent: room.name || "", - }), - ); - meta.appendChild( - createElement("span", "irc-listed-room-topic", { - textContent: plainIRCText(room.topic || ""), - }), - ); - meta.addEventListener("click", () => this._joinRoom(room.name)); - const count = createElement("span", "irc-listed-room-count", { - textContent: String(room.users || 0), - }); - row.appendChild(meta); - row.appendChild(count); - this.roomListEl.appendChild(row); - } - } - - _setActiveTarget(target) { - this._ensureTarget(target, this._targetLabel(target)); - this.activeTarget = target; - this.unreadTargets.delete(target); - this._renderTabs(); - this._renderRooms(); - this._renderUsers(); - this._replayActiveTarget(); - if (this.inputEl) this.inputEl.focus(); - } - - _appendLine(target, event) { - const state = this._ensureTarget(target, this._targetLabel(target)); - const line = this._formatEvent(event); - if (!line) return; - state.lines.push(line); - if (state.lines.length > MAX_LINES_PER_TARGET) { - state.lines.splice(0, state.lines.length - MAX_LINES_PER_TARGET); - } - if (target === this.activeTarget && this.term) { - this.term.writeln(line); - this.term.scrollToBottom(); - } - } - - _replayActiveTarget() { - if (!this.term) return; - this.term.clear(); - const state = this.targets.get(this.activeTarget); - for (const line of state?.lines || []) { - this.term.writeln(line); - } - this.term.scrollToBottom(); - this.fit(); - } - - async _sendComposer() { - const text = this.inputEl.value; - if (!text.trim()) return; - this.inputEl.value = ""; - try { - await window.AlchementaryLink.wailsService.IRCHandleInput( - this.sessionID, - this.activeTarget, - text, - ); - } catch (err) { - this._notifyError(String(err)); - this._appendLine(this.activeTarget, { - type: "error", - text: String(err), - timestamp: Date.now(), - }); - } - } - - async _joinRoom(room) { - if (!room) return; - try { - await window.AlchementaryLink.wailsService.IRCJoin(this.sessionID, room); - } catch (err) { - this._notifyError("Join failed: " + err); - } - } - - async _refreshJoinedRooms() { - try { - const rooms = await window.AlchementaryLink.wailsService.IRCRooms(this.sessionID); - if (this.destroyed) return; - this.handleEvent({ type: "rooms", rooms: Array.isArray(rooms) ? rooms : [] }); - } catch (_) { - // The session may have closed while the viewer was being created. - } - } - - _completeMention() { - if (!this.inputEl || !this._isRoom(this.activeTarget)) return false; - const start = this.inputEl.selectionStart || 0; - const before = this.inputEl.value.slice(0, start); - const match = before.match(/(^|\s)@([^\s@]*)$/); - if (!match) return false; - const prefix = match[2].toLowerCase(); - const users = this.users.get(this.activeTarget) || []; - const nick = users - .map((user) => this._displayNick(user)) - .find((user) => user.toLowerCase().startsWith(prefix)); - if (!nick) return false; - const replacement = `${match[1]}@${nick} `; - const nextValue = - this.inputEl.value.slice(0, start - match[0].length) + - replacement + - this.inputEl.value.slice(start); - const caret = start - match[0].length + replacement.length; - this.inputEl.value = nextValue; - this.inputEl.setSelectionRange(caret, caret); - return true; - } - - _insertMention(nick) { - if (!this.inputEl) return; - const current = this.inputEl.value; - const spacer = current && !/\s$/.test(current) ? " " : ""; - this.inputEl.value = `${current}${spacer}@${nick} `; - this.inputEl.focus(); - } - - _ensureTarget(target, label) { - const safeTarget = target || SERVER_TARGET; - if (!this.targets.has(safeTarget)) { - this.targets.set(safeTarget, { - label: label || this._targetLabel(safeTarget), - lines: [], - }); - } - return this.targets.get(safeTarget); - } - - _eventTarget(event) { - if (!event) return SERVER_TARGET; - if (event.target) return event.target; - return SERVER_TARGET; - } - - _targetLabel(target) { - return target === SERVER_TARGET ? "Server" : target; - } - - _formatEvent(event) { - return formatIRCEvent(event, this._time(event.timestamp)); - } - - _time(value) { - const date = value ? new Date(value) : new Date(); - const hours = String(date.getHours()).padStart(2, "0"); - const mins = String(date.getMinutes()).padStart(2, "0"); - const secs = String(date.getSeconds()).padStart(2, "0"); - return `${hours}:${mins}:${secs}`; - } - - _sanitize(value) { - return plainIRCText(value); - } - - _displayNick(user) { - return String(user || "").replace(/^[~&@%+]+/, ""); - } - - _isRoom(target) { - return /^[#&+!]/.test(target || ""); - } - - _notifyError(message) { - if (this.app?.notifications) { - this.app.notifications.error(message); - } - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.IRCFormatting = Object.freeze({ - formatEvent: formatIRCEvent, - formatText: formatIRCText, - plainText: plainIRCText, - }); - window.AlchementaryLink.IRCViewer = new IRCViewer(); -})(); diff --git a/frontend/src/js/leviathan.js b/frontend/src/js/leviathan.js deleted file mode 100644 index 2525aa2..0000000 --- a/frontend/src/js/leviathan.js +++ /dev/null @@ -1,1441 +0,0 @@ -(function () { - const { createElement, escapeHtml, formatBytes, generateId } = - window.AlchementaryLink; - - const tools = [ - { - group: "Editor", - id: "hex", - name: "Hex Editor", - ico: "HX", - desc: "Editable hex view over the loaded working copy.", - }, - { - group: "Triage", - id: "overview", - name: "Overview", - ico: "OV", - desc: "First-pass summary across hashes, entropy, strings, IOCs and carve candidates.", - }, - { group: "Triage", id: "hashes", name: "Hashes", ico: "#", desc: "CRC32, MD5, SHA-1, SHA-256 and SHA-512." }, - { group: "Triage", id: "entropy", name: "Entropy", ico: "EN", desc: "Windowed entropy for packed or encrypted regions." }, - { group: "Triage", id: "strings", name: "Strings", ico: "ST", desc: "ASCII and UTF-16 string extraction." }, - { group: "Triage", id: "iocs", name: "IOCs", ico: "IO", desc: "URLs, domains, paths, hashes and other indicators." }, - { group: "Find", id: "search", name: "Search", ico: "SR", desc: "Single text, hex or regular-expression search." }, - { group: "Find", id: "multi-search", name: "Multi-search", ico: "MS", desc: "Batch text, hex and regex searches." }, - { group: "Structure", id: "parse", name: "Structures", ico: "PX", desc: "Parsers for PE, ELF, Mach-O, ZIP, images, RIFF, ASN.1 and certs." }, - { group: "Structure", id: "disasm", name: "Disasm", ico: "DA", desc: "Heuristic disassembly for common CPU families." }, - { group: "Structure", id: "pdf", name: "PDF", ico: "PDF", desc: "PDF object, stream, xref and suspicious-surface inspection." }, - { group: "Structure", id: "squashfs", name: "SquashFS", ico: "SQ", desc: "SquashFS superblock and directory metadata." }, - { group: "Audio", id: "spectrogram-encode", name: "Spectrogram Encode", ico: "SE", desc: "Convert an image into WAV audio, or hide an image inside a carrier WAV/MP3." }, - { group: "Audio", id: "spectrogram-view", name: "Spectrogram View", ico: "SV", desc: "Render WAV or MP3 audio as a spectrogram image." }, - { group: "Compare", id: "diff", name: "Diff", ico: "DF", desc: "Changed byte regions against a compare file." }, - { group: "Recover", id: "carve", name: "Carve", ico: "CV", desc: "Embedded file carving candidates." }, - { group: "Recover", id: "keys", name: "Keys", ico: "KY", desc: "PEM, DER, OpenSSH and PKCS#12 artefact discovery." }, - { group: "Recover", id: "timestamps", name: "Timestamps", ico: "TS", desc: "UNIX, FILETIME, WebKit and OLE timestamp heuristics." }, - { group: "Recover", id: "stego", name: "Stego", ico: "SG", desc: "Appended payloads and embedded trailer formats." }, - { group: "Recover", id: "metadata", name: "Metadata", ico: "MD", desc: "PNG, JPEG and GIF textual metadata extraction." }, - { group: "Advanced", id: "binsnoop", name: "Binsnoop", ico: "BS", desc: "Deep signature scanning through the Binsnoop engine." }, - { group: "Advanced", id: "rules", name: "Rules", ico: "RL", desc: "YARA-like rule scans over the current document." }, - { group: "Edit", id: "patch-plan", name: "Patch Plan", ico: "PP", desc: "Validate patch scripts without modifying the working copy." }, - { group: "Edit", id: "patch-apply", name: "Patch Apply", ico: "PA", desc: "Apply a patch script to the working copy." }, - { group: "Edit", id: "transform", name: "Transforms", ico: "TR", desc: "Hex, Base64, URL, XOR, inflate and cipher pipelines." }, - { group: "Case", id: "case", name: "Case Export", ico: "CE", desc: "Build a case archive payload from the current working copy." }, - ]; - - const defaults = { - search: { mode: "text", query: "MZ", max: 1000 }, - "multi-search": { spec: "text MZ\nhex 7f454c46\nregex https?://", max: 1000 }, - parse: { kind: "auto" }, - disasm: { arch: "x86_64", start: 0, length: 4096, max: 1000 }, - entropy: { window: 4096, step: 4096 }, - strings: { min: 4, max: 5000 }, - iocs: { min: 5 }, - carve: { max: 256 }, - keys: { max: 200 }, - timestamps: { max: 1000 }, - rules: { source: 'rule mz { strings: $a = "MZ" condition: $a }', max: 1000 }, - "patch-plan": { source: "set 0x00 90 90" }, - "patch-apply": { source: "set 0x00 90 90" }, - transform: { pipeline: "hex.encode", start: 0, length: 1024 }, - "spectrogram-view": { fftSize: 2048, height: 1024, maxWidth: 1400, aspectRatio: 1, minHz: 0, maxHz: 0, brightness: 1, contrast: 1 }, - hex: { start: 0, bytes: 2048, width: 16 }, - }; - - class LeviathanWorkbench { - constructor(app) { - this.app = app; - this.state = null; - this.lastResults = new Map(); - this.autoRunners = new Map(); - this.hexSearches = new Map(); - } - - renderSidebar() { - const el = document.getElementById("sidebar-profiles"); - if (!el) return; - el.innerHTML = ""; - - const actions = createElement("div", "leviathan-actions"); - const load = createElement("button", "btn btn-primary btn-sm", { - type: "button", - textContent: "Load", - }); - const save = createElement("button", "btn btn-secondary btn-sm", { - type: "button", - textContent: "Save", - }); - load.addEventListener("click", () => this.loadFile()); - save.addEventListener("click", () => this.saveFile()); - actions.appendChild(load); - actions.appendChild(save); - el.appendChild(actions); - - let lastGroup = ""; - for (const tool of tools) { - if (tool.group !== lastGroup) { - el.appendChild( - createElement("div", "sidebar-section-header leviathan-group", { - textContent: tool.group, - }), - ); - lastGroup = tool.group; - } - const item = createElement("div", "sidebar-item leviathan-tool", { - dataset: { toolId: tool.id }, - title: tool.desc, - }); - item.appendChild(createElement("span", "leviathan-tool-ico", { textContent: tool.ico })); - const label = createElement("span", "sidebar-item-label", { textContent: tool.name }); - item.appendChild(label); - item.addEventListener("click", () => this.openTool(tool.id)); - el.appendChild(item); - } - this._highlightActiveTool(); - } - - async loadFile() { - try { - const payload = await window.AlchementaryLink.wailsService.LeviathanOpenFile(); - this.applyState(payload); - this.refreshOpenHexTabs(); - } catch (err) { - if (String(err || "").includes("cancel")) return; - this.app.notifications.error("Load failed: " + err); - } - } - - async saveFile() { - try { - const path = await window.AlchementaryLink.wailsService.LeviathanSaveWorking(); - if (path) this.app.notifications.success("Saved working copy"); - const state = await window.AlchementaryLink.wailsService.LeviathanState(); - this.applyState(state); - } catch (err) { - this.app.notifications.error("Save failed: " + err); - } - } - - openTool(toolId, options = {}) { - const tool = tools.find((t) => t.id === toolId) || tools[0]; - const existingId = options.reuseExisting - ? this._findExistingToolSession(tool.id) - : null; - const sessionId = existingId || `leviathan-${tool.id}-${generateId(8)}`; - - if (!existingId) { - const sessionInfo = { - id: sessionId, - profileId: `leviathan:${tool.id}`, - profileName: "Leviathan", - protocol: "leviathan", - host: "local", - port: 0, - state: "connected", - tabName: tool.name, - toolId: tool.id, - leviathanParams: options.params || null, - }; - this.app.state.addSession(sessionInfo); - this.app.tabs.addTab(sessionInfo); - const panel = document.getElementById(`panel-${sessionId}`); - if (panel) this.renderToolPanel(panel, sessionId, tool); - } else { - this.app.tabs.setActive(existingId); - } - this._highlightActiveTool(tool.id); - } - - renderToolPanel(panel, sessionId, tool) { - panel.classList.add("leviathan-panel"); - panel.innerHTML = ""; - - const header = createElement("div", "leviathan-panel-header"); - const titleWrap = createElement("div", "leviathan-title-wrap"); - titleWrap.appendChild(createElement("span", "leviathan-tool-mark", { textContent: tool.ico })); - const heading = createElement("div", "leviathan-heading"); - heading.appendChild(createElement("h2", "", { textContent: tool.name })); - heading.appendChild(createElement("span", "leviathan-eyebrow", { textContent: this._stateLabel() })); - titleWrap.appendChild(heading); - header.appendChild(titleWrap); - - const headerActions = createElement("div", "leviathan-header-actions"); - const undo = createElement("button", "btn btn-sm", { textContent: "Undo" }); - const redo = createElement("button", "btn btn-sm", { textContent: "Redo" }); - const revert = createElement("button", "btn btn-sm", { textContent: "Revert" }); - undo.addEventListener("click", () => this._stateAction("LeviathanUndo", sessionId)); - redo.addEventListener("click", () => this._stateAction("LeviathanRedo", sessionId)); - revert.addEventListener("click", () => this._stateAction("LeviathanRevert", sessionId)); - headerActions.appendChild(undo); - headerActions.appendChild(redo); - headerActions.appendChild(revert); - header.appendChild(headerActions); - panel.appendChild(header); - - const body = createElement("div", "leviathan-panel-body"); - panel.appendChild(body); - - if (tool.id === "hex") { - this.renderHexEditor(body, sessionId); - } else if (tool.id === "search") { - this.renderSearchTool(body, sessionId); - } else if (tool.id === "spectrogram-encode") { - this.renderSpectrogramEncode(body, sessionId); - } else if (tool.id === "spectrogram-view") { - this.renderSpectrogramView(body, sessionId); - } else { - this.renderToolRunner(body, sessionId, tool); - } - } - - renderHexEditor(body, sessionId) { - const { controls, output } = this._createWorkbench(body, "leviathan-hex-workbench", { topBar: true }); - const toolbar = createElement("div", "leviathan-tool-toolbar"); - const initial = this._sessionParams(sessionId); - const start = this._input("start", String(initial.start ?? 0)); - const bytes = this._input("bytes", String(initial.bytes ?? initial.length ?? 2048)); - const width = this._selectInput("row", [ - ["16", "16 bytes"], - ["32", "32 bytes"], - ], String(initial.width || 16)); - const search = createElement("button", "btn btn-sm", { type: "button", textContent: "Search" }); - const prev = createElement("button", "btn btn-sm", { type: "button", textContent: "Prev" }); - const next = createElement("button", "btn btn-sm", { type: "button", textContent: "Next" }); - toolbar.append(start.row, bytes.row, width.row, search, prev, next); - - const status = createElement("div", "leviathan-status", { textContent: this._stateLabel() }); - const grid = createElement("div", "leviathan-hex-grid"); - controls.append(toolbar, status); - output.appendChild(grid); - - const load = async () => { - try { - status.textContent = "Loading hex..."; - const result = await window.AlchementaryLink.wailsService.LeviathanRunTool("hex", { - start: Number(start.input.value || 0), - bytes: Number(bytes.input.value || 2048), - width: Number(width.input.value || 16), - }); - this.lastResults.set(sessionId, result); - this._renderHexRows(grid, result.rows || [], status, Number(width.input.value || 16), this._currentHexSearchHit(sessionId)); - status.textContent = this._stateLabel(); - } catch (err) { - status.textContent = String(err); - } - }; - [start.input, bytes.input, width.input].forEach((input) => { - input.addEventListener("change", load); - }); - search.addEventListener("click", () => this._showHexSearchModal(sessionId, start.input, width.input, load)); - prev.addEventListener("click", () => this._gotoHexSearchHit(sessionId, -1, start.input, width.input, load)); - next.addEventListener("click", () => this._gotoHexSearchHit(sessionId, 1, start.input, width.input, load)); - load(); - } - - renderSearchTool(body, sessionId) { - const { controls, output } = this._createWorkbench(body, "leviathan-compact-workbench", { topBar: true }); - const params = createElement("div", "leviathan-params"); - const mode = this._selectInput("mode", [ - ["text", "String"], - ["hex", "Hex"], - ["regex", "Regex"], - ], "text"); - const query = this._input("query", "MZ"); - const start = this._input("start", "0"); - const length = this._input("length", "-1"); - const max = this._input("max", "1000"); - params.append(mode.row, query.row, start.row, length.row, max.row); - - const toolbar = createElement("div", "leviathan-tool-toolbar"); - const search = createElement("button", "btn btn-sm btn-primary", { type: "button", textContent: "Search" }); - const exportBtn = createElement("button", "btn btn-sm", { type: "button", textContent: "Export JSON" }); - toolbar.append(search, exportBtn); - controls.append(params, toolbar); - - const summary = createElement("div", "leviathan-summary"); - const results = createElement("div", "leviathan-results"); - output.append(summary, results); - results.appendChild(createElement("div", "leviathan-empty", { textContent: "Search results will appear here." })); - - const runSearch = async () => { - try { - results.innerHTML = ""; - results.appendChild(createElement("div", "leviathan-empty", { textContent: "Searching..." })); - const payload = { - mode: mode.input.value, - query: query.input.value, - start: Number(start.input.value || 0), - length: Number(length.input.value || -1), - max: Number(max.input.value || 1000), - }; - const out = await window.AlchementaryLink.wailsService.LeviathanRunTool("search", payload); - this.lastResults.set(sessionId, out); - this._renderResult(summary, results, out, "search"); - } catch (err) { - results.innerHTML = ""; - this._renderToolError(results, "search", err); - } - }; - search.addEventListener("click", runSearch); - query.input.addEventListener("keydown", (event) => { - if (event.key === "Enter") runSearch(); - }); - exportBtn.addEventListener("click", async () => { - const out = this.lastResults.get(sessionId); - if (!out) return; - try { - await window.AlchementaryLink.wailsService.LeviathanExportJSON("search.json", out); - this.app.notifications.success("Exported JSON"); - } catch (err) { - this.app.notifications.error("Export failed: " + err); - } - }); - this.autoRunners.set(sessionId, runSearch); - requestAnimationFrame(runSearch); - } - - renderSpectrogramEncode(body, sessionId) { - const { controls, output } = this._createWorkbench(body); - const params = createElement("div", "leviathan-params"); - const fields = [ - this._paramInput(params, "durationSec", "Duration (s)", 5, { type: "number", min: "0.25", max: "60", step: "0.25" }), - this._paramInput(params, "sampleRate", "Sample rate", 44100, { type: "number", min: "8000", max: "96000", step: "1000" }), - this._paramInput(params, "minHz", "Min frequency", 200, { type: "number", min: "20", step: "10" }), - this._paramInput(params, "maxHz", "Max frequency", 20000, { type: "number", min: "120", step: "10" }), - this._paramInput(params, "bins", "Frequency bins", 160, { type: "number", min: "16", max: "384", step: "1" }), - this._paramInput(params, "columns", "Time columns", 768, { type: "number", min: "16", max: "2048", step: "1" }), - this._paramInput(params, "gain", "Output gain", 0.85, { type: "number", min: "0.05", max: "1", step: "0.05" }), - this._paramInput(params, "startSec", "Insert start (s)", 0, { type: "number", min: "0", step: "0.1" }), - this._paramInput(params, "mixLevel", "Insert mix", 0.35, { type: "number", min: "0.01", max: "1", step: "0.01" }), - this._paramInput(params, "preserveAspectRatio", "Preserve aspect", true, { type: "checkbox" }), - this._paramInput(params, "invert", "Invert image", true, { type: "checkbox" }), - ]; - - const imagePicker = createElement("div", "leviathan-file-picker"); - const chooseImage = createElement("button", "btn btn-sm", { type: "button", textContent: "Select Image" }); - const imagePath = createElement("span", "leviathan-file-path", { textContent: "No insertion image selected" }); - imagePicker.append(chooseImage, imagePath); - - let selectedImagePath = ""; - chooseImage.addEventListener("click", async () => { - try { - const path = await window.AlchementaryLink.wailsService.SelectFile("Select Image to Insert", ""); - if (!path) return; - selectedImagePath = path; - imagePath.textContent = path; - } catch (err) { - this.app.notifications.error("Image selection failed: " + err); - } - }); - - const toolbar = createElement("div", "leviathan-tool-toolbar"); - const encode = createElement("button", "btn btn-sm btn-primary", { type: "button", textContent: "Encode Loaded Image" }); - const insert = createElement("button", "btn btn-sm", { type: "button", textContent: "Insert Into Loaded Audio" }); - toolbar.append(encode, insert); - controls.append(params, imagePicker, toolbar); - - const summary = createElement("div", "leviathan-summary"); - const results = createElement("div", "leviathan-results"); - output.append(summary, results); - results.appendChild(createElement("div", "leviathan-empty", { textContent: "Load an image to encode, or load WAV/MP3 audio and select an image to insert." })); - - const run = async (toolId) => { - try { - results.innerHTML = ""; - results.appendChild(createElement("div", "leviathan-empty", { textContent: "Rendering audio..." })); - const payload = {}; - for (const field of fields) payload[field.key] = this._fieldValue(field); - if (toolId === "spectrogram-insert") { - if (!selectedImagePath) throw new Error("Select an image to insert first."); - payload.imagePath = selectedImagePath; - } - const out = await window.AlchementaryLink.wailsService.LeviathanRunTool(toolId, payload); - this.lastResults.set(sessionId, out); - summary.innerHTML = ""; - results.innerHTML = ""; - this._renderSummaryPills(summary, out.summary || out); - results.appendChild(createElement("pre", "leviathan-json", { textContent: JSON.stringify(out, null, 2) })); - if (out?.path) this.app.notifications.success("Saved " + out.path); - } catch (err) { - results.innerHTML = ""; - this._renderToolError(results, toolId, err); - } - }; - encode.addEventListener("click", () => run("spectrogram-encode")); - insert.addEventListener("click", () => run("spectrogram-insert")); - } - - renderSpectrogramView(body, sessionId) { - const { controls, output } = this._createWorkbench(body); - const params = createElement("div", "leviathan-params"); - const fields = [ - this._paramInput(params, "fftSize", "FFT size", 2048, { type: "number", min: "256", max: "8192", step: "256" }), - this._paramInput(params, "height", "Base height", 1024, { type: "number", min: "64", max: "1024", step: "16" }), - this._paramInput(params, "maxWidth", "Max width", 1400, { type: "number", min: "64", max: "2400", step: "32" }), - this._paramInput(params, "aspectRatio", "Aspect ratio", 1, { type: "number", min: "0.25", max: "4", step: "0.25" }), - this._paramInput(params, "minHz", "Min frequency", 0, { type: "number", min: "0", step: "10" }), - this._paramInput(params, "maxHz", "Max frequency", 0, { type: "number", min: "0", step: "10" }), - this._paramInput(params, "brightness", "Brightness", 1, { type: "number", min: "0.1", max: "4", step: "0.1" }), - this._paramInput(params, "contrast", "Contrast", 1, { type: "number", min: "0.1", max: "4", step: "0.1" }), - ]; - - const toolbar = createElement("div", "leviathan-tool-toolbar"); - const run = createElement("button", "btn btn-sm btn-primary", { type: "button", textContent: "Render Spectrogram" }); - const exportBtn = createElement("button", "btn btn-sm", { type: "button", textContent: "Export JSON" }); - toolbar.append(run, exportBtn); - controls.append(params, toolbar); - - const summary = createElement("div", "leviathan-summary"); - const results = createElement("div", "leviathan-results"); - output.append(summary, results); - results.appendChild(createElement("div", "leviathan-empty", { textContent: "Load a WAV or MP3 file, then render its spectrogram." })); - - const render = async () => { - try { - results.innerHTML = ""; - results.appendChild(createElement("div", "leviathan-empty", { textContent: "Rendering spectrogram..." })); - const payload = {}; - for (const field of fields) payload[field.key] = this._fieldValue(field); - const out = await window.AlchementaryLink.wailsService.LeviathanRunTool("spectrogram-view", payload); - this.lastResults.set(sessionId, out); - summary.innerHTML = ""; - results.innerHTML = ""; - this._renderSummaryPills(summary, out.summary || out); - if (out?.pngBase64) { - const frame = createElement("div", "leviathan-spectrogram-frame"); - const img = createElement("img", "leviathan-spectrogram-image", { - alt: "Rendered audio spectrogram", - }); - img.src = `data:${out.mime || "image/png"};base64,${out.pngBase64}`; - frame.appendChild(img); - results.appendChild(frame); - } else { - results.appendChild(createElement("pre", "leviathan-json", { textContent: JSON.stringify(out, null, 2) })); - } - } catch (err) { - results.innerHTML = ""; - this._renderToolError(results, "spectrogram-view", err); - } - }; - run.addEventListener("click", render); - exportBtn.addEventListener("click", async () => { - const out = this.lastResults.get(sessionId); - if (!out) return; - try { - await window.AlchementaryLink.wailsService.LeviathanExportJSON("spectrogram-view.json", out); - this.app.notifications.success("Exported JSON"); - } catch (err) { - this.app.notifications.error("Export failed: " + err); - } - }); - this.autoRunners.set(sessionId, render); - requestAnimationFrame(render); - } - - renderToolRunner(body, sessionId, tool) { - const toolbar = createElement("div", "leviathan-tool-toolbar"); - const params = createElement("div", "leviathan-params"); - const fields = this._renderParams(tool.id, params); - const autoRun = this._shouldAutoRunTool(tool.id, fields); - const topBar = fields.length === 0 || this._shouldUseTopBar(fields); - const { controls, output } = this._createWorkbench( - body, - topBar ? "leviathan-compact-workbench" : "", - { topBar }, - ); - const run = createElement("button", "btn btn-sm btn-primary", { textContent: this._toolActionLabel(tool.id) }); - const exportBtn = createElement("button", "btn btn-sm", { textContent: "Export JSON" }); - if (!autoRun || fields.length) toolbar.append(run); - toolbar.append(exportBtn); - if (fields.length) controls.append(params); - controls.append(toolbar); - - const summary = createElement("div", "leviathan-summary"); - const results = createElement("div", "leviathan-results"); - output.append(summary, results); - - let running = false; - const runTool = async () => { - if (running) return; - running = true; - try { - if (tool.id === "diff" && !this.state?.compare) { - const compare = await window.AlchementaryLink.wailsService.LeviathanOpenCompare(); - this.applyState(compare); - } - results.innerHTML = ""; - results.appendChild(createElement("div", "leviathan-empty", { textContent: "Running..." })); - const payload = {}; - for (const field of fields) { - payload[field.key] = this._fieldValue(field); - } - const out = await window.AlchementaryLink.wailsService.LeviathanRunTool(tool.id, payload); - this.lastResults.set(sessionId, out); - this._renderResult(summary, results, out, tool.id); - if (out && out.state) this.applyState(out.state); - } catch (err) { - results.innerHTML = ""; - this._renderToolError(results, tool.id, err); - } finally { - running = false; - } - }; - if (!autoRun || fields.length) run.addEventListener("click", runTool); - exportBtn.addEventListener("click", async () => { - const out = this.lastResults.get(sessionId); - if (!out) return; - try { - await window.AlchementaryLink.wailsService.LeviathanExportJSON(`${tool.id}.json`, out); - this.app.notifications.success("Exported JSON"); - } catch (err) { - this.app.notifications.error("Export failed: " + err); - } - }); - if (autoRun) { - this.autoRunners.set(sessionId, runTool); - requestAnimationFrame(runTool); - } else { - this.autoRunners.delete(sessionId); - results.appendChild( - createElement("div", "leviathan-empty", { - textContent: "Ready.", - }), - ); - } - } - - _createWorkbench(body, extraClass = "", options = {}) { - body.classList.add("leviathan-workbench"); - body.classList.toggle("leviathan-top-workbench", Boolean(options.topBar)); - if (extraClass) body.classList.add(extraClass); - const controls = createElement("section", "leviathan-control-plane"); - const output = createElement("section", "leviathan-output-plane"); - body.append(controls, output); - return { controls, output }; - } - - applyState(payload) { - this.state = payload?.state || payload || null; - this._updateOpenHeaders(); - } - - destroyPanel(sessionId) { - this.lastResults.delete(sessionId); - this.autoRunners.delete(sessionId); - this.hexSearches.delete(sessionId); - } - - handleTabFocus(sessionId) { - const session = this.app.state.getSession(sessionId); - if (session?.protocol !== "leviathan") return; - this._highlightActiveTool(session.toolId); - const runner = this.autoRunners.get(sessionId); - if (runner) runner(); - } - - refreshOpenHexTabs() { - for (const session of this.app.state.getAllSessions()) { - if (session.protocol !== "leviathan" || session.toolId !== "hex") continue; - const panel = document.getElementById(`panel-${session.id}`); - if (panel) this.renderToolPanel(panel, session.id, tools[0]); - } - } - - _renderHexRows(grid, rows, status, rowWidth = 16, activeHit = null) { - grid.innerHTML = ""; - grid.style.setProperty("--leviathan-hex-width", String(rowWidth)); - if (!rows.length) { - grid.appendChild(createElement("div", "leviathan-empty", { textContent: "Load a file to inspect bytes." })); - return; - } - for (const row of rows) { - const line = createElement("div", "leviathan-hex-row"); - line.appendChild(createElement("span", "leviathan-offset", { textContent: this._hexOffset(row.offset) })); - const bytes = createElement("span", "leviathan-byte-cells"); - const cells = row.bytes || String(row.hex || "").split(/\s+/).filter(Boolean); - const ascii = createElement("span", "leviathan-ascii", { textContent: row.ascii || "" }); - cells.forEach((value, idx) => { - const cellOffset = (row.offset || 0) + idx; - const hexValue = this._normaliseHexByte(value) || "00"; - const cell = createElement("input", "leviathan-byte", { - value: hexValue, - maxlength: "2", - inputmode: "text", - autocomplete: "off", - spellcheck: "false", - "aria-label": `Byte ${this._hexOffset(cellOffset)}`, - dataset: { - offset: String(cellOffset), - committedHex: hexValue, - }, - }); - if (activeHit && cellOffset >= activeHit.offset && cellOffset < activeHit.offset + activeHit.length) { - cell.classList.add("is-search-hit"); - } - this._wireHexByteCell(cell, bytes, ascii, idx, status); - bytes.appendChild(cell); - }); - line.appendChild(bytes); - line.appendChild(ascii); - grid.appendChild(line); - } - } - - _wireHexByteCell(cell, rowCells, ascii, asciiIndex, status) { - cell.addEventListener("focus", () => { - cell.select(); - }); - cell.addEventListener("keydown", (event) => { - if (event.key === "ArrowRight") { - event.preventDefault(); - this._focusHexByte(cell, 1); - } else if (event.key === "ArrowLeft") { - event.preventDefault(); - this._focusHexByte(cell, -1); - } else if (event.key === "Enter" || event.key === " ") { - event.preventDefault(); - this._finaliseHexByte(cell, rowCells, ascii, asciiIndex, status, { advance: 1 }); - } else if (event.key === "Backspace" && cell.value.length === 0) { - event.preventDefault(); - this._focusHexByte(cell, -1); - } - }); - cell.addEventListener("input", () => { - this._handleHexByteInput(cell, rowCells, ascii, asciiIndex, status); - }); - cell.addEventListener("paste", (event) => { - this._pasteHexBytes(event, cell, rowCells, ascii, asciiIndex, status); - }); - cell.addEventListener("blur", () => { - this._finaliseHexByte(cell, rowCells, ascii, asciiIndex, status, { advance: 0 }); - }); - } - - _handleHexByteInput(cell, rowCells, ascii, asciiIndex, status) { - const clean = this._normaliseHexByte(cell.value); - if (cell.value !== clean) cell.value = clean; - cell.classList.toggle("is-editing", clean.length === 1); - if (clean.length === 0) { - this._updateHexAsciiCell(ascii, asciiIndex, cell.dataset.committedHex || "00"); - rowCells.classList.toggle("is-editing", Boolean(rowCells.querySelector(".leviathan-byte.is-editing"))); - return; - } - if (clean.length === 2) { - this._updateHexAsciiCell(ascii, asciiIndex, clean); - this._commitHexByte(cell, ascii, asciiIndex, status); - this._focusHexByte(cell, 1); - } - rowCells.classList.toggle("is-editing", Boolean(rowCells.querySelector(".leviathan-byte.is-editing"))); - } - - _finaliseHexByte(cell, rowCells, ascii, asciiIndex, status, options = {}) { - let clean = this._normaliseHexByte(cell.value); - if (clean.length === 0) { - clean = cell.dataset.committedHex || "00"; - } else if (clean.length === 1) { - clean = clean.padStart(2, "0"); - } - cell.value = clean; - cell.classList.remove("is-editing"); - rowCells.classList.toggle("is-editing", Boolean(rowCells.querySelector(".leviathan-byte.is-editing"))); - this._updateHexAsciiCell(ascii, asciiIndex, clean); - this._commitHexByte(cell, ascii, asciiIndex, status); - if (options.advance) this._focusHexByte(cell, options.advance); - } - - _pasteHexBytes(event, cell, rowCells, ascii, asciiIndex, status) { - const text = event.clipboardData?.getData("text/plain") || ""; - const pasted = this._hexByteList(text); - if (!pasted.length) return; - event.preventDefault(); - const scope = cell.closest(".leviathan-hex-grid") || rowCells; - const cells = Array.from(scope.querySelectorAll(".leviathan-byte")); - const startIndex = cells.indexOf(cell); - if (startIndex < 0) return; - pasted.forEach((hex, idx) => { - const target = cells[startIndex + idx]; - if (!target) return; - const context = this._hexCellContext(target); - if (!context) return; - target.value = hex; - target.classList.remove("is-editing"); - this._updateHexAsciiCell(context.ascii, context.index, hex); - this._commitHexByte(target, context.ascii, context.index, status); - }); - const next = cells[Math.min(startIndex + pasted.length, cells.length - 1)]; - if (next) { - next.focus(); - next.select(); - } - } - - _commitHexByte(cell, ascii, asciiIndex, status) { - const hex = this._normaliseHexByte(cell.value); - if (hex.length !== 2) return; - if (hex === cell.dataset.committedHex || hex === cell.dataset.pendingHex) return; - const previous = cell.dataset.committedHex || "00"; - const offset = Number(cell.dataset.offset || 0); - cell.dataset.pendingHex = hex; - cell.classList.add("is-saving"); - window.AlchementaryLink.wailsService - .LeviathanSetByte(offset, hex) - .then((payload) => { - if (cell.dataset.pendingHex === hex) { - delete cell.dataset.pendingHex; - cell.dataset.committedHex = hex; - cell.classList.remove("is-saving"); - status.textContent = "Edited " + this._hexOffset(offset); - this.applyState(payload); - } - }) - .catch((err) => { - if (cell.dataset.pendingHex === hex) { - delete cell.dataset.pendingHex; - cell.classList.remove("is-saving"); - cell.value = previous; - this._updateHexAsciiCell(ascii, asciiIndex, previous); - status.textContent = String(err); - } - }); - } - - _focusHexByte(cell, direction) { - const scope = cell.closest(".leviathan-hex-grid") || cell.closest(".leviathan-byte-cells"); - if (!scope) return; - const cells = Array.from(scope.querySelectorAll(".leviathan-byte")); - const index = cells.indexOf(cell); - const next = cells[index + direction]; - if (!next) return; - next.focus(); - next.select(); - } - - _hexCellContext(cell) { - const row = cell.closest(".leviathan-hex-row"); - if (!row) return null; - const ascii = row.querySelector(".leviathan-ascii"); - const rowCells = Array.from(row.querySelectorAll(".leviathan-byte")); - const index = rowCells.indexOf(cell); - if (!ascii || index < 0) return null; - return { ascii, index }; - } - - _updateHexAsciiCell(ascii, index, hex) { - const chars = Array.from(ascii.textContent || ""); - while (chars.length <= index) chars.push(" "); - chars[index] = this._hexByteToAscii(hex); - ascii.textContent = chars.join(""); - } - - _hexByteToAscii(hex) { - const value = Number.parseInt(hex, 16); - if (!Number.isFinite(value)) return "."; - return value >= 32 && value < 127 ? String.fromCharCode(value) : "."; - } - - _normaliseHexByte(value) { - return String(value || "") - .replace(/[^0-9a-f]/gi, "") - .slice(0, 2) - .toLowerCase(); - } - - _hexByteList(value) { - const clean = String(value || "") - .replace(/0x/gi, "") - .replace(/[^0-9a-f]/gi, "") - .toLowerCase(); - const bytes = []; - for (let i = 0; i + 1 < clean.length; i += 2) { - bytes.push(clean.slice(i, i + 2)); - } - return bytes; - } - - _renderParams(toolId, container) { - container.innerHTML = ""; - const cfg = defaults[toolId] || {}; - return Object.entries(cfg).map(([key, value]) => { - const row = createElement("label", "leviathan-param"); - row.appendChild(createElement("span", "", { textContent: key })); - let input; - if (key === "source" || key === "spec" || key === "pipeline") { - input = createElement("textarea", "form-input"); - } else if (key === "mode") { - input = createElement("select", "form-input"); - ["text", "hex", "regex"].forEach((v) => input.add(new Option(v, v))); - } else if (key === "kind") { - input = createElement("select", "form-input"); - ["auto", "png", "jpeg", "gif", "bmp", "zip", "elf", "pe", "macho", "riff", "asn1", "cert"].forEach((v) => input.add(new Option(v, v))); - } else if (key === "arch") { - input = createElement("select", "form-input"); - ["x86_64", "x86", "aarch64", "arm", "thumb", "mips", "mipsle"].forEach((v) => input.add(new Option(v, v))); - } else { - input = createElement("input", "form-input"); - } - input.value = value; - row.appendChild(input); - container.appendChild(row); - return { key, input }; - }); - } - - _shouldUseTopBar(fields) { - return fields.length <= 3 && !fields.some((field) => field.input.tagName === "TEXTAREA"); - } - - _shouldAutoRunTool(toolId, fields) { - const autoRunTools = new Set([ - "overview", - "hashes", - "entropy", - "strings", - "iocs", - "search", - "multi-search", - "parse", - "disasm", - "pdf", - "squashfs", - "carve", - "keys", - "timestamps", - "stego", - "metadata", - "binsnoop", - "rules", - "patch-plan", - "transform", - "spectrogram-view", - ]); - return autoRunTools.has(toolId); - } - - _toolActionLabel(toolId) { - if (toolId === "case") return "Export Case"; - if (toolId === "diff") return this.state?.compare ? "Compare" : "Load Compare"; - return "Run"; - } - - _renderResult(summary, results, out, toolId = "") { - summary.innerHTML = ""; - results.innerHTML = ""; - this._renderSummaryPills(summary, this._flatSummary(out)); - if (toolId === "overview") return this._renderOverview(results, out); - if (toolId === "hashes") return this._renderHashes(results, out); - if (toolId === "entropy") return this._renderEntropy(summary, results, out); - if (toolId === "search") return this._renderSearchResults(summary, results, out); - if (toolId === "strings") return this._renderStrings(results, out.items || []); - if (toolId === "binsnoop") return this._renderBinsnoop(summary, results, out); - if (toolId === "carve") return this._renderCarve(summary, results, out); - if (out?.tree) { - this._renderTree(results, out.tree); - return; - } - const arr = this._firstArray(out); - if (arr) { - this._renderTable(results, arr); - return; - } - results.appendChild(createElement("pre", "leviathan-json", { textContent: JSON.stringify(out, null, 2) })); - } - - _renderOverview(container, out) { - const dashboard = createElement("div", "leviathan-dashboard"); - dashboard.appendChild(this._metricCard("File", out?.name || this.state?.name || "No file")); - dashboard.appendChild(this._metricCard("Size", formatBytes(out?.size || 0))); - dashboard.appendChild(this._metricCard("Entropy mean", this._formatNumber(out?.entropy?.mean, 2))); - dashboard.appendChild(this._metricCard("Strings", out?.strings?.total ?? 0)); - dashboard.appendChild(this._metricCard("Carve hits", out?.carve?.total ?? 0)); - dashboard.appendChild(this._metricCard("Valid carve", out?.carve?.valid ?? 0)); - container.appendChild(dashboard); - - const sections = createElement("div", "leviathan-sections"); - sections.appendChild(this._kvSection("Hashes", out?.hashes || {}, ["crc32", "md5", "sha1", "sha256", "sha512"])); - sections.appendChild(this._kvSection("IOC totals", out?.iocs || {}, Object.keys(out?.iocs || {}).sort())); - sections.appendChild(this._kvSection("Scan limits", out?.limits || {}, Object.keys(out?.limits || {}), true)); - container.appendChild(sections); - } - - _renderHashes(container, out) { - const rows = ["crc32", "md5", "sha1", "sha256", "sha512"] - .filter((key) => out?.[key]) - .map((key) => ({ algorithm: key.toUpperCase(), digest: out[key] })); - this._renderTable(container, rows, ["algorithm", "digest"]); - } - - _renderEntropy(summary, container, out) { - summary.innerHTML = ""; - this._renderSummaryPills(summary, { - samples: Array.isArray(out?.samples) ? out.samples.length : 0, - mean: this._formatNumber(out?.mean, 3), - min: this._formatNumber(out?.min, 3), - max: this._formatNumber(out?.max, 3), - high: out?.highCount ?? 0, - }); - - const frame = createElement("div", "leviathan-entropy-frame"); - const canvas = createElement("canvas", "leviathan-entropy-canvas", { - width: "1200", - height: "260", - }); - frame.appendChild(canvas); - container.appendChild(frame); - - const samples = (out?.samples || []).map((v) => Number(v) || 0); - const offsets = (out?.offsets || []).map((v) => Number(v) || 0); - const step = Number(out?.step || out?.windowSize || 4096); - let dragStart = null; - let dragEnd = null; - - const pointerIndex = (event) => { - const rect = canvas.getBoundingClientRect(); - const ratio = Math.min(1, Math.max(0, (event.clientX - rect.left) / Math.max(rect.width, 1))); - return Math.min(samples.length - 1, Math.max(0, Math.round(ratio * (samples.length - 1)))); - }; - const draw = () => this._drawEntropyCanvas(canvas, samples, dragStart, dragEnd); - - canvas.addEventListener("mousedown", (event) => { - if (!samples.length) return; - dragStart = pointerIndex(event); - dragEnd = dragStart; - draw(); - }); - canvas.addEventListener("mousemove", (event) => { - if (dragStart == null) return; - dragEnd = pointerIndex(event); - draw(); - }); - canvas.addEventListener("mouseup", () => { - if (dragStart == null || dragEnd == null || !offsets.length) { - dragStart = null; - dragEnd = null; - draw(); - return; - } - const lo = Math.min(dragStart, dragEnd); - const hi = Math.max(dragStart, dragEnd); - const start = offsets[lo] || 0; - const end = (offsets[hi] || start) + step; - dragStart = null; - dragEnd = null; - draw(); - if (end > start) this.openHexRange(start, end - start); - }); - canvas.addEventListener("mouseleave", () => { - if (dragStart == null) return; - dragStart = null; - dragEnd = null; - draw(); - }); - requestAnimationFrame(draw); - } - - _renderSearchResults(summary, container, out) { - const hits = out?.hits || []; - summary.innerHTML = ""; - this._renderSummaryPills(summary, { hits: hits.length }); - this._renderTable(container, hits, ["offset", "length", "sample"]); - } - - _renderStrings(container, rows) { - this._renderTable(container, rows, ["offset", "length", "encoding", "value"]); - } - - _renderCarve(summary, container, out) { - const rows = out?.items || []; - summary.innerHTML = ""; - this._renderSummaryPills(summary, { - hits: rows.length, - exportable: rows.filter((row) => this._rowRange(row).length > 0).length, - }); - this._renderTable(container, rows, ["offset", "length", "kind", "extension", "confidence", "status"], { - exportRows: true, - exportName: (row) => this._foundFileName(row, "carve"), - }); - } - - _renderBinsnoop(summary, container, out) { - const results = out?.results || {}; - const rows = results.file_map || results.fileMap || []; - summary.innerHTML = ""; - this._renderSummaryPills(summary, { - hits: rows.length, - scanned: formatBytes(results.scanned_bytes || results.document_bytes || this.state?.size || 0), - mode: results.whole_file ? "whole file" : "range", - window: results.windowed ? formatBytes(results.window_bytes || 0) : "full", - }); - this._renderTable(container, rows, ["offset", "size", "name", "description", "confidence"], { - exportRows: true, - exportName: (row) => this._foundFileName(row, "binsnoop"), - }); - } - - _drawEntropyCanvas(canvas, samples, dragStart, dragEnd) { - const ctx = canvas.getContext("2d"); - const width = canvas.width; - const height = canvas.height; - ctx.clearRect(0, 0, width, height); - ctx.fillStyle = "#101010"; - ctx.fillRect(0, 0, width, height); - ctx.strokeStyle = "rgba(255,255,255,0.08)"; - ctx.lineWidth = 1; - for (let i = 1; i < 8; i++) { - const y = height - (i / 8) * height; - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(width, y); - ctx.stroke(); - } - if (dragStart != null && dragEnd != null) { - const lo = Math.min(dragStart, dragEnd); - const hi = Math.max(dragStart, dragEnd); - const x1 = (lo / Math.max(samples.length - 1, 1)) * width; - const x2 = (hi / Math.max(samples.length - 1, 1)) * width; - ctx.fillStyle = "rgba(78, 205, 196, 0.18)"; - ctx.fillRect(x1, 0, Math.max(2, x2 - x1), height); - } - if (!samples.length) return; - ctx.strokeStyle = "#4ecdc4"; - ctx.lineWidth = 2; - ctx.beginPath(); - samples.forEach((sample, index) => { - const x = (index / Math.max(samples.length - 1, 1)) * width; - const y = height - (Math.max(0, Math.min(8, sample)) / 8) * height; - if (index === 0) ctx.moveTo(x, y); - else ctx.lineTo(x, y); - }); - ctx.stroke(); - } - - _renderTable(container, rows, preferredColumns = null, options = {}) { - const sourceRows = rows || []; - const maxRows = options.maxRows || (options.exportRows ? sourceRows.length : 1000); - rows = sourceRows.slice(0, maxRows); - if (!rows.length) { - container.appendChild(createElement("div", "leviathan-empty", { textContent: "No rows." })); - return; - } - const preferred = preferredColumns || ["offset", "length", "size", "kind", "name", "category", "value", "sample", "summary", "description", "iso", "type"]; - const all = Array.from(new Set(rows.flatMap((row) => Object.keys(row || {})))); - const cols = [...preferred.filter((c) => all.includes(c)), ...all.filter((c) => !preferred.includes(c))].slice(0, 8); - if (options.exportRows) cols.push("export"); - const table = createElement("table", "leviathan-table"); - const thead = createElement("thead"); - const headRow = createElement("tr"); - cols.forEach((c) => headRow.appendChild(createElement("th", "", { textContent: c }))); - thead.appendChild(headRow); - const tbody = createElement("tbody"); - rows.forEach((row) => { - const tr = createElement("tr"); - cols.forEach((c) => { - const td = createElement("td"); - if (c === "export") { - const range = this._rowRange(row); - if (range.length > 0) { - const btn = createElement("button", "btn btn-xs leviathan-export-found", { - type: "button", - textContent: "Export", - }); - btn.addEventListener("click", () => this._exportFoundRange(row, options.exportName)); - td.appendChild(btn); - } else { - td.appendChild(createElement("span", "leviathan-muted", { textContent: "No length" })); - } - } else if (c === "offset" && row[c] != null) { - const offset = Number(row[c]); - const length = Number(row.length || row.size || 256); - const btn = createElement("button", "leviathan-offset-link", { - type: "button", - textContent: this._hexOffset(offset), - }); - btn.addEventListener("click", () => this.openHexRange(offset, Math.max(256, length || 256))); - td.appendChild(btn); - } else { - td.textContent = this._formatCell(row[c]); - } - tr.appendChild(td); - }); - tbody.appendChild(tr); - }); - table.append(thead, tbody); - container.appendChild(table); - if (sourceRows.length > rows.length) { - container.appendChild(createElement("div", "leviathan-table-note", { - textContent: `Showing ${rows.length} of ${sourceRows.length} rows.`, - })); - } - } - - _rowRange(row) { - const offset = Number(row?.offset); - if (!Number.isFinite(offset) || offset < 0) return { offset: 0, length: 0 }; - let length = Number(row.length ?? row.size ?? 0); - if ((!Number.isFinite(length) || length <= 0) && Number.isFinite(Number(row.end))) { - length = Number(row.end) - offset; - } - if ((!Number.isFinite(length) || length <= 0) && this.state?.size > offset) { - length = Number(this.state.size) - offset; - } - return { - offset, - length: Number.isFinite(length) ? Math.max(0, Math.floor(length)) : 0, - }; - } - - _foundFileName(row, prefix) { - const range = this._rowRange(row); - const label = String(row.kind || row.name || row.id || prefix || "found") - .toLowerCase() - .replace(/[^a-z0-9._-]+/g, "-") - .replace(/^-+|-+$/g, "") - .slice(0, 48) || "found"; - const ext = String(row.extension || row.ext || "bin") - .toLowerCase() - .replace(/[^a-z0-9]+/g, "") - .slice(0, 12) || "bin"; - return `${prefix || "found"}-${label}-${this._hexOffset(range.offset)}.${ext}`; - } - - async _exportFoundRange(row, nameBuilder) { - const range = this._rowRange(row); - if (range.length <= 0) { - this.app.notifications.error("Export failed: finding has no readable range"); - return; - } - const name = typeof nameBuilder === "function" ? nameBuilder(row) : this._foundFileName(row, "found"); - try { - const path = await window.AlchementaryLink.wailsService.LeviathanExportRange(name, range.offset, range.length); - if (path) this.app.notifications.success("Exported " + path); - } catch (err) { - this.app.notifications.error("Export failed: " + err); - } - } - - _renderToolError(container, toolId, err) { - const message = String(err || "The tool could not complete."); - const isSquashMagic = toolId === "squashfs" && /magic|superblock|squash/i.test(message); - const state = createElement("div", isSquashMagic ? "leviathan-error-state leviathan-error-state-warning" : "leviathan-error-state"); - const icon = createElement("div", "leviathan-error-icon", { "aria-hidden": "true" }); - icon.appendChild(createElement("span", "", { textContent: "!" })); - const title = isSquashMagic ? "Not a SquashFS image" : "Tool could not run"; - const body = isSquashMagic - ? "The loaded file does not start with a valid SquashFS superblock. Try another file, or inspect this one in the Hex Editor." - : message; - state.appendChild(icon); - state.appendChild(createElement("strong", "", { textContent: title })); - state.appendChild(createElement("p", "", { textContent: body })); - container.appendChild(state); - } - - _renderTree(container, root) { - const wrap = createElement("div", "leviathan-tree"); - const walk = (node, depth = 0) => { - const row = createElement("div", "leviathan-tree-node"); - row.style.paddingLeft = `${depth * 16}px`; - row.appendChild(createElement("strong", "", { textContent: node.name || "" })); - row.appendChild(createElement("span", "", { textContent: ` ${node.type || ""} ${node.offset != null ? this._hexOffset(node.offset) : ""} +${node.length || 0}` })); - if (node.value) row.appendChild(createElement("div", "", { textContent: node.value })); - wrap.appendChild(row); - (node.children || []).forEach((child) => walk(child, depth + 1)); - }; - walk(root); - container.appendChild(wrap); - } - - _stateAction(method, sessionId) { - return window.AlchementaryLink.wailsService[method]() - .then((state) => { - this.applyState(state); - const session = this.app.state.getSession(sessionId); - if (session?.toolId === "hex") { - const panel = document.getElementById(`panel-${sessionId}`); - if (panel) this.renderToolPanel(panel, sessionId, tools[0]); - } - }) - .catch((err) => this.app.notifications.error(String(err))); - } - - _input(label, value) { - const row = createElement("label", "leviathan-param-inline"); - row.appendChild(createElement("span", "", { textContent: label })); - const input = createElement("input", "form-input", { value }); - row.appendChild(input); - return { row, input }; - } - - _selectInput(label, options, value) { - const row = createElement("label", "leviathan-param-inline"); - row.appendChild(createElement("span", "", { textContent: label })); - const input = createElement("select", "form-input"); - options.forEach(([optionValue, text]) => input.add(new Option(text, optionValue))); - input.value = value; - row.appendChild(input); - return { row, input }; - } - - _paramInput(container, key, label, value, attrs = {}) { - const row = createElement("label", "leviathan-param"); - row.appendChild(createElement("span", "", { textContent: label })); - const input = createElement("input", "form-input"); - for (const [attr, attrValue] of Object.entries(attrs)) { - input.setAttribute(attr, attrValue); - } - if (attrs.type === "checkbox") { - input.checked = Boolean(value); - input.classList.add("leviathan-checkbox"); - } else { - input.value = value; - } - row.appendChild(input); - container.appendChild(row); - return { key, input }; - } - - _fieldValue(field) { - if (field.input.type === "checkbox") { - return field.input.checked; - } - const raw = field.input.value; - const numeric = Number(raw); - const stringKeys = ["query", "source", "spec", "pipeline", "kind", "arch", "mode"]; - return raw !== "" && Number.isFinite(numeric) && !stringKeys.includes(field.key) - ? numeric - : raw; - } - - openHexRange(start, length = 2048) { - const safeStart = Math.max(0, Math.floor(Number(start) || 0)); - const safeLength = Math.max(256, Math.min(8192, Math.floor(Number(length) || 2048))); - this.openTool("hex", { - params: { start: safeStart, bytes: safeLength, width: 16 }, - }); - } - - _sessionParams(sessionId) { - return this.app.state.getSession(sessionId)?.leviathanParams || {}; - } - - _currentHexSearchHit(sessionId) { - const state = this.hexSearches.get(sessionId); - if (!state || state.index < 0 || !state.hits?.length) return null; - const hit = state.hits[state.index]; - return { - offset: Number(hit.offset || 0), - length: Number(hit.length || 1), - }; - } - - _gotoHexSearchHit(sessionId, direction, startInput, widthInput, load) { - const state = this.hexSearches.get(sessionId); - if (!state || !state.hits?.length) { - this._showHexSearchModal(sessionId, startInput, widthInput, load); - return; - } - state.index = (state.index + direction + state.hits.length) % state.hits.length; - const hit = state.hits[state.index]; - const rowWidth = Number(widthInput.value || 16); - const offset = Number(hit.offset || 0); - startInput.value = String(Math.max(0, offset - (offset % rowWidth))); - load(); - } - - _showHexSearchModal(sessionId, startInput, widthInput, load) { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm leviathan-search-modal"); - const header = createElement("div", "modal-header"); - header.appendChild(createElement("h3", "modal-title", { textContent: "Hex Search" })); - const closeBtn = createElement("button", "btn btn-icon btn-xs modal-close", { - type: "button", - textContent: "x", - }); - header.appendChild(closeBtn); - - const body = createElement("div", "modal-body"); - const fields = createElement("div", "leviathan-modal-fields"); - const mode = this._selectInput("mode", [ - ["text", "String"], - ["hex", "Hex"], - ["regex", "Regex"], - ], "text"); - const query = this._input("query", ""); - fields.append(mode.row, query.row); - const status = createElement("div", "leviathan-status", { textContent: "" }); - body.append(fields, status); - - const footer = createElement("div", "modal-footer"); - const cancel = createElement("button", "btn btn-sm", { type: "button", textContent: "Cancel" }); - const run = createElement("button", "btn btn-sm btn-primary", { type: "button", textContent: "Search" }); - footer.append(cancel, run); - modal.append(header, body, footer); - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - - const close = () => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - }; - const search = async () => { - try { - status.textContent = "Searching..."; - const out = await window.AlchementaryLink.wailsService.LeviathanRunTool("search", { - mode: mode.input.value, - query: query.input.value, - start: 0, - length: -1, - max: 5000, - }); - const hits = (out.hits || []).map((hit) => ({ - offset: Number(hit.offset || 0), - length: Number(hit.length || 1), - sample: hit.sample || "", - })); - this.hexSearches.set(sessionId, { - mode: mode.input.value, - query: query.input.value, - hits, - index: -1, - }); - close(); - if (hits.length) this._gotoHexSearchHit(sessionId, 1, startInput, widthInput, load); - } catch (err) { - status.textContent = String(err); - } - }; - - closeBtn.addEventListener("click", close); - cancel.addEventListener("click", close); - run.addEventListener("click", search); - query.input.addEventListener("keydown", (event) => { - if (event.key === "Enter") search(); - }); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - query.input.focus(); - }); - } - - _metricCard(label, value) { - const item = createElement("div", "leviathan-metric"); - item.appendChild(createElement("span", "", { textContent: label })); - item.appendChild(createElement("strong", "", { textContent: this._formatCell(value) })); - return item; - } - - _kvSection(title, values, keys, bytes = false) { - const section = createElement("section", "leviathan-section"); - section.appendChild(createElement("h3", "", { textContent: title })); - const list = createElement("div", "leviathan-kv-list"); - keys.forEach((key) => { - if (values?.[key] == null) return; - const row = createElement("div", "leviathan-kv-row"); - row.appendChild(createElement("span", "", { textContent: key })); - row.appendChild(createElement("strong", "", { - textContent: bytes ? formatBytes(Number(values[key]) || 0) : this._formatCell(values[key]), - })); - list.appendChild(row); - }); - section.appendChild(list); - return section; - } - - _formatNumber(value, digits = 2) { - const n = Number(value); - return Number.isFinite(n) ? n.toFixed(digits) : "0"; - } - - _renderSummaryPills(container, values) { - Object.entries(values || {}) - .slice(0, 10) - .forEach(([key, value]) => { - const item = createElement("div", "leviathan-pill"); - item.appendChild(createElement("span", "", { textContent: key })); - item.appendChild(createElement("strong", "", { textContent: this._formatCell(value) })); - container.appendChild(item); - }); - } - - _findExistingToolSession(toolId) { - const session = this.app.state - .getAllSessions() - .find((s) => s.protocol === "leviathan" && s.toolId === toolId); - return session?.id || null; - } - - _updateOpenHeaders() { - document.querySelectorAll(".leviathan-eyebrow").forEach((el) => { - el.textContent = this._stateLabel(); - }); - } - - _highlightActiveTool(toolId) { - document.querySelectorAll(".leviathan-tool").forEach((el) => { - el.classList.toggle("sidebar-item-active", el.dataset.toolId === toolId); - }); - } - - _stateLabel() { - if (!this.state?.name) return "No file loaded"; - const dirty = this.state.dirty ? "modified" : "clean"; - return `${this.state.name} · ${formatBytes(this.state.size || 0)} · ${dirty}`; - } - - _flatSummary(out) { - if (!out || typeof out !== "object") return {}; - const summary = {}; - for (const [key, value] of Object.entries(out)) { - if (Array.isArray(value)) summary[key] = value.length; - else if (value && typeof value === "object") { - if (key === "hashes") summary.hash = value.sha256 || value.md5 || "ready"; - else summary[key] = Object.keys(value).length + " keys"; - } else { - summary[key] = value; - } - } - return summary; - } - - _firstArray(out) { - if (!out || typeof out !== "object") return null; - for (const key of ["items", "hits", "all", "regions", "blocks", "entries", "metadata"]) { - if (Array.isArray(out[key])) return out[key]; - } - if (out.results?.Results && Array.isArray(out.results.Results)) return out.results.Results; - return null; - } - - _formatCell(value) { - return typeof value === "object" ? JSON.stringify(value) : String(value ?? ""); - } - - _hexOffset(value) { - return "0x" + Number(value || 0).toString(16).padStart(8, "0"); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.LeviathanWorkbench = LeviathanWorkbench; - window.AlchementaryLink.LeviathanTools = tools; -})(); diff --git a/frontend/src/js/lib/xterm-addon-fit.min.js b/frontend/src/js/lib/xterm-addon-fit.min.js deleted file mode 100644 index 9b25be3..0000000 --- a/frontend/src/js/lib/xterm-addon-fit.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(globalThis,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core._renderService.dimensions;if(0===e.css.cell.width||0===e.css.cell.height)return;const t=0===this._terminal.options.scrollback?0:this._terminal.options.overviewRuler?.width||14,r=window.getComputedStyle(this._terminal.element.parentElement),i=parseInt(r.getPropertyValue("height")),o=Math.max(0,parseInt(r.getPropertyValue("width"))),s=window.getComputedStyle(this._terminal.element),n=i-(parseInt(s.getPropertyValue("padding-top"))+parseInt(s.getPropertyValue("padding-bottom"))),l=o-(parseInt(s.getPropertyValue("padding-right"))+parseInt(s.getPropertyValue("padding-left")))-t;return{cols:Math.max(2,Math.floor(l/e.css.cell.width)),rows:Math.max(1,Math.floor(n/e.css.cell.height))}}}})(),e})())); -//# sourceMappingURL=addon-fit.js.map \ No newline at end of file diff --git a/frontend/src/js/lib/xterm-addon-search.min.js b/frontend/src/js/lib/xterm-addon-search.min.js deleted file mode 100644 index 23bb5e7..0000000 --- a/frontend/src/js/lib/xterm-addon-search.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.SearchAddon=t():e.SearchAddon=t()}(globalThis,(()=>(()=>{"use strict";var e={864:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DecorationManager=void 0;const i=s(540);class r extends i.Disposable{constructor(e){super(),this._terminal=e,this._highlightDecorations=[],this._highlightedLines=new Set,this._register((0,i.toDisposable)((()=>this.clearHighlightDecorations())))}createHighlightDecorations(e,t){this.clearHighlightDecorations();for(const s of e){const e=this._createResultDecorations(s,t,!1);if(e)for(const t of e)this._storeDecoration(t,s)}}createActiveDecoration(e,t){const s=this._createResultDecorations(e,t,!0);if(s)return{decorations:s,match:e,dispose(){(0,i.dispose)(s)}}}clearHighlightDecorations(){(0,i.dispose)(this._highlightDecorations),this._highlightDecorations=[],this._highlightedLines.clear()}_storeDecoration(e,t){this._highlightedLines.add(e.marker.line),this._highlightDecorations.push({decoration:e,match:t,dispose(){e.dispose()}})}_applyStyles(e,t,s){e.classList.contains("xterm-find-result-decoration")||(e.classList.add("xterm-find-result-decoration"),t&&(e.style.outline=`1px solid ${t}`)),s&&e.classList.add("xterm-find-active-result-decoration")}_createResultDecorations(e,t,s){const r=[];let n=e.col,o=e.size,a=-this._terminal.buffer.active.baseY-this._terminal.buffer.active.cursorY+e.row;for(;o>0;){const e=Math.min(this._terminal.cols-n,o);r.push([a,n,e]),n=0,o-=e,a++}const l=[];for(const e of r){const r=this._terminal.registerMarker(e[0]),n=this._terminal.registerDecoration({marker:r,x:e[1],width:e[2],backgroundColor:s?t.activeMatchBackground:t.matchBackground,overviewRulerOptions:this._highlightedLines.has(r.line)?void 0:{color:s?t.activeMatchColorOverviewRuler:t.matchOverviewRuler,position:"center"}});if(n){const e=[];e.push(r),e.push(n.onRender((e=>this._applyStyles(e,s?t.activeMatchBorder:t.matchBorder,!1)))),e.push(n.onDispose((()=>(0,i.dispose)(e)))),l.push(n)}}return 0===l.length?void 0:l}}t.DecorationManager=r},615:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SearchEngine=void 0,t.SearchEngine=class{constructor(e,t){this._terminal=e,this._lineCache=t}find(e,t,s,i){if(!e||0===e.length)return void this._terminal.clearSelection();if(s>this._terminal.cols)throw new Error(`Invalid col: ${s} to search in terminal of ${this._terminal.cols} cols`);this._lineCache.initLinesCache();const r={startRow:t,startCol:s};let n=this._findInLine(e,r,i);if(!n)for(let s=t+1;s=0&&(a.startRow=s,l=this._findInLine(e,a,t,o),!l);s--);}if(!l&&r!==this._terminal.buffer.active.baseY+this._terminal.rows-1)for(let s=this._terminal.buffer.active.baseY+this._terminal.rows-1;s>=r&&(a.startRow=s,l=this._findInLine(e,a,t,o),!l);s--);return l}_isWholeWord(e,t,s){return(0===e||" ~!@#$%^&*()+`-=[]{}|\\;:\"',./<>?".includes(t[e-1]))&&(e+s.length===t.length||" ~!@#$%^&*()+`-=[]{}|\\;:\"',./<>?".includes(t[e+s.length]))}_findInLine(e,t,s={},i=!1){const r=t.startRow,n=t.startCol,o=this._terminal.buffer.active.getLine(r);if(o?.isWrapped)return i?void(t.startCol+=this._terminal.cols):(t.startRow--,t.startCol+=this._terminal.cols,this._findInLine(e,t,s));let a=this._lineCache.getLineFromCache(r);a||(a=this._lineCache.translateBufferLineToStringWithWrap(r,!0),this._lineCache.setLineInCache(r,a));const[l,h]=a,c=this._bufferColsToStringOffset(r,n);let u=e,d=l;s.regex||(u=s.caseSensitive?e:e.toLowerCase(),d=s.caseSensitive?l:l.toLowerCase());let f=-1;if(s.regex){const t=RegExp(u,s.caseSensitive?"g":"gi");let r;if(i)for(;r=t.exec(d.slice(0,c));)f=t.lastIndex-r[0].length,e=r[0],t.lastIndex-=e.length-1;else r=t.exec(d.slice(c)),r&&r[0].length>0&&(f=c+(t.lastIndex-r[0].length),e=r[0])}else i?c-u.length>=0&&(f=d.lastIndexOf(u,c-u.length)):f=d.indexOf(u,c);if(f>=0){if(s.wholeWord&&!this._isWholeWord(f,d,e))return;let t=0;for(;t=h[t+1];)t++;let i=t;for(;i=h[i+1];)i++;const n=f-h[t],o=f+e.length-h[i],a=this._stringLengthToBufferSize(r+t,n);return{term:e,col:a,row:r+t,size:this._stringLengthToBufferSize(r+i,o)-a+this._terminal.cols*(i-t)}}}_stringLengthToBufferSize(e,t){const s=this._terminal.buffer.active.getLine(e);if(!s)return 0;for(let e=0;e1&&(t-=r.length-1);const n=s.getCell(e+1);n&&0===n.getWidth()&&t++}return t}_bufferColsToStringOffset(e,t){let s=e,i=0,r=this._terminal.buffer.active.getLine(s);for(;t>0&&r;){for(let e=0;e{Object.defineProperty(t,"__esModule",{value:!0}),t.SearchLineCache=void 0;const i=s(540),r=s(940);class n extends i.Disposable{constructor(e){super(),this._terminal=e,this._linesCacheTimeout=this._register(new i.MutableDisposable),this._linesCacheDisposables=this._register(new i.MutableDisposable),this._register((0,i.toDisposable)((()=>this._destroyLinesCache())))}initLinesCache(){this._linesCache||(this._linesCache=new Array(this._terminal.buffer.active.length),this._linesCacheDisposables.value=(0,i.combinedDisposable)(this._terminal.onLineFeed((()=>this._destroyLinesCache())),this._terminal.onCursorMove((()=>this._destroyLinesCache())),this._terminal.onResize((()=>this._destroyLinesCache())))),this._linesCacheTimeout.value=(0,r.disposableTimeout)((()=>this._destroyLinesCache()),15e3)}_destroyLinesCache(){this._linesCache=void 0,this._linesCacheDisposables.clear(),this._linesCacheTimeout.clear()}getLineFromCache(e){return this._linesCache?.[e]}setLineInCache(e,t){this._linesCache&&(this._linesCache[e]=t)}translateBufferLineToStringWithWrap(e,t){const s=[],i=[0];let r=this._terminal.buffer.active.getLine(e);for(;r;){const n=this._terminal.buffer.active.getLine(e+1),o=!!n&&n.isWrapped;let a=r.translateToString(!o&&t);if(o&&n){const e=r.getCell(r.length-1);e&&0===e.getCode()&&1===e.getWidth()&&2===n.getCell(0)?.getWidth()&&(a=a.slice(0,-1))}if(s.push(a),!o)break;i.push(i[i.length-1]+a.length),e++,r=n}return[s.join(""),i]}}t.SearchLineCache=n},438:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SearchResultTracker=void 0;const i=s(276),r=s(540);class n extends r.Disposable{constructor(){super(...arguments),this._searchResults=[],this._onDidChangeResults=this._register(new i.Emitter)}get onDidChangeResults(){return this._onDidChangeResults.event}get searchResults(){return this._searchResults}get selectedDecoration(){return this._selectedDecoration}set selectedDecoration(e){this._selectedDecoration=e}updateResults(e,t){this._searchResults=e.slice(0,t)}clearResults(){this._searchResults=[]}clearSelectedDecoration(){this._selectedDecoration&&(this._selectedDecoration.dispose(),this._selectedDecoration=void 0)}findResultIndex(e){for(let t=0;t{Object.defineProperty(t,"__esModule",{value:!0}),t.SearchState=void 0,t.SearchState=class{get cachedSearchTerm(){return this._cachedSearchTerm}set cachedSearchTerm(e){this._cachedSearchTerm=e}get lastSearchOptions(){return this._lastSearchOptions}set lastSearchOptions(e){this._lastSearchOptions=e}isValidSearchTerm(e){return!!(e&&e.length>0)}didOptionsChange(e){return!this._lastSearchOptions||!!e&&(this._lastSearchOptions.caseSensitive!==e.caseSensitive||this._lastSearchOptions.regex!==e.regex||this._lastSearchOptions.wholeWord!==e.wholeWord)}shouldUpdateHighlighting(e,t){return!!t?.decorations&&(void 0===this._cachedSearchTerm||e!==this._cachedSearchTerm||this.didOptionsChange(t))}clearCachedTerm(){this._cachedSearchTerm=void 0}reset(){this._cachedSearchTerm=void 0,this._lastSearchOptions=void 0}}},732:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Permutation=t.CallbackIterable=t.ArrayQueue=t.booleanComparator=t.numberComparator=t.CompareResult=void 0,t.tail=function(e,t=0){return e[e.length-(1+t)]},t.tail2=function(e){if(0===e.length)throw new Error("Invalid tail call");return[e.slice(0,e.length-1),e[e.length-1]]},t.equals=function(e,t,s=(e,t)=>e===t){if(e===t)return!0;if(!e||!t)return!1;if(e.length!==t.length)return!1;for(let i=0,r=e.length;is(e[i],t)))},t.binarySearch2=n,t.quickSelect=function e(t,s,i){if((t|=0)>=s.length)throw new TypeError("invalid index");const r=s[Math.floor(s.length*Math.random())],n=[],o=[],a=[];for(const e of s){const t=i(e,r);t<0?n.push(e):t>0?o.push(e):a.push(e)}return t{(async()=>{const o=e.length,l=e.slice(0,s).sort(t);for(let h=s,c=Math.min(s+r,o);hs&&await new Promise((e=>setTimeout(e))),n&&n.isCancellationRequested)throw new i.CancellationError;a(e,t,l,h,c)}return l})().then(o,l)}))},t.coalesce=function(e){return e.filter((e=>!!e))},t.coalesceInPlace=function(e){let t=0;for(let s=0;s0},t.distinct=function(e,t=e=>e){const s=new Set;return e.filter((e=>{const i=t(e);return!s.has(i)&&(s.add(i),!0)}))},t.uniqueFilter=function(e){const t=new Set;return s=>{const i=e(s);return!t.has(i)&&(t.add(i),!0)}},t.firstOrDefault=function(e,t){return e.length>0?e[0]:t},t.lastOrDefault=function(e,t){return e.length>0?e[e.length-1]:t},t.commonPrefixLength=function(e,t,s=(e,t)=>e===t){let i=0;for(let r=0,n=Math.min(e.length,t.length);rt;e--)i.push(e);return i},t.index=function(e,t,s){return e.reduce(((e,i)=>(e[t(i)]=s?s(i):i,e)),Object.create(null))},t.insert=function(e,t){return e.push(t),()=>l(e,t)},t.remove=l,t.arrayInsert=function(e,t,s){const i=e.slice(0,t),r=e.slice(t);return i.concat(s,r)},t.shuffle=function(e,t){let s;if("number"==typeof t){let e=t;s=()=>{const t=179426549*Math.sin(e++);return t-Math.floor(t)}}else s=Math.random;for(let t=e.length-1;t>0;t-=1){const i=Math.floor(s()*(t+1)),r=e[t];e[t]=e[i],e[i]=r}},t.pushToStart=function(e,t){const s=e.indexOf(t);s>-1&&(e.splice(s,1),e.unshift(t))},t.pushToEnd=function(e,t){const s=e.indexOf(t);s>-1&&(e.splice(s,1),e.push(t))},t.pushMany=function(e,t){for(const s of t)e.push(s)},t.mapArrayOrNot=function(e,t){return Array.isArray(e)?e.map(t):t(e)},t.asArray=function(e){return Array.isArray(e)?e:[e]},t.getRandomElement=function(e){return e[Math.floor(Math.random()*e.length)]},t.insertInto=h,t.splice=function(e,t,s,i){const r=c(e,t);let n=e.splice(r,s);return void 0===n&&(n=[]),h(e,r,i),n},t.compareBy=function(e,t){return(s,i)=>t(e(s),e(i))},t.tieBreakComparators=function(...e){return(t,s)=>{for(const i of e){const e=i(t,s);if(!u.isNeitherLessOrGreaterThan(e))return e}return u.neitherLessOrGreaterThan}},t.reverseOrder=function(e){return(t,s)=>-e(t,s)};const i=s(577),r=s(411);function n(e,t){let s=0,i=e-1;for(;s<=i;){const e=(s+i)/2|0,r=t(e);if(r<0)s=e+1;else{if(!(r>0))return e;i=e-1}}return-(s+1)}function o(e,t,s){const i=[];function r(e,t,s){if(0===t&&0===s.length)return;const r=i[i.length-1];r&&r.start+r.deleteCount===e?(r.deleteCount+=t,r.toInsert.push(...s)):i.push({start:e,deleteCount:t,toInsert:s})}let n=0,o=0;for(;;){if(n===e.length){r(n,0,t.slice(o));break}if(o===t.length){r(n,e.length-n,[]);break}const i=e[n],a=t[o],l=s(i,a);0===l?(n+=1,o+=1):l<0?(r(n,1,[]),n+=1):l>0&&(r(n,0,[a]),o+=1)}return i}function a(e,t,s,i,n){for(const o=s.length;it(n,e)<0));s.splice(e,0,n)}}}function l(e,t){const s=e.indexOf(t);if(s>-1)return e.splice(s,1),t}function h(e,t,s){const i=c(e,t),r=e.length,n=s.length;e.length=r+n;for(let t=r-1;t>=i;t--)e[t+n]=e[t];for(let t=0;t0},e.isNeitherLessOrGreaterThan=function(e){return 0===e},e.greaterThan=1,e.lessThan=-1,e.neitherLessOrGreaterThan=0}(u||(t.CompareResult=u={})),t.numberComparator=(e,t)=>e-t,t.booleanComparator=(e,s)=>(0,t.numberComparator)(e?1:0,s?1:0),t.ArrayQueue=class{constructor(e){this.items=e,this.firstIdx=0,this.lastIdx=this.items.length-1}get length(){return this.lastIdx-this.firstIdx+1}takeWhile(e){let t=this.firstIdx;for(;t=0&&e(this.items[t]);)t--;const s=t===this.lastIdx?null:this.items.slice(t+1,this.lastIdx+1);return this.lastIdx=t,s}peek(){if(0!==this.length)return this.items[this.firstIdx]}peekLast(){if(0!==this.length)return this.items[this.lastIdx]}dequeue(){const e=this.items[this.firstIdx];return this.firstIdx++,e}removeLast(){const e=this.items[this.lastIdx];return this.lastIdx--,e}takeCount(e){const t=this.items.slice(this.firstIdx,this.firstIdx+e);return this.firstIdx+=e,t}};class d{static{this.empty=new d((e=>{}))}constructor(e){this.iterate=e}forEach(e){this.iterate((t=>(e(t),!0)))}toArray(){const e=[];return this.iterate((t=>(e.push(t),!0))),e}filter(e){return new d((t=>this.iterate((s=>!e(s)||t(s)))))}map(e){return new d((t=>this.iterate((s=>t(e(s))))))}some(e){let t=!1;return this.iterate((s=>(t=e(s),!t))),t}findFirst(e){let t;return this.iterate((s=>!e(s)||(t=s,!1))),t}findLast(e){let t;return this.iterate((s=>(e(s)&&(t=s),!0))),t}findLastMaxBy(e){let t,s=!0;return this.iterate((i=>((s||u.isGreaterThan(e(i,t)))&&(s=!1,t=i),!0))),t}}t.CallbackIterable=d;class f{constructor(e){this._indexMap=e}static createSortPermutation(e,t){const s=Array.from(e.keys()).sort(((s,i)=>t(e[s],e[i])));return new f(s)}apply(e){return e.map(((t,s)=>e[this._indexMap[s]]))}inverse(){const e=this._indexMap.slice();for(let t=0;t{function s(e,t,s=e.length-1){for(let i=s;i>=0;i--)if(t(e[i]))return i;return-1}function i(e,t,s=0,i=e.length){let r=s,n=i;for(;r=0&&(s=r)}return s},t.findFirstMin=function(e,t){return o(e,((e,s)=>-t(e,s)))},t.findMaxIdx=function(e,t){if(0===e.length)return-1;let s=0;for(let i=1;i0&&(s=i);return s},t.mapFindFirst=function(e,t){for(const s of e){const e=t(s);if(void 0!==e)return e}};class n{static{this.assertInvariants=!1}constructor(e){this._array=e,this._findLastMonotonousLastIdx=0}findLastMonotonous(e){if(n.assertInvariants){if(this._prevFindLastPredicate)for(const t of this._array)if(this._prevFindLastPredicate(t)&&!e(t))throw new Error("MonotonousArray: current predicate must be weaker than (or equal to) the previous predicate.");this._prevFindLastPredicate=e}const t=i(this._array,e,this._findLastMonotonousLastIdx);return this._findLastMonotonousLastIdx=t+1,-1===t?void 0:this._array[t]}}function o(e,t){if(0===e.length)return;let s=e[0];for(let i=1;i0&&(s=r)}return s}t.MonotonousArray=n},940:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.AsyncIterableSource=t.CancelableAsyncIterableObject=t.AsyncIterableObject=t.LazyStatefulPromise=t.StatefulPromise=t.Promises=t.DeferredPromise=t.IntervalCounter=t.TaskSequentializer=t.GlobalIdleValue=t.AbstractIdleValue=t._runWhenIdle=t.runWhenGlobalIdle=t.ThrottledWorker=t.RunOnceWorker=t.ProcessTimeRunOnceScheduler=t.RunOnceScheduler=t.IntervalTimer=t.TimeoutTimer=t.LimitedQueue=t.Queue=t.Limiter=t.AutoOpenBarrier=t.Barrier=t.ThrottledDelayer=t.Delayer=t.SequencerByKey=t.Sequencer=t.Throttler=void 0,t.isThenable=c,t.createCancelablePromise=u,t.raceCancellation=function(e,t,s){return new Promise(((i,r)=>{const n=t.onCancellationRequested((()=>{n.dispose(),i(s)}));e.then(i,r).finally((()=>n.dispose()))}))},t.raceCancellationError=function(e,t){return new Promise(((s,i)=>{const n=t.onCancellationRequested((()=>{n.dispose(),i(new r.CancellationError)}));e.then(s,i).finally((()=>n.dispose()))}))},t.raceCancellablePromises=async function(e){let t=-1;const s=e.map(((e,s)=>e.then((e=>(t=s,e)))));try{return await Promise.race(s)}finally{e.forEach(((e,s)=>{s!==t&&e.cancel()}))}},t.raceTimeout=function(e,t,s){let i;const r=setTimeout((()=>{i?.(void 0),s?.()}),t);return Promise.race([e.finally((()=>clearTimeout(r))),new Promise((e=>i=e))])},t.asPromise=function(e){return new Promise(((t,s)=>{const i=e();c(i)?i.then(t,s):t(i)}))},t.promiseWithResolvers=d,t.timeout=m,t.disposableTimeout=function(e,t=0,s){const i=setTimeout((()=>{e(),s&&r.dispose()}),t),r=(0,o.toDisposable)((()=>{clearTimeout(i),s?.deleteAndLeak(r)}));return s?.add(r),r},t.sequence=function(e){const t=[];let s=0;const i=e.length;return Promise.resolve(null).then((function r(n){null!=n&&t.push(n);const o=s!!e,s=null){let i=0;const r=e.length,n=()=>{if(i>=r)return Promise.resolve(s);const o=e[i++];return Promise.resolve(o()).then((e=>t(e)?Promise.resolve(e):n()))};return n()},t.firstParallel=function(e,t=e=>!!e,s=null){if(0===e.length)return Promise.resolve(s);let i=e.length;const r=()=>{i=-1;for(const t of e)t.cancel?.()};return new Promise(((n,o)=>{for(const a of e)a.then((e=>{--i>=0&&t(e)?(r(),n(e)):0===i&&n(s)})).catch((e=>{--i>=0&&(r(),o(e))}))}))},t.retry=async function(e,t,s){let i;for(let r=0;r{const i=t.token.onCancellationRequested((()=>{i.dispose(),t.dispose(),e.reject(new r.CancellationError)}));try{for await(const i of s){if(t.token.isCancellationRequested)return;e.emitOne(i)}i.dispose(),t.dispose()}catch(s){i.dispose(),t.dispose(),e.reject(s)}}))};const i=s(473),r=s(577),n=s(276),o=s(540),a=s(973),l=s(329),h=s(764);function c(e){return!!e&&"function"==typeof e.then}function u(e){const t=new i.CancellationTokenSource,s=e(t.token),n=new Promise(((e,i)=>{const n=t.token.onCancellationRequested((()=>{n.dispose(),i(new r.CancellationError)}));Promise.resolve(s).then((s=>{n.dispose(),t.dispose(),e(s)}),(e=>{n.dispose(),t.dispose(),i(e)}))}));return new class{cancel(){t.cancel(),t.dispose()}then(e,t){return n.then(e,t)}catch(e){return this.then(void 0,e)}finally(e){return n.finally(e)}}}function d(){let e,t;return{promise:new Promise(((s,i)=>{e=s,t=i})),resolve:e,reject:t}}class f{constructor(){this.isDisposed=!1,this.activePromise=null,this.queuedPromise=null,this.queuedPromiseFactory=null}queue(e){if(this.isDisposed)return Promise.reject(new Error("Throttler is disposed"));if(this.activePromise){if(this.queuedPromiseFactory=e,!this.queuedPromise){const e=()=>{if(this.queuedPromise=null,this.isDisposed)return;const e=this.queue(this.queuedPromiseFactory);return this.queuedPromiseFactory=null,e};this.queuedPromise=new Promise((t=>{this.activePromise.then(e,e).then(t)}))}return new Promise(((e,t)=>{this.queuedPromise.then(e,t)}))}return this.activePromise=e(),new Promise(((e,t)=>{this.activePromise.then((t=>{this.activePromise=null,e(t)}),(e=>{this.activePromise=null,t(e)}))}))}dispose(){this.isDisposed=!0}}t.Throttler=f,t.Sequencer=class{constructor(){this.current=Promise.resolve(null)}queue(e){return this.current=this.current.then((()=>e()),(()=>e()))}},t.SequencerByKey=class{constructor(){this.promiseMap=new Map}queue(e,t){const s=(this.promiseMap.get(e)??Promise.resolve()).catch((()=>{})).then(t).finally((()=>{this.promiseMap.get(e)===s&&this.promiseMap.delete(e)}));return this.promiseMap.set(e,s),s}};class p{constructor(e){this.defaultDelay=e,this.deferred=null,this.completionPromise=null,this.doResolve=null,this.doReject=null,this.task=null}trigger(e,t=this.defaultDelay){this.task=e,this.cancelTimeout(),this.completionPromise||(this.completionPromise=new Promise(((e,t)=>{this.doResolve=e,this.doReject=t})).then((()=>{if(this.completionPromise=null,this.doResolve=null,this.task){const e=this.task;return this.task=null,e()}})));const s=()=>{this.deferred=null,this.doResolve?.(null)};return this.deferred=t===l.MicrotaskDelay?(e=>{let t=!0;return queueMicrotask((()=>{t&&(t=!1,e())})),{isTriggered:()=>t,dispose:()=>{t=!1}}})(s):((e,t)=>{let s=!0;const i=setTimeout((()=>{s=!1,t()}),e);return{isTriggered:()=>s,dispose:()=>{clearTimeout(i),s=!1}}})(t,s),this.completionPromise}isTriggered(){return!!this.deferred?.isTriggered()}cancel(){this.cancelTimeout(),this.completionPromise&&(this.doReject?.(new r.CancellationError),this.completionPromise=null)}cancelTimeout(){this.deferred?.dispose(),this.deferred=null}dispose(){this.cancel()}}t.Delayer=p,t.ThrottledDelayer=class{constructor(e){this.delayer=new p(e),this.throttler=new f}trigger(e,t){return this.delayer.trigger((()=>this.throttler.queue(e)),t)}isTriggered(){return this.delayer.isTriggered()}cancel(){this.delayer.cancel()}dispose(){this.delayer.dispose(),this.throttler.dispose()}};class _{constructor(){this._isOpen=!1,this._promise=new Promise(((e,t)=>{this._completePromise=e}))}isOpen(){return this._isOpen}open(){this._isOpen=!0,this._completePromise(!0)}wait(){return this._promise}}function m(e,t){return t?new Promise(((s,i)=>{const n=setTimeout((()=>{o.dispose(),s()}),e),o=t.onCancellationRequested((()=>{clearTimeout(n),o.dispose(),i(new r.CancellationError)}))})):u((t=>m(e,t)))}t.Barrier=_,t.AutoOpenBarrier=class extends _{constructor(e){super(),this._timeout=setTimeout((()=>this.open()),e)}open(){clearTimeout(this._timeout),super.open()}};class g{constructor(e){this._size=0,this._isDisposed=!1,this.maxDegreeOfParalellism=e,this.outstandingPromises=[],this.runningPromises=0,this._onDrained=new n.Emitter}whenIdle(){return this.size>0?n.Event.toPromise(this.onDrained):Promise.resolve()}get onDrained(){return this._onDrained.event}get size(){return this._size}queue(e){if(this._isDisposed)throw new Error("Object has been disposed");return this._size++,new Promise(((t,s)=>{this.outstandingPromises.push({factory:e,c:t,e:s}),this.consume()}))}consume(){for(;this.outstandingPromises.length&&this.runningPromisesthis.consumed()),(()=>this.consumed()))}}consumed(){this._isDisposed||(this.runningPromises--,0==--this._size&&this._onDrained.fire(),this.outstandingPromises.length>0&&this.consume())}clear(){if(this._isDisposed)throw new Error("Object has been disposed");this.outstandingPromises.length=0,this._size=this.runningPromises}dispose(){this._isDisposed=!0,this.outstandingPromises.length=0,this._size=0,this._onDrained.dispose()}}t.Limiter=g,t.Queue=class extends g{constructor(){super(1)}},t.LimitedQueue=class{constructor(){this.sequentializer=new w,this.tasks=0}queue(e){return this.sequentializer.isRunning()?this.sequentializer.queue((()=>this.sequentializer.run(this.tasks++,e()))):this.sequentializer.run(this.tasks++,e())}},t.TimeoutTimer=class{constructor(e,t){this._isDisposed=!1,this._token=-1,"function"==typeof e&&"number"==typeof t&&this.setIfNotSet(e,t)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){-1!==this._token&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new r.BugIndicatingError("Calling 'cancelAndSet' on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout((()=>{this._token=-1,e()}),t)}setIfNotSet(e,t){if(this._isDisposed)throw new r.BugIndicatingError("Calling 'setIfNotSet' on a disposed TimeoutTimer");-1===this._token&&(this._token=setTimeout((()=>{this._token=-1,e()}),t))}},t.IntervalTimer=class{constructor(){this.disposable=void 0,this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(e,t,s=globalThis){if(this.isDisposed)throw new r.BugIndicatingError("Calling 'cancelAndSet' on a disposed IntervalTimer");this.cancel();const i=s.setInterval((()=>{e()}),t);this.disposable=(0,o.toDisposable)((()=>{s.clearInterval(i),this.disposable=void 0}))}dispose(){this.cancel(),this.isDisposed=!0}};class v{constructor(e,t){this.timeoutToken=-1,this.runner=e,this.timeout=t,this.timeoutHandler=this.onTimeout.bind(this)}dispose(){this.cancel(),this.runner=null}cancel(){this.isScheduled()&&(clearTimeout(this.timeoutToken),this.timeoutToken=-1)}schedule(e=this.timeout){this.cancel(),this.timeoutToken=setTimeout(this.timeoutHandler,e)}get delay(){return this.timeout}set delay(e){this.timeout=e}isScheduled(){return-1!==this.timeoutToken}flush(){this.isScheduled()&&(this.cancel(),this.doRun())}onTimeout(){this.timeoutToken=-1,this.runner&&this.doRun()}doRun(){this.runner?.()}}t.RunOnceScheduler=v,t.ProcessTimeRunOnceScheduler=class{constructor(e,t){t%1e3!=0&&console.warn(`ProcessTimeRunOnceScheduler resolution is 1s, ${t}ms is not a multiple of 1000ms.`),this.runner=e,this.timeout=t,this.counter=0,this.intervalToken=-1,this.intervalHandler=this.onInterval.bind(this)}dispose(){this.cancel(),this.runner=null}cancel(){this.isScheduled()&&(clearInterval(this.intervalToken),this.intervalToken=-1)}schedule(e=this.timeout){e%1e3!=0&&console.warn(`ProcessTimeRunOnceScheduler resolution is 1s, ${e}ms is not a multiple of 1000ms.`),this.cancel(),this.counter=Math.ceil(e/1e3),this.intervalToken=setInterval(this.intervalHandler,1e3)}isScheduled(){return-1!==this.intervalToken}onInterval(){this.counter--,this.counter>0||(clearInterval(this.intervalToken),this.intervalToken=-1,this.runner?.())}},t.RunOnceWorker=class extends v{constructor(e,t){super(e,t),this.units=[]}work(e){this.units.push(e),this.isScheduled()||this.schedule()}doRun(){const e=this.units;this.units=[],this.runner?.(e)}dispose(){this.units=[],super.dispose()}};class b extends o.Disposable{constructor(e,t){super(),this.options=e,this.handler=t,this.pendingWork=[],this.throttler=this._register(new o.MutableDisposable),this.disposed=!1}get pending(){return this.pendingWork.length}work(e){if(this.disposed)return!1;if("number"==typeof this.options.maxBufferedWork)if(this.throttler.value){if(this.pending+e.length>this.options.maxBufferedWork)return!1}else if(this.pending+e.length-this.options.maxWorkChunkSize>this.options.maxBufferedWork)return!1;for(const t of e)this.pendingWork.push(t);return this.throttler.value||this.doWork(),!0}doWork(){this.handler(this.pendingWork.splice(0,this.options.maxWorkChunkSize)),this.pendingWork.length>0&&(this.throttler.value=new v((()=>{this.throttler.clear(),this.doWork()}),this.options.throttleDelay),this.throttler.value.schedule())}dispose(){super.dispose(),this.disposed=!0}}t.ThrottledWorker=b,"function"!=typeof globalThis.requestIdleCallback||"function"!=typeof globalThis.cancelIdleCallback?t._runWhenIdle=(e,t)=>{(0,a.setTimeout0)((()=>{if(s)return;const e=Date.now()+15,i={didTimeout:!0,timeRemaining:()=>Math.max(0,e-Date.now())};t(Object.freeze(i))}));let s=!1;return{dispose(){s||(s=!0)}}}:t._runWhenIdle=(e,t,s)=>{const i=e.requestIdleCallback(t,"number"==typeof s?{timeout:s}:void 0);let r=!1;return{dispose(){r||(r=!0,e.cancelIdleCallback(i))}}},t.runWhenGlobalIdle=e=>(0,t._runWhenIdle)(globalThis,e);class y{constructor(e,s){this._didRun=!1,this._executor=()=>{try{this._value=s()}catch(e){this._error=e}finally{this._didRun=!0}},this._handle=(0,t._runWhenIdle)(e,(()=>this._executor()))}dispose(){this._handle.dispose()}get value(){if(this._didRun||(this._handle.dispose(),this._executor()),this._error)throw this._error;return this._value}get isInitialized(){return this._didRun}}t.AbstractIdleValue=y,t.GlobalIdleValue=class extends y{constructor(e){super(globalThis,e)}};class w{isRunning(e){return"number"==typeof e?this._running?.taskId===e:!!this._running}get running(){return this._running?.promise}cancelRunning(){this._running?.cancel()}run(e,t,s){return this._running={taskId:e,cancel:()=>s?.(),promise:t},t.then((()=>this.doneRunning(e)),(()=>this.doneRunning(e))),t}doneRunning(e){this._running&&e===this._running.taskId&&(this._running=void 0,this.runQueued())}runQueued(){if(this._queued){const e=this._queued;this._queued=void 0,e.run().then(e.promiseResolve,e.promiseReject)}}queue(e){if(this._queued)this._queued.run=e;else{const{promise:t,resolve:s,reject:i}=d();this._queued={run:e,promise:t,promiseResolve:s,promiseReject:i}}return this._queued.promise}hasQueued(){return!!this._queued}async join(){return this._queued?.promise??this._running?.promise}}var D,S,E;t.TaskSequentializer=w,t.IntervalCounter=class{constructor(e,t=()=>Date.now()){this.interval=e,this.nowFn=t,this.lastIncrementTime=0,this.value=0}increment(){const e=this.nowFn();return e-this.lastIncrementTime>this.interval&&(this.lastIncrementTime=e,this.value=0),this.value++,this.value}},function(e){e[e.Resolved=0]="Resolved",e[e.Rejected=1]="Rejected"}(D||(D={}));class k{get isRejected(){return this.outcome?.outcome===D.Rejected}get isResolved(){return this.outcome?.outcome===D.Resolved}get isSettled(){return!!this.outcome}get value(){return this.outcome?.outcome===D.Resolved?this.outcome?.value:void 0}constructor(){this.p=new Promise(((e,t)=>{this.completeCallback=e,this.errorCallback=t}))}complete(e){return new Promise((t=>{this.completeCallback(e),this.outcome={outcome:D.Resolved,value:e},t()}))}error(e){return new Promise((t=>{this.errorCallback(e),this.outcome={outcome:D.Rejected,value:e},t()}))}cancel(){return this.error(new r.CancellationError)}}t.DeferredPromise=k,function(e){e.settled=async function(e){let t;const s=await Promise.all(e.map((e=>e.then((e=>e),(e=>{t||(t=e)})))));if(void 0!==t)throw t;return s},e.withAsyncBody=function(e){return new Promise((async(t,s)=>{try{await e(t,s)}catch(e){s(e)}}))}}(S||(t.Promises=S={}));class C{get value(){return this._value}get error(){return this._error}get isResolved(){return this._isResolved}constructor(e){this._value=void 0,this._error=void 0,this._isResolved=!1,this.promise=e.then((e=>(this._value=e,this._isResolved=!0,e)),(e=>{throw this._error=e,this._isResolved=!0,e}))}requireValue(){if(!this._isResolved)throw new r.BugIndicatingError("Promise is not resolved yet");if(this._error)throw this._error;return this._value}}t.StatefulPromise=C,t.LazyStatefulPromise=class{constructor(e){this._compute=e,this._promise=new h.Lazy((()=>new C(this._compute())))}requireValue(){return this._promise.value.requireValue()}getPromise(){return this._promise.value.promise}get currentValue(){return this._promise.rawValue?.value}},function(e){e[e.Initial=0]="Initial",e[e.DoneOK=1]="DoneOK",e[e.DoneError=2]="DoneError"}(E||(E={}));class L{static fromArray(e){return new L((t=>{t.emitMany(e)}))}static fromPromise(e){return new L((async t=>{t.emitMany(await e)}))}static fromPromises(e){return new L((async t=>{await Promise.all(e.map((async e=>t.emitOne(await e))))}))}static merge(e){return new L((async t=>{await Promise.all(e.map((async e=>{for await(const s of e)t.emitOne(s)})))}))}static{this.EMPTY=L.fromArray([])}constructor(e,t){this._state=E.Initial,this._results=[],this._error=null,this._onReturn=t,this._onStateChanged=new n.Emitter,queueMicrotask((async()=>{const t={emitOne:e=>this.emitOne(e),emitMany:e=>this.emitMany(e),reject:e=>this.reject(e)};try{await Promise.resolve(e(t)),this.resolve()}catch(e){this.reject(e)}finally{t.emitOne=void 0,t.emitMany=void 0,t.reject=void 0}}))}[Symbol.asyncIterator](){let e=0;return{next:async()=>{for(;;){if(this._state===E.DoneError)throw this._error;if(e(this._onReturn?.(),{done:!0,value:void 0})}}static map(e,t){return new L((async s=>{for await(const i of e)s.emitOne(t(i))}))}map(e){return L.map(this,e)}static filter(e,t){return new L((async s=>{for await(const i of e)t(i)&&s.emitOne(i)}))}filter(e){return L.filter(this,e)}static coalesce(e){return L.filter(e,(e=>!!e))}coalesce(){return L.coalesce(this)}static async toPromise(e){const t=[];for await(const s of e)t.push(s);return t}toPromise(){return L.toPromise(this)}emitOne(e){this._state===E.Initial&&(this._results.push(e),this._onStateChanged.fire())}emitMany(e){this._state===E.Initial&&(this._results=this._results.concat(e),this._onStateChanged.fire())}resolve(){this._state===E.Initial&&(this._state=E.DoneOK,this._onStateChanged.fire())}reject(e){this._state===E.Initial&&(this._state=E.DoneError,this._error=e,this._onStateChanged.fire())}}t.AsyncIterableObject=L;class T extends L{constructor(e,t){super(t),this._source=e}cancel(){this._source.cancel()}}t.CancelableAsyncIterableObject=T,t.AsyncIterableSource=class{constructor(e){let t,s;this._deferred=new k,this._asyncIterable=new L((e=>{if(!t)return s&&e.emitMany(s),this._errorFn=t=>e.reject(t),this._emitFn=t=>e.emitOne(t),this._deferred.p;e.reject(t)}),e),this._emitFn=e=>{s||(s=[]),s.push(e)},this._errorFn=e=>{t||(t=e)}}get asyncIterable(){return this._asyncIterable}resolve(){this._deferred.complete()}reject(e){this._errorFn(e),this._deferred.complete()}emitOne(e){this._emitFn(e)}}},473:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CancellationTokenSource=t.CancellationToken=void 0,t.cancelOnDispose=function(e){const t=new a;return e.add({dispose(){t.cancel()}}),t.token};const i=s(276),r=Object.freeze((function(e,t){const s=setTimeout(e.bind(t),0);return{dispose(){clearTimeout(s)}}}));var n;!function(e){e.isCancellationToken=function(t){return t===e.None||t===e.Cancelled||t instanceof o||!(!t||"object"!=typeof t)&&"boolean"==typeof t.isCancellationRequested&&"function"==typeof t.onCancellationRequested},e.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:i.Event.None}),e.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:r})}(n||(t.CancellationToken=n={}));class o{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?r:(this._emitter||(this._emitter=new i.Emitter),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=null)}}class a{constructor(e){this._token=void 0,this._parentListener=void 0,this._parentListener=e&&e.onCancellationRequested(this.cancel,this)}get token(){return this._token||(this._token=new o),this._token}cancel(){this._token?this._token instanceof o&&this._token.cancel():this._token=n.Cancelled}dispose(e=!1){e&&this.cancel(),this._parentListener?.dispose(),this._token?this._token instanceof o&&this._token.dispose():this._token=n.None}}t.CancellationTokenSource=a},33:(e,t)=>{var s;Object.defineProperty(t,"__esModule",{value:!0}),t.SetWithKey=void 0,t.groupBy=function(e,t){const s=Object.create(null);for(const i of e){const e=t(i);let r=s[e];r||(r=s[e]=[]),r.push(i)}return s},t.diffSets=function(e,t){const s=[],i=[];for(const i of e)t.has(i)||s.push(i);for(const s of t)e.has(s)||i.push(s);return{removed:s,added:i}},t.diffMaps=function(e,t){const s=[],i=[];for(const[i,r]of e)t.has(i)||s.push(r);for(const[s,r]of t)e.has(s)||i.push(r);return{removed:s,added:i}},t.intersection=function(e,t){const s=new Set;for(const i of t)e.has(i)&&s.add(i);return s};class i{static{s=Symbol.toStringTag}constructor(e,t){this.toKey=t,this._map=new Map,this[s]="SetWithKey";for(const t of e)this.add(t)}get size(){return this._map.size}add(e){const t=this.toKey(e);return this._map.set(t,e),this}delete(e){return this._map.delete(this.toKey(e))}has(e){return this._map.has(this.toKey(e))}*entries(){for(const e of this._map.values())yield[e,e]}keys(){return this.values()}*values(){for(const e of this._map.values())yield e}clear(){this._map.clear()}forEach(e,t){this._map.forEach((s=>e.call(t,s,s,this)))}[Symbol.iterator](){return this.values()}}t.SetWithKey=i},577:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BugIndicatingError=t.ErrorNoTelemetry=t.ExpectedError=t.NotSupportedError=t.NotImplementedError=t.ReadonlyError=t.CancellationError=t.errorHandler=t.ErrorHandler=void 0,t.setUnexpectedErrorHandler=function(e){t.errorHandler.setUnexpectedErrorHandler(e)},t.isSigPipeError=function(e){if(!e||"object"!=typeof e)return!1;const t=e;return"EPIPE"===t.code&&"WRITE"===t.syscall?.toUpperCase()},t.onUnexpectedError=function(e){r(e)||t.errorHandler.onUnexpectedError(e)},t.onUnexpectedExternalError=function(e){r(e)||t.errorHandler.onUnexpectedExternalError(e)},t.transformErrorForSerialization=function(e){if(e instanceof Error){const{name:t,message:s}=e;return{$isError:!0,name:t,message:s,stack:e.stacktrace||e.stack,noTelemetry:c.isErrorNoTelemetry(e)}}return e},t.transformErrorFromSerialization=function(e){let t;return e.noTelemetry?t=new c:(t=new Error,t.name=e.name),t.message=e.message,t.stack=e.stack,t},t.isCancellationError=r,t.canceled=function(){const e=new Error(i);return e.name=e.message,e},t.illegalArgument=function(e){return e?new Error(`Illegal argument: ${e}`):new Error("Illegal argument")},t.illegalState=function(e){return e?new Error(`Illegal state: ${e}`):new Error("Illegal state")},t.getErrorMessage=function(e){return e?e.message?e.message:e.stack?e.stack.split("\n")[0]:String(e):"Error"};class s{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout((()=>{if(e.stack){if(c.isErrorNoTelemetry(e))throw new c(e.message+"\n\n"+e.stack);throw new Error(e.message+"\n\n"+e.stack)}throw e}),0)}}addListener(e){return this.listeners.push(e),()=>{this._removeListener(e)}}emit(e){this.listeners.forEach((t=>{t(e)}))}_removeListener(e){this.listeners.splice(this.listeners.indexOf(e),1)}setUnexpectedErrorHandler(e){this.unexpectedErrorHandler=e}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(e){this.unexpectedErrorHandler(e),this.emit(e)}onUnexpectedExternalError(e){this.unexpectedErrorHandler(e)}}t.ErrorHandler=s,t.errorHandler=new s;const i="Canceled";function r(e){return e instanceof n||e instanceof Error&&e.name===i&&e.message===i}class n extends Error{constructor(){super(i),this.name=this.message}}t.CancellationError=n;class o extends TypeError{constructor(e){super(e?`${e} is read-only and cannot be changed`:"Cannot change read-only property")}}t.ReadonlyError=o;class a extends Error{constructor(e){super("NotImplemented"),e&&(this.message=e)}}t.NotImplementedError=a;class l extends Error{constructor(e){super("NotSupported"),e&&(this.message=e)}}t.NotSupportedError=l;class h extends Error{constructor(){super(...arguments),this.isExpected=!0}}t.ExpectedError=h;class c extends Error{constructor(e){super(e),this.name="CodeExpectedError"}static fromError(e){if(e instanceof c)return e;const t=new c;return t.message=e.message,t.stack=e.stack,t}static isErrorNoTelemetry(e){return"CodeExpectedError"===e.name}}t.ErrorNoTelemetry=c;class u extends Error{constructor(e){super(e||"An unexpected bug occurred."),Object.setPrototypeOf(this,u.prototype)}}t.BugIndicatingError=u},276:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ValueWithChangeEvent=t.Relay=t.EventBufferer=t.DynamicListEventMultiplexer=t.EventMultiplexer=t.MicrotaskEmitter=t.DebounceEmitter=t.PauseableEmitter=t.AsyncEmitter=t.createEventDeliveryQueue=t.Emitter=t.ListenerRefusalError=t.ListenerLeakError=t.EventProfiling=t.Event=void 0,t.setGlobalLeakWarningThreshold=function(e){const t=c;return c=e,{dispose(){c=t}}};const i=s(577),r=s(355),n=s(540),o=s(711),a=s(79);var l;!function(e){function t(e){return(t,s=null,i)=>{let r,n=!1;return r=e((e=>{if(!n)return r?r.dispose():n=!0,t.call(s,e)}),null,i),n&&r.dispose(),r}}function s(e,t,s){return r(((s,i=null,r)=>e((e=>s.call(i,t(e))),null,r)),s)}function i(e,t,s){return r(((s,i=null,r)=>e((e=>t(e)&&s.call(i,e)),null,r)),s)}function r(e,t){let s;const i=new g({onWillAddFirstListener(){s=e(i.fire,i)},onDidRemoveLastListener(){s?.dispose()}});return t?.add(i),i.event}function o(e,t,s=100,i=!1,r=!1,n,o){let a,l,h,c,u=0;const d=new g({leakWarningThreshold:n,onWillAddFirstListener(){a=e((e=>{u++,l=t(l,e),i&&!h&&(d.fire(l),l=void 0),c=()=>{const e=l;l=void 0,h=void 0,(!i||u>1)&&d.fire(e),u=0},"number"==typeof s?(clearTimeout(h),h=setTimeout(c,s)):void 0===h&&(h=0,queueMicrotask(c))}))},onWillRemoveListener(){r&&u>0&&c?.()},onDidRemoveLastListener(){c=void 0,a.dispose()}});return o?.add(d),d.event}e.None=()=>n.Disposable.None,e.defer=function(e,t){return o(e,(()=>{}),0,void 0,!0,void 0,t)},e.once=t,e.map=s,e.forEach=function(e,t,s){return r(((s,i=null,r)=>e((e=>{t(e),s.call(i,e)}),null,r)),s)},e.filter=i,e.signal=function(e){return e},e.any=function(...e){return(t,s=null,i)=>{return r=(0,n.combinedDisposable)(...e.map((e=>e((e=>t.call(s,e)))))),(o=i)instanceof Array?o.push(r):o&&o.add(r),r;var r,o}},e.reduce=function(e,t,i,r){let n=i;return s(e,(e=>(n=t(n,e),n)),r)},e.debounce=o,e.accumulate=function(t,s=0,i){return e.debounce(t,((e,t)=>e?(e.push(t),e):[t]),s,void 0,!0,void 0,i)},e.latch=function(e,t=(e,t)=>e===t,s){let r,n=!0;return i(e,(e=>{const s=n||!t(e,r);return n=!1,r=e,s}),s)},e.split=function(t,s,i){return[e.filter(t,s,i),e.filter(t,(e=>!s(e)),i)]},e.buffer=function(e,t=!1,s=[],i){let r=s.slice(),n=e((e=>{r?r.push(e):a.fire(e)}));i&&i.add(n);const o=()=>{r?.forEach((e=>a.fire(e))),r=null},a=new g({onWillAddFirstListener(){n||(n=e((e=>a.fire(e))),i&&i.add(n))},onDidAddFirstListener(){r&&(t?setTimeout(o):o())},onDidRemoveLastListener(){n&&n.dispose(),n=null}});return i&&i.add(a),a.event},e.chain=function(e,t){return(s,i,r)=>{const n=t(new l);return e((function(e){const t=n.evaluate(e);t!==a&&s.call(i,t)}),void 0,r)}};const a=Symbol("HaltChainable");class l{constructor(){this.steps=[]}map(e){return this.steps.push(e),this}forEach(e){return this.steps.push((t=>(e(t),t))),this}filter(e){return this.steps.push((t=>e(t)?t:a)),this}reduce(e,t){let s=t;return this.steps.push((t=>(s=e(s,t),s))),this}latch(e=(e,t)=>e===t){let t,s=!0;return this.steps.push((i=>{const r=s||!e(i,t);return s=!1,t=i,r?i:a})),this}evaluate(e){for(const t of this.steps)if((e=t(e))===a)break;return e}}e.fromNodeEventEmitter=function(e,t,s=e=>e){const i=(...e)=>r.fire(s(...e)),r=new g({onWillAddFirstListener:()=>e.on(t,i),onDidRemoveLastListener:()=>e.removeListener(t,i)});return r.event},e.fromDOMEventEmitter=function(e,t,s=e=>e){const i=(...e)=>r.fire(s(...e)),r=new g({onWillAddFirstListener:()=>e.addEventListener(t,i),onDidRemoveLastListener:()=>e.removeEventListener(t,i)});return r.event},e.toPromise=function(e){return new Promise((s=>t(e)(s)))},e.fromPromise=function(e){const t=new g;return e.then((e=>{t.fire(e)}),(()=>{t.fire(void 0)})).finally((()=>{t.dispose()})),t.event},e.forward=function(e,t){return e((e=>t.fire(e)))},e.runAndSubscribe=function(e,t,s){return t(s),e((e=>t(e)))};class h{constructor(e,t){this._observable=e,this._counter=0,this._hasChanged=!1;const s={onWillAddFirstListener:()=>{e.addObserver(this)},onDidRemoveLastListener:()=>{e.removeObserver(this)}};this.emitter=new g(s),t&&t.add(this.emitter)}beginUpdate(e){this._counter++}handlePossibleChange(e){}handleChange(e,t){this._hasChanged=!0}endUpdate(e){this._counter--,0===this._counter&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}e.fromObservable=function(e,t){return new h(e,t).emitter.event},e.fromObservableLight=function(e){return(t,s,i)=>{let r=0,o=!1;const a={beginUpdate(){r++},endUpdate(){r--,0===r&&(e.reportChanges(),o&&(o=!1,t.call(s)))},handlePossibleChange(){},handleChange(){o=!0}};e.addObserver(a),e.reportChanges();const l={dispose(){e.removeObserver(a)}};return i instanceof n.DisposableStore?i.add(l):Array.isArray(i)&&i.push(l),l}}}(l||(t.Event=l={}));class h{static{this.all=new Set}static{this._idPool=0}constructor(e){this.listenerCount=0,this.invocationCount=0,this.elapsedOverall=0,this.durations=[],this.name=`${e}_${h._idPool++}`,h.all.add(this)}start(e){this._stopWatch=new a.StopWatch,this.listenerCount=e}stop(){if(this._stopWatch){const e=this._stopWatch.elapsed();this.durations.push(e),this.elapsedOverall+=e,this.invocationCount+=1,this._stopWatch=void 0}}}t.EventProfiling=h;let c=-1;class u{static{this._idPool=1}constructor(e,t,s=(u._idPool++).toString(16).padStart(3,"0")){this._errorHandler=e,this.threshold=t,this.name=s,this._warnCountdown=0}dispose(){this._stacks?.clear()}check(e,t){const s=this.threshold;if(s<=0||t{const t=this._stacks.get(e.value)||0;this._stacks.set(e.value,t-1)}}getMostFrequentStack(){if(!this._stacks)return;let e,t=0;for(const[s,i]of this._stacks)(!e||t0||this._options?.leakWarningThreshold?new u(e?.onListenerError??i.onUnexpectedError,this._options?.leakWarningThreshold??c):void 0,this._perfMon=this._options?._profName?new h(this._options._profName):void 0,this._deliveryQueue=this._options?.deliveryQueue}dispose(){this._disposed||(this._disposed=!0,this._deliveryQueue?.current===this&&this._deliveryQueue.reset(),this._listeners&&(this._listeners=void 0,this._size=0),this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose())}get event(){return this._event??=(e,t,s)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){const e=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(e);const t=this._leakageMon.getMostFrequentStack()??["UNKNOWN stack",-1],s=new p(`${e}. HINT: Stack shows most frequent listener (${t[1]}-times)`,t[0]);return(this._options?.onListenerError||i.onUnexpectedError)(s),n.Disposable.None}if(this._disposed)return n.Disposable.None;t&&(e=e.bind(t));const r=new m(e);let o;this._leakageMon&&this._size>=Math.ceil(.2*this._leakageMon.threshold)&&(r.stack=d.create(),o=this._leakageMon.check(r.stack,this._size+1)),this._listeners?this._listeners instanceof m?(this._deliveryQueue??=new v,this._listeners=[this._listeners,r]):this._listeners.push(r):(this._options?.onWillAddFirstListener?.(this),this._listeners=r,this._options?.onDidAddFirstListener?.(this)),this._size++;const a=(0,n.toDisposable)((()=>{o?.(),this._removeListener(r)}));return s instanceof n.DisposableStore?s.add(a):Array.isArray(s)&&s.push(a),a},this._event}_removeListener(e){if(this._options?.onWillRemoveListener?.(this),!this._listeners)return;if(1===this._size)return this._listeners=void 0,this._options?.onDidRemoveLastListener?.(this),void(this._size=0);const t=this._listeners,s=t.indexOf(e);if(-1===s)throw console.log("disposed?",this._disposed),console.log("size?",this._size),console.log("arr?",JSON.stringify(this._listeners)),new Error("Attempted to dispose unknown listener");this._size--,t[s]=void 0;const i=this._deliveryQueue.current===this;if(2*this._size<=t.length){let e=0;for(let s=0;s0}}t.Emitter=g,t.createEventDeliveryQueue=()=>new v;class v{constructor(){this.i=-1,this.end=0}enqueue(e,t,s){this.i=0,this.end=s,this.current=e,this.value=t}reset(){this.i=this.end,this.current=void 0,this.value=void 0}}t.AsyncEmitter=class extends g{async fireAsync(e,t,s){if(this._listeners)for(this._asyncDeliveryQueue||(this._asyncDeliveryQueue=new o.LinkedList),((e,t)=>{if(e instanceof m)t(e);else for(let s=0;sthis._asyncDeliveryQueue.push([t.value,e])));this._asyncDeliveryQueue.size>0&&!t.isCancellationRequested;){const[e,r]=this._asyncDeliveryQueue.shift(),n=[],o={...r,token:t,waitUntil:t=>{if(Object.isFrozen(n))throw new Error("waitUntil can NOT be called asynchronous");s&&(t=s(t,e)),n.push(t)}};try{e(o)}catch(e){(0,i.onUnexpectedError)(e);continue}Object.freeze(n),await Promise.allSettled(n).then((e=>{for(const t of e)"rejected"===t.status&&(0,i.onUnexpectedError)(t.reason)}))}}};class b extends g{get isPaused(){return 0!==this._isPaused}constructor(e){super(e),this._isPaused=0,this._eventQueue=new o.LinkedList,this._mergeFn=e?.merge}pause(){this._isPaused++}resume(){if(0!==this._isPaused&&0==--this._isPaused)if(this._mergeFn){if(this._eventQueue.size>0){const e=Array.from(this._eventQueue);this._eventQueue.clear(),super.fire(this._mergeFn(e))}}else for(;!this._isPaused&&0!==this._eventQueue.size;)super.fire(this._eventQueue.shift())}fire(e){this._size&&(0!==this._isPaused?this._eventQueue.push(e):super.fire(e))}}t.PauseableEmitter=b,t.DebounceEmitter=class extends b{constructor(e){super(e),this._delay=e.delay??100}fire(e){this._handle||(this.pause(),this._handle=setTimeout((()=>{this._handle=void 0,this.resume()}),this._delay)),super.fire(e)}},t.MicrotaskEmitter=class extends g{constructor(e){super(e),this._queuedEvents=[],this._mergeFn=e?.merge}fire(e){this.hasListeners()&&(this._queuedEvents.push(e),1===this._queuedEvents.length&&queueMicrotask((()=>{this._mergeFn?super.fire(this._mergeFn(this._queuedEvents)):this._queuedEvents.forEach((e=>super.fire(e))),this._queuedEvents=[]})))}};class y{constructor(){this.hasListeners=!1,this.events=[],this.emitter=new g({onWillAddFirstListener:()=>this.onFirstListenerAdd(),onDidRemoveLastListener:()=>this.onLastListenerRemove()})}get event(){return this.emitter.event}add(e){const t={event:e,listener:null};return this.events.push(t),this.hasListeners&&this.hook(t),(0,n.toDisposable)((0,r.createSingleCallFunction)((()=>{this.hasListeners&&this.unhook(t);const e=this.events.indexOf(t);this.events.splice(e,1)})))}onFirstListenerAdd(){this.hasListeners=!0,this.events.forEach((e=>this.hook(e)))}onLastListenerRemove(){this.hasListeners=!1,this.events.forEach((e=>this.unhook(e)))}hook(e){e.listener=e.event((e=>this.emitter.fire(e)))}unhook(e){e.listener?.dispose(),e.listener=null}dispose(){this.emitter.dispose();for(const e of this.events)e.listener?.dispose();this.events=[]}}t.EventMultiplexer=y,t.DynamicListEventMultiplexer=class{constructor(e,t,s,i){this._store=new n.DisposableStore;const r=this._store.add(new y),o=this._store.add(new n.DisposableMap);function a(e){o.set(e,r.add(i(e)))}for(const t of e)a(t);this._store.add(t((e=>{a(e)}))),this._store.add(s((e=>{o.deleteAndDispose(e)}))),this.event=r.event}dispose(){this._store.dispose()}},t.EventBufferer=class{constructor(){this.data=[]}wrapEvent(e,t,s){return(i,r,n)=>e((e=>{const n=this.data[this.data.length-1];if(!t)return void(n?n.buffers.push((()=>i.call(r,e))):i.call(r,e));const o=n;o?(o.items??=[],o.items.push(e),0===o.buffers.length&&n.buffers.push((()=>{o.reducedResult??=s?o.items.reduce(t,s):o.items.reduce(t),i.call(r,o.reducedResult)}))):i.call(r,t(s,e))}),void 0,n)}bufferEvents(e){const t={buffers:new Array};this.data.push(t);const s=e();return this.data.pop(),t.buffers.forEach((e=>e())),s}},t.Relay=class{constructor(){this.listening=!1,this.inputEvent=l.None,this.inputEventListener=n.Disposable.None,this.emitter=new g({onDidAddFirstListener:()=>{this.listening=!0,this.inputEventListener=this.inputEvent(this.emitter.fire,this.emitter)},onDidRemoveLastListener:()=>{this.listening=!1,this.inputEventListener.dispose()}}),this.event=this.emitter.event}set input(e){this.inputEvent=e,this.listening&&(this.inputEventListener.dispose(),this.inputEventListener=e(this.emitter.fire,this.emitter))}dispose(){this.inputEventListener.dispose(),this.emitter.dispose()}},t.ValueWithChangeEvent=class{static const(e){return new w(e)}constructor(e){this._value=e,this._onDidChange=new g,this.onDidChange=this._onDidChange.event}get value(){return this._value}set value(e){e!==this._value&&(this._value=e,this._onDidChange.fire(void 0))}};class w{constructor(e){this.value=e,this.onDidChange=l.None}}},355:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.createSingleCallFunction=function(e,t){const s=this;let i,r=!1;return function(){if(r)return i;if(r=!0,t)try{i=e.apply(s,arguments)}finally{t()}else i=e.apply(s,arguments);return i}}},956:(e,t)=>{var s;Object.defineProperty(t,"__esModule",{value:!0}),t.Iterable=void 0,function(e){function t(e){return e&&"object"==typeof e&&"function"==typeof e[Symbol.iterator]}e.is=t;const s=Object.freeze([]);function*i(e){yield e}e.empty=function(){return s},e.single=i,e.wrap=function(e){return t(e)?e:i(e)},e.from=function(e){return e||s},e.reverse=function*(e){for(let t=e.length-1;t>=0;t--)yield e[t]},e.isEmpty=function(e){return!e||!0===e[Symbol.iterator]().next().done},e.first=function(e){return e[Symbol.iterator]().next().value},e.some=function(e,t){let s=0;for(const i of e)if(t(i,s++))return!0;return!1},e.find=function(e,t){for(const s of e)if(t(s))return s},e.filter=function*(e,t){for(const s of e)t(s)&&(yield s)},e.map=function*(e,t){let s=0;for(const i of e)yield t(i,s++)},e.flatMap=function*(e,t){let s=0;for(const i of e)yield*t(i,s++)},e.concat=function*(...e){for(const t of e)yield*t},e.reduce=function(e,t,s){let i=s;for(const s of e)i=t(i,s);return i},e.slice=function*(e,t,s=e.length){for(t<0&&(t+=e.length),s<0?s+=e.length:s>e.length&&(s=e.length);tr}]},e.asyncToArray=async function(e){const t=[];for await(const s of e)t.push(s);return Promise.resolve(t)}}(s||(t.Iterable=s={}))},764:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Lazy=void 0,t.Lazy=class{constructor(e){this.executor=e,this._didRun=!1}get hasValue(){return this._didRun}get value(){if(!this._didRun)try{this._value=this.executor()}catch(e){this._error=e}finally{this._didRun=!0}if(this._error)throw this._error;return this._value}get rawValue(){return this._value}}},540:(e,t,s)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DisposableMap=t.ImmortalReference=t.AsyncReferenceCollection=t.ReferenceCollection=t.SafeDisposable=t.RefCountedDisposable=t.MandatoryMutableDisposable=t.MutableDisposable=t.Disposable=t.DisposableStore=t.DisposableTracker=void 0,t.setDisposableTracker=function(e){l=e},t.trackDisposable=c,t.markAsDisposed=u,t.markAsSingleton=function(e){return l?.markAsSingleton(e),e},t.isDisposable=f,t.dispose=p,t.disposeIfDisposable=function(e){for(const t of e)f(t)&&t.dispose();return[]},t.combinedDisposable=function(...e){const t=_((()=>p(e)));return function(e,t){if(l)for(const s of e)l.setParent(s,t)}(e,t),t},t.toDisposable=_,t.disposeOnReturn=function(e){const t=new m;try{e(t)}finally{t.dispose()}};const i=s(732),r=s(33),n=s(714),o=s(355),a=s(956);let l=null;class h{constructor(){this.livingDisposables=new Map}static{this.idx=0}getDisposableData(e){let t=this.livingDisposables.get(e);return t||(t={parent:null,source:null,isSingleton:!1,value:e,idx:h.idx++},this.livingDisposables.set(e,t)),t}trackDisposable(e){const t=this.getDisposableData(e);t.source||(t.source=(new Error).stack)}setParent(e,t){this.getDisposableData(e).parent=t}markAsDisposed(e){this.livingDisposables.delete(e)}markAsSingleton(e){this.getDisposableData(e).isSingleton=!0}getRootParent(e,t){const s=t.get(e);if(s)return s;const i=e.parent?this.getRootParent(this.getDisposableData(e.parent),t):e;return t.set(e,i),i}getTrackedDisposables(){const e=new Map;return[...this.livingDisposables.entries()].filter((([,t])=>null!==t.source&&!this.getRootParent(t,e).isSingleton)).flatMap((([e])=>e))}computeLeakingDisposables(e=10,t){let s;if(t)s=t;else{const e=new Map,t=[...this.livingDisposables.values()].filter((t=>null!==t.source&&!this.getRootParent(t,e).isSingleton));if(0===t.length)return;const i=new Set(t.map((e=>e.value)));if(s=t.filter((e=>!(e.parent&&i.has(e.parent)))),0===s.length)throw new Error("There are cyclic diposable chains!")}if(!s)return;function o(e){const t=e.source.split("\n").map((e=>e.trim().replace("at ",""))).filter((e=>""!==e));return function(e,t){for(;e.length>0&&t.some((t=>"string"==typeof t?t===e[0]:e[0].match(t)));)e.shift()}(t,["Error",/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),t.reverse()}const a=new n.SetMap;for(const e of s){const t=o(e);for(let s=0;s<=t.length;s++)a.add(t.slice(0,s).join("\n"),e)}s.sort((0,i.compareBy)((e=>e.idx),i.numberComparator));let l="",h=0;for(const t of s.slice(0,e)){h++;const e=o(t),i=[];for(let t=0;to(e)[t])),(e=>e));delete h[e[t]];for(const[e,t]of Object.entries(h))i.unshift(` - stacktraces of ${t.length} other leaks continue with ${e}`);i.unshift(n)}l+=`\n\n\n==================== Leaking disposable ${h}/${s.length}: ${t.value.constructor.name} ====================\n${i.join("\n")}\n============================================================\n\n`}return s.length>e&&(l+=`\n\n\n... and ${s.length-e} more leaking disposables\n\n`),{leaks:s,details:l}}}function c(e){return l?.trackDisposable(e),e}function u(e){l?.markAsDisposed(e)}function d(e,t){l?.setParent(e,t)}function f(e){return"object"==typeof e&&null!==e&&"function"==typeof e.dispose&&0===e.dispose.length}function p(e){if(a.Iterable.is(e)){const t=[];for(const s of e)if(s)try{s.dispose()}catch(e){t.push(e)}if(1===t.length)throw t[0];if(t.length>1)throw new AggregateError(t,"Encountered errors while disposing of store");return Array.isArray(e)?[]:e}if(e)return e.dispose(),e}function _(e){const t=c({dispose:(0,o.createSingleCallFunction)((()=>{u(t),e()}))});return t}t.DisposableTracker=h;class m{static{this.DISABLE_DISPOSED_WARNING=!1}constructor(){this._toDispose=new Set,this._isDisposed=!1,c(this)}dispose(){this._isDisposed||(u(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(0!==this._toDispose.size)try{p(this._toDispose)}finally{this._toDispose.clear()}}add(e){if(!e)return e;if(e===this)throw new Error("Cannot register a disposable on itself!");return d(e,this),this._isDisposed?m.DISABLE_DISPOSED_WARNING||console.warn(new Error("Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!").stack):this._toDispose.add(e),e}delete(e){if(e){if(e===this)throw new Error("Cannot dispose a disposable on itself!");this._toDispose.delete(e),e.dispose()}}deleteAndLeak(e){e&&this._toDispose.has(e)&&(this._toDispose.delete(e),d(e,null))}}t.DisposableStore=m;class g{static{this.None=Object.freeze({dispose(){}})}constructor(){this._store=new m,c(this),d(this._store,this)}dispose(){u(this),this._store.dispose()}_register(e){if(e===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(e)}}t.Disposable=g;class v{constructor(){this._isDisposed=!1,c(this)}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),e&&d(e,this),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,u(this),this._value?.dispose(),this._value=void 0}clearAndLeak(){const e=this._value;return this._value=void 0,e&&d(e,null),e}}t.MutableDisposable=v,t.MandatoryMutableDisposable=class{constructor(e){this._disposable=new v,this._isDisposed=!1,this._disposable.value=e}get value(){return this._disposable.value}set value(e){this._isDisposed||e===this._disposable.value||(this._disposable.value=e)}dispose(){this._isDisposed=!0,this._disposable.dispose()}},t.RefCountedDisposable=class{constructor(e){this._disposable=e,this._counter=1}acquire(){return this._counter++,this}release(){return 0==--this._counter&&this._disposable.dispose(),this}},t.SafeDisposable=class{constructor(){this.dispose=()=>{},this.unset=()=>{},this.isset=()=>!1,c(this)}set(e){let t=e;return this.unset=()=>t=void 0,this.isset=()=>void 0!==t,this.dispose=()=>{t&&(t(),t=void 0,u(this))},this}},t.ReferenceCollection=class{constructor(){this.references=new Map}acquire(e,...t){let s=this.references.get(e);s||(s={counter:0,object:this.createReferencedObject(e,...t)},this.references.set(e,s));const{object:i}=s,r=(0,o.createSingleCallFunction)((()=>{0==--s.counter&&(this.destroyReferencedObject(e,s.object),this.references.delete(e))}));return s.counter++,{object:i,dispose:r}}},t.AsyncReferenceCollection=class{constructor(e){this.referenceCollection=e}async acquire(e,...t){const s=this.referenceCollection.acquire(e,...t);try{return{object:await s.object,dispose:()=>s.dispose()}}catch(e){throw s.dispose(),e}}},t.ImmortalReference=class{constructor(e){this.object=e}dispose(){}};class b{constructor(){this._store=new Map,this._isDisposed=!1,c(this)}dispose(){u(this),this._isDisposed=!0,this.clearAndDisposeAll()}clearAndDisposeAll(){if(this._store.size)try{p(this._store.values())}finally{this._store.clear()}}has(e){return this._store.has(e)}get size(){return this._store.size}get(e){return this._store.get(e)}set(e,t,s=!1){this._isDisposed&&console.warn(new Error("Trying to add a disposable to a DisposableMap that has already been disposed of. The added object will be leaked!").stack),s||this._store.get(e)?.dispose(),this._store.set(e,t)}deleteAndDispose(e){this._store.get(e)?.dispose(),this._store.delete(e)}deleteAndLeak(e){const t=this._store.get(e);return this._store.delete(e),t}keys(){return this._store.keys()}values(){return this._store.values()}[Symbol.iterator](){return this._store[Symbol.iterator]()}}t.DisposableMap=b},711:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.LinkedList=void 0;class s{static{this.Undefined=new s(void 0)}constructor(e){this.element=e,this.next=s.Undefined,this.prev=s.Undefined}}class i{constructor(){this._first=s.Undefined,this._last=s.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===s.Undefined}clear(){let e=this._first;for(;e!==s.Undefined;){const t=e.next;e.prev=s.Undefined,e.next=s.Undefined,e=t}this._first=s.Undefined,this._last=s.Undefined,this._size=0}unshift(e){return this._insert(e,!1)}push(e){return this._insert(e,!0)}_insert(e,t){const i=new s(e);if(this._first===s.Undefined)this._first=i,this._last=i;else if(t){const e=this._last;this._last=i,i.prev=e,e.next=i}else{const e=this._first;this._first=i,i.next=e,e.prev=i}this._size+=1;let r=!1;return()=>{r||(r=!0,this._remove(i))}}shift(){if(this._first!==s.Undefined){const e=this._first.element;return this._remove(this._first),e}}pop(){if(this._last!==s.Undefined){const e=this._last.element;return this._remove(this._last),e}}_remove(e){if(e.prev!==s.Undefined&&e.next!==s.Undefined){const t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===s.Undefined&&e.next===s.Undefined?(this._first=s.Undefined,this._last=s.Undefined):e.next===s.Undefined?(this._last=this._last.prev,this._last.next=s.Undefined):e.prev===s.Undefined&&(this._first=this._first.next,this._first.prev=s.Undefined);this._size-=1}*[Symbol.iterator](){let e=this._first;for(;e!==s.Undefined;)yield e.element,e=e.next}}t.LinkedList=i},714:(e,t)=>{var s;Object.defineProperty(t,"__esModule",{value:!0}),t.SetMap=t.BidirectionalMap=t.CounterSet=t.Touch=void 0,t.getOrSet=function(e,t,s){let i=e.get(t);return void 0===i&&(i=s,e.set(t,i)),i},t.mapToString=function(e){const t=[];return e.forEach(((e,s)=>{t.push(`${s} => ${e}`)})),`Map(${e.size}) {${t.join(", ")}}`},t.setToString=function(e){const t=[];return e.forEach((e=>{t.push(e)})),`Set(${e.size}) {${t.join(", ")}}`},t.mapsStrictEqualIgnoreOrder=function(e,t){if(e===t)return!0;if(e.size!==t.size)return!1;for(const[s,i]of e)if(!t.has(s)||t.get(s)!==i)return!1;for(const[s]of t)if(!e.has(s))return!1;return!0},function(e){e[e.None=0]="None",e[e.AsOld=1]="AsOld",e[e.AsNew=2]="AsNew"}(s||(t.Touch=s={})),t.CounterSet=class{constructor(){this.map=new Map}add(e){return this.map.set(e,(this.map.get(e)||0)+1),this}delete(e){let t=this.map.get(e)||0;return 0!==t&&(t--,0===t?this.map.delete(e):this.map.set(e,t),!0)}has(e){return this.map.has(e)}},t.BidirectionalMap=class{constructor(e){if(this._m1=new Map,this._m2=new Map,e)for(const[t,s]of e)this.set(t,s)}clear(){this._m1.clear(),this._m2.clear()}set(e,t){this._m1.set(e,t),this._m2.set(t,e)}get(e){return this._m1.get(e)}getKey(e){return this._m2.get(e)}delete(e){const t=this._m1.get(e);return void 0!==t&&(this._m1.delete(e),this._m2.delete(t),!0)}forEach(e,t){this._m1.forEach(((s,i)=>{e.call(t,s,i,this)}))}keys(){return this._m1.keys()}values(){return this._m1.values()}},t.SetMap=class{constructor(){this.map=new Map}add(e,t){let s=this.map.get(e);s||(s=new Set,this.map.set(e,s)),s.add(t)}delete(e,t){const s=this.map.get(e);s&&(s.delete(t),0===s.size&&this.map.delete(e))}forEach(e,t){const s=this.map.get(e);s&&s.forEach(t)}get(e){return this.map.get(e)||new Set}}},973:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.isAndroid=t.isEdge=t.isSafari=t.isFirefox=t.isChrome=t.OS=t.OperatingSystem=t.setTimeout0=t.setTimeout0IsFaster=t.translationsConfigFile=t.platformLocale=t.locale=t.Language=t.language=t.userAgent=t.platform=t.isCI=t.isMobile=t.isIOS=t.webWorkerOrigin=t.isWebWorker=t.isWeb=t.isElectron=t.isNative=t.isLinuxSnap=t.isLinux=t.isMacintosh=t.isWindows=t.Platform=t.LANGUAGE_DEFAULT=void 0,t.PlatformToString=function(e){switch(e){case w.Web:return"Web";case w.Mac:return"Mac";case w.Linux:return"Linux";case w.Windows:return"Windows"}},t.isLittleEndian=function(){if(!C){C=!0;const e=new Uint8Array(2);e[0]=1,e[1]=2;const t=new Uint16Array(e.buffer);k=513===t[0]}return k},t.isBigSurOrNewer=function(e){return parseFloat(e)>=20},t.LANGUAGE_DEFAULT="en";let s,i,r,n=!1,o=!1,a=!1,l=!1,h=!1,c=!1,u=!1,d=!1,f=!1,p=!1,_=t.LANGUAGE_DEFAULT,m=t.LANGUAGE_DEFAULT;const g=globalThis;let v;void 0!==g.vscode&&void 0!==g.vscode.process?v=g.vscode.process:"undefined"!=typeof process&&"string"==typeof process?.versions?.node&&(v=process);const b="string"==typeof v?.versions?.electron,y=b&&"renderer"===v?.type;if("object"==typeof v){n="win32"===v.platform,o="darwin"===v.platform,a="linux"===v.platform,l=a&&!!v.env.SNAP&&!!v.env.SNAP_REVISION,u=b,f=!!v.env.CI||!!v.env.BUILD_ARTIFACTSTAGINGDIRECTORY,s=t.LANGUAGE_DEFAULT,_=t.LANGUAGE_DEFAULT;const e=v.env.VSCODE_NLS_CONFIG;if(e)try{const r=JSON.parse(e);s=r.userLocale,m=r.osLocale,_=r.resolvedLanguage||t.LANGUAGE_DEFAULT,i=r.languagePack?.translationsConfigFile}catch(e){}h=!0}else"object"!=typeof navigator||y?console.error("Unable to resolve platform."):(r=navigator.userAgent,n=r.indexOf("Windows")>=0,o=r.indexOf("Macintosh")>=0,d=(r.indexOf("Macintosh")>=0||r.indexOf("iPad")>=0||r.indexOf("iPhone")>=0)&&!!navigator.maxTouchPoints&&navigator.maxTouchPoints>0,a=r.indexOf("Linux")>=0,p=r?.indexOf("Mobi")>=0,c=!0,_=globalThis._VSCODE_NLS_LANGUAGE||t.LANGUAGE_DEFAULT,s=navigator.language.toLowerCase(),m=s);var w;!function(e){e[e.Web=0]="Web",e[e.Mac=1]="Mac",e[e.Linux=2]="Linux",e[e.Windows=3]="Windows"}(w||(t.Platform=w={}));let D=w.Web;var S,E;o?D=w.Mac:n?D=w.Windows:a&&(D=w.Linux),t.isWindows=n,t.isMacintosh=o,t.isLinux=a,t.isLinuxSnap=l,t.isNative=h,t.isElectron=u,t.isWeb=c,t.isWebWorker=c&&"function"==typeof g.importScripts,t.webWorkerOrigin=t.isWebWorker?g.origin:void 0,t.isIOS=d,t.isMobile=p,t.isCI=f,t.platform=D,t.userAgent=r,t.language=_,function(e){e.value=function(){return t.language},e.isDefaultVariant=function(){return 2===t.language.length?"en"===t.language:t.language.length>=3&&"e"===t.language[0]&&"n"===t.language[1]&&"-"===t.language[2]},e.isDefault=function(){return"en"===t.language}}(S||(t.Language=S={})),t.locale=s,t.platformLocale=m,t.translationsConfigFile=i,t.setTimeout0IsFaster="function"==typeof g.postMessage&&!g.importScripts,t.setTimeout0=(()=>{if(t.setTimeout0IsFaster){const e=[];g.addEventListener("message",(t=>{if(t.data&&t.data.vscodeScheduleAsyncWork)for(let s=0,i=e.length;s{const i=++t;e.push({id:i,callback:s}),g.postMessage({vscodeScheduleAsyncWork:i},"*")}}return e=>setTimeout(e)})(),function(e){e[e.Windows=1]="Windows",e[e.Macintosh=2]="Macintosh",e[e.Linux=3]="Linux"}(E||(t.OperatingSystem=E={})),t.OS=o||d?E.Macintosh:n?E.Windows:E.Linux;let k=!0,C=!1;t.isChrome=!!(t.userAgent&&t.userAgent.indexOf("Chrome")>=0),t.isFirefox=!!(t.userAgent&&t.userAgent.indexOf("Firefox")>=0),t.isSafari=!!(!t.isChrome&&t.userAgent&&t.userAgent.indexOf("Safari")>=0),t.isEdge=!!(t.userAgent&&t.userAgent.indexOf("Edg/")>=0),t.isAndroid=!!(t.userAgent&&t.userAgent.indexOf("Android")>=0)},79:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.StopWatch=void 0;const s=globalThis.performance&&"function"==typeof globalThis.performance.now;class i{static create(e){return new i(e)}constructor(e){this._now=s&&!1===e?Date.now:globalThis.performance.now.bind(globalThis.performance),this._startTime=this._now(),this._stopTime=-1}stop(){this._stopTime=this._now()}reset(){this._startTime=this._now(),this._stopTime=-1}elapsed(){return-1!==this._stopTime?this._stopTime-this._startTime:this._now()-this._startTime}}t.StopWatch=i},329:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.MicrotaskDelay=void 0,t.MicrotaskDelay=Symbol("MicrotaskDelay")}},t={};function s(i){var r=t[i];if(void 0!==r)return r.exports;var n=t[i]={exports:{}};return e[i](n,n.exports,s),n.exports}var i={};return(()=>{var e=i;Object.defineProperty(e,"__esModule",{value:!0}),e.SearchAddon=void 0;const t=s(540),r=s(940),n=s(149),o=s(772),a=s(615),l=s(864),h=s(438);class c extends t.Disposable{get onDidChangeResults(){return this._resultTracker.onDidChangeResults}constructor(e){super(),this._highlightTimeout=this._register(new t.MutableDisposable),this._lineCache=this._register(new t.MutableDisposable),this._state=new o.SearchState,this._resultTracker=this._register(new h.SearchResultTracker),this._highlightLimit=e?.highlightLimit??1e3}activate(e){this._terminal=e,this._lineCache.value=new n.SearchLineCache(e),this._engine=new a.SearchEngine(e,this._lineCache.value),this._decorationManager=new l.DecorationManager(e),this._register(this._terminal.onWriteParsed((()=>this._updateMatches()))),this._register(this._terminal.onResize((()=>this._updateMatches()))),this._register((0,t.toDisposable)((()=>this.clearDecorations())))}_updateMatches(){this._highlightTimeout.clear(),this._state.cachedSearchTerm&&this._state.lastSearchOptions?.decorations&&(this._highlightTimeout.value=(0,r.disposableTimeout)((()=>{const e=this._state.cachedSearchTerm;this._state.clearCachedTerm(),this.findPrevious(e,{...this._state.lastSearchOptions,incremental:!0},{noScroll:!0})}),200))}clearDecorations(e){this._resultTracker.clearSelectedDecoration(),this._decorationManager?.clearHighlightDecorations(),this._resultTracker.clearResults(),e||this._state.clearCachedTerm()}clearActiveDecoration(){this._resultTracker.clearSelectedDecoration()}findNext(e,t,s){if(!this._terminal||!this._engine)throw new Error("Cannot use addon until it has been loaded");this._state.lastSearchOptions=t,this._state.shouldUpdateHighlighting(e,t)&&this._highlightAllMatches(e,t);const i=this._findNextAndSelect(e,t,s);return this._fireResults(t),this._state.cachedSearchTerm=e,i}_highlightAllMatches(e,t){if(!this._terminal||!this._engine||!this._decorationManager)throw new Error("Cannot use addon until it has been loaded");if(!this._state.isValidSearchTerm(e))return void this.clearDecorations();this.clearDecorations(!0);const s=[];let i,r=this._engine.find(e,0,0,t);for(;r&&(i?.row!==r.row||i?.col!==r.col)&&!(s.length>=this._highlightLimit);)i=r,s.push(i),r=this._engine.find(e,i.col+i.term.length>=this._terminal.cols?i.row+1:i.row,i.col+i.term.length>=this._terminal.cols?0:i.col+1,t);this._resultTracker.updateResults(s,this._highlightLimit),t.decorations&&this._decorationManager.createHighlightDecorations(s,t.decorations)}_findNextAndSelect(e,t,s){if(!this._terminal||!this._engine)return!1;if(!this._state.isValidSearchTerm(e))return this._terminal.clearSelection(),this.clearDecorations(),!1;const i=this._engine.findNextWithSelection(e,t,this._state.cachedSearchTerm);return this._selectResult(i,t?.decorations,s?.noScroll)}findPrevious(e,t,s){if(!this._terminal||!this._engine)throw new Error("Cannot use addon until it has been loaded");this._state.lastSearchOptions=t,this._state.shouldUpdateHighlighting(e,t)&&this._highlightAllMatches(e,t);const i=this._findPreviousAndSelect(e,t,s);return this._fireResults(t),this._state.cachedSearchTerm=e,i}_fireResults(e){this._resultTracker.fireResultsChanged(!!e?.decorations)}_findPreviousAndSelect(e,t,s){if(!this._terminal||!this._engine)return!1;if(!this._state.isValidSearchTerm(e))return this._terminal.clearSelection(),this.clearDecorations(),!1;const i=this._engine.findPreviousWithSelection(e,t,this._state.cachedSearchTerm);return this._selectResult(i,t?.decorations,s?.noScroll)}_selectResult(e,t,s){if(!this._terminal||!this._decorationManager)return!1;if(this._resultTracker.clearSelectedDecoration(),!e)return this._terminal.clearSelection(),!1;if(this._terminal.select(e.col,e.row,e.size),t){const s=this._decorationManager.createActiveDecoration(e,t);s&&(this._resultTracker.selectedDecoration=s)}if(!s&&(e.row>=this._terminal.buffer.active.viewportY+this._terminal.rows||e.row(()=>{"use strict";var e={490:(e,t)=>{function n(e){try{const t=new URL(e),n=t.password&&t.username?`${t.protocol}//${t.username}:${t.password}@${t.host}`:t.username?`${t.protocol}//${t.username}@${t.host}`:`${t.protocol}//${t.host}`;return e.toLocaleLowerCase().startsWith(n.toLocaleLowerCase())}catch(e){return!1}}Object.defineProperty(t,"__esModule",{value:!0}),t.LinkComputer=t.WebLinkProvider=void 0,t.WebLinkProvider=class{constructor(e,t,n,o={}){this._terminal=e,this._regex=t,this._handler=n,this._options=o}provideLinks(e,t){const n=o.computeLink(e,this._regex,this._terminal,this._handler);t(this._addCallbacks(n))}_addCallbacks(e){return e.map((e=>(e.leave=this._options.leave,e.hover=(t,n)=>{if(this._options.hover){const{range:o}=e;this._options.hover(t,n,o)}},e)))}};class o{static computeLink(e,t,r,i){const s=new RegExp(t.source,(t.flags||"")+"g"),[a,c]=o._getWindowedLineStrings(e-1,r),l=a.join("");let d;const p=[];for(;d=s.exec(l);){const e=d[0];if(!n(e))continue;const[t,s]=o._mapStrIdx(r,c,0,d.index),[a,l]=o._mapStrIdx(r,t,s,e.length);if(-1===t||-1===s||-1===a||-1===l)continue;const h={start:{x:s+1,y:t+1},end:{x:l,y:a+1}};p.push({range:h,text:e,activate:i})}return p}static _getWindowedLineStrings(e,t){let n,o=e,r=e,i=0,s="";const a=[];if(n=t.buffer.active.getLine(e)){const e=n.translateToString(!0);if(n.isWrapped&&" "!==e[0]){for(i=0;(n=t.buffer.active.getLine(--o))&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),n.isWrapped&&-1===s.indexOf(" ")););a.reverse()}for(a.push(e),i=0;(n=t.buffer.active.getLine(++r))&&n.isWrapped&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),-1===s.indexOf(" ")););}return[a,o]}static _mapStrIdx(e,t,n,o){const r=e.buffer.active,i=r.getNullCell();let s=n;for(;o;){const e=r.getLine(t);if(!e)return[-1,-1];for(let n=s;n{var e=o;Object.defineProperty(e,"__esModule",{value:!0}),e.WebLinksAddon=void 0;const t=n(490),r=/(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;function i(e,t){const n=window.open();if(n){try{n.opener=null}catch{}n.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}e.WebLinksAddon=class{constructor(e=i,t={}){this._handler=e,this._options=t}activate(e){this._terminal=e;const n=this._options,o=n.urlRegex||r;this._linkProvider=this._terminal.registerLinkProvider(new t.WebLinkProvider(this._terminal,o,this._handler,n))}dispose(){this._linkProvider?.dispose()}}})(),o})())); -//# sourceMappingURL=addon-web-links.js.map \ No newline at end of file diff --git a/frontend/src/js/lib/xterm.min.js b/frontend/src/js/lib/xterm.min.js deleted file mode 100644 index e47e2dd..0000000 --- a/frontend/src/js/lib/xterm.min.js +++ /dev/null @@ -1,2 +0,0 @@ -!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var s in i)("object"==typeof exports?exports:e)[s]=i[s]}}(globalThis,(()=>(()=>{"use strict";var e={2840:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.AccessibilityManager=void 0;const n=i(7721),o=i(4292),a=i(7150),l=i(7098),h=i(6501),c=i(7093);let d=class extends a.Disposable{constructor(e,t,i,s){super(),this._terminal=e,this._coreBrowserService=i,this._renderService=s,this._rowColumns=new WeakMap,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="";const r=this._coreBrowserService.mainDocument;this._accessibilityContainer=r.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=r.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let e=0;ethis._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new o.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize((e=>this._handleResize(e.rows)))),this._register(this._terminal.onRender((e=>this._refreshRows(e.start,e.end)))),this._register(this._terminal.onScroll((()=>this._refreshRows()))),this._register(this._terminal.onA11yChar((e=>this._handleChar(e)))),this._register(this._terminal.onLineFeed((()=>this._handleChar("\n")))),this._register(this._terminal.onA11yTab((e=>this._handleTab(e)))),this._register(this._terminal.onKey((e=>this._handleKey(e.key)))),this._register(this._terminal.onBlur((()=>this._clearLiveRegion()))),this._register(this._renderService.onDimensionsChange((()=>this._refreshRowsDimensions()))),this._register((0,c.addDisposableListener)(r,"selectionchange",(()=>this._handleSelectionChange()))),this._register(this._coreBrowserService.onDprChange((()=>this._refreshRowsDimensions()))),this._refreshRowsDimensions(),this._refreshRows(),this._register((0,a.toDisposable)((()=>{this._accessibilityContainer.remove(),this._rowElements.length=0})))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent+=n.tooMuchOutput.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){const i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){const e=i.lines.get(i.ydisp+r),t=[],n=e?.translateToString(!0,void 0,void 0,t)||"",o=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(0===n.length?(a.textContent=" ",this._rowColumns.set(a,[0,1])):(a.textContent=n,this._rowColumns.set(a,t)),a.setAttribute("aria-posinset",o),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){0!==this._charsToAnnounce.length&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){const i=e.target,s=this._rowElements[0===t?1:this._rowElements.length-2];if(i.getAttribute("aria-posinset")===(0===t?"1":`${this._terminal.buffer.lines.length}`))return;if(e.relatedTarget!==s)return;let r,n;if(0===t?(r=i,n=this._rowElements.pop(),this._rowContainer.removeChild(n)):(r=this._rowElements.shift(),n=i,this._rowContainer.removeChild(r)),r.removeEventListener("focus",this._topBoundaryFocusListener),n.removeEventListener("focus",this._bottomBoundaryFocusListener),0===t){const e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement("afterbegin",e)}else{const e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(0===t?-1:1),this._rowElements[0===t?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(0===this._rowElements.length)return;const e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed)return void(this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection());if(!e.anchorNode||!e.focusNode)return void console.error("anchorNode and/or focusNode are null");let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;const s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;const r=({node:e,offset:t})=>{const i=e instanceof Text?e.parentNode:e;let s=parseInt(i?.getAttribute("aria-posinset"),10)-1;if(isNaN(s))return console.warn("row is invalid. Race condition?"),null;const r=this._rowColumns.get(i);if(!r)return console.warn("columns is null. Race condition?"),null;let n=t=this._terminal.cols&&(++s,n=0),{row:s,column:n}},n=r(t),o=r(i);if(n&&o){if(n.row>o.row||n.row===o.row&&n.column>=o.column)throw new Error("invalid range");this._terminal.select(n.column,n.row,(o.row-n.row)*this._terminal.cols-n.column+o.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;ee;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{function i(e){return e.replace(/\r?\n/g,"\r")}function s(e,t){return t?"[200~"+e+"[201~":e}function r(e,t,r,n){e=s(e=i(e),r.decPrivateModes.bracketedPasteMode&&!0!==n.rawOptions.ignoreBracketedPasteMode),r.triggerDataEvent(e,!0),t.value=""}function n(e,t,i){const s=i.getBoundingClientRect(),r=e.clientX-s.left-10,n=e.clientY-s.top-10;t.style.width="20px",t.style.height="20px",t.style.left=`${r}px`,t.style.top=`${n}px`,t.style.zIndex="1000",t.focus()}Object.defineProperty(t,"__esModule",{value:!0}),t.prepareTextForTerminal=i,t.bracketTextForPaste=s,t.copyHandler=function(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()},t.handlePasteEvent=function(e,t,i,s){e.stopPropagation(),e.clipboardData&&r(e.clipboardData.getData("text/plain"),t,i,s)},t.paste=r,t.moveTextAreaUnderMouseCursor=n,t.rightClickHandler=function(e,t,i,s,r){n(e,t,i),r&&s.rightClickSelect(e),t.value=s.selectionText,t.select()}},7174:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ColorContrastCache=void 0;const s=i(7710);t.ColorContrastCache=class{constructor(){this._color=new s.TwoKeyMap,this._css=new s.TwoKeyMap}setCss(e,t,i){this._css.set(e,t,i)}getCss(e,t){return this._css.get(e,t)}setColor(e,t,i){this._color.set(e,t,i)}getColor(e,t){return this._color.get(e,t)}clear(){this._color.clear(),this._css.clear()}}},1718:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CoreBrowserTerminal=void 0;const s=i(7861),r=i(7721),n=i(3285),o=i(4017),a=i(4196),l=i(9925),h=i(3618),c=i(3955),d=i(4792),u=i(945),_=i(9574),f=i(9820),p=i(9784),g=i(5783),m=i(2079),v=i(7098),S=i(9078),b=i(4103),C=i(5777),y=i(701),w=i(6107),E=i(3534),D=i(706),L=i(8693),R=i(4720),A=i(6501),T=i(2486),k=i(2840),M=i(8906),O=i(802),I=i(7093),P=i(7150);class x extends C.CoreTerminal{get linkifier(){return this._linkifier.value}get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}constructor(e={}){super(e),this._linkifier=this._register(new P.MutableDisposable),this.browser=y,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this._register(new P.MutableDisposable),this._onCursorMove=this._register(new O.Emitter),this.onCursorMove=this._onCursorMove.event,this._onKey=this._register(new O.Emitter),this.onKey=this._onKey.event,this._onRender=this._register(new O.Emitter),this.onRender=this._onRender.event,this._onSelectionChange=this._register(new O.Emitter),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this._register(new O.Emitter),this.onTitleChange=this._onTitleChange.event,this._onBell=this._register(new O.Emitter),this.onBell=this._onBell.event,this._onFocus=this._register(new O.Emitter),this._onBlur=this._register(new O.Emitter),this._onA11yCharEmitter=this._register(new O.Emitter),this._onA11yTabEmitter=this._register(new O.Emitter),this._onWillOpen=this._register(new O.Emitter),this._setup(),this._decorationService=this._instantiationService.createInstance(R.DecorationService),this._instantiationService.setService(A.IDecorationService,this._decorationService),this._linkProviderService=this._instantiationService.createInstance(f.LinkProviderService),this._instantiationService.setService(v.ILinkProviderService,this._linkProviderService),this._linkProviderService.registerLinkProvider(this._instantiationService.createInstance(n.OscLinkProvider)),this._register(this._inputHandler.onRequestBell((()=>this._onBell.fire()))),this._register(this._inputHandler.onRequestRefreshRows((e=>this.refresh(e?.start??0,e?.end??this.rows-1)))),this._register(this._inputHandler.onRequestSendFocus((()=>this._reportFocus()))),this._register(this._inputHandler.onRequestReset((()=>this.reset()))),this._register(this._inputHandler.onRequestWindowsOptionsReport((e=>this._reportWindowsOptions(e)))),this._register(this._inputHandler.onColor((e=>this._handleColorEvent(e)))),this._register(O.Event.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(O.Event.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(O.Event.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(O.Event.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize((e=>this._afterResize(e.cols,e.rows)))),this._register((0,P.toDisposable)((()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)})))}_handleColorEvent(e){if(this._themeService)for(const t of e){let e,i="";switch(t.index){case 256:e="foreground",i="10";break;case 257:e="background",i="11";break;case 258:e="cursor",i="12";break;default:e="ansi",i="4;"+t.index}switch(t.type){case 0:const s=b.color.toColorRGB("ansi"===e?this._themeService.colors.ansi[t.index]:this._themeService.colors[e]);this.coreService.triggerDataEvent(`${E.C0.ESC}]${i};${(0,L.toRgbString)(s)}${E.C1_ESCAPED.ST}`);break;case 1:if("ansi"===e)this._themeService.modifyColors((e=>e.ansi[t.index]=b.channels.toColor(...t.color)));else{const i=e;this._themeService.modifyColors((e=>e[i]=b.channels.toColor(...t.color)))}break;case 2:this._themeService.restoreColor(t.index)}}}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(k.AccessibilityManager,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(E.C0.ESC+"[I"),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()}blur(){return this.textarea?.blur()}_handleTextAreaBlur(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(E.C0.ESC+"[O"),this.element.classList.remove("focus"),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;const e=this.buffer.ybase+this.buffer.y,t=this.buffer.lines.get(e);if(!t)return;const i=Math.min(this.buffer.x,this.cols-1),s=this._renderService.dimensions.css.cell.height,r=t.getWidth(i),n=this._renderService.dimensions.css.cell.width*r,o=this.buffer.y*this._renderService.dimensions.css.cell.height,a=i*this._renderService.dimensions.css.cell.width;this.textarea.style.left=a+"px",this.textarea.style.top=o+"px",this.textarea.style.width=n+"px",this.textarea.style.height=s+"px",this.textarea.style.lineHeight=s+"px",this.textarea.style.zIndex="-5"}_initGlobal(){this._bindKeys(),this._register((0,I.addDisposableListener)(this.element,"copy",(e=>{this.hasSelection()&&(0,s.copyHandler)(e,this._selectionService)})));const e=e=>(0,s.handlePasteEvent)(e,this.textarea,this.coreService,this.optionsService);this._register((0,I.addDisposableListener)(this.textarea,"paste",e)),this._register((0,I.addDisposableListener)(this.element,"paste",e)),y.isFirefox?this._register((0,I.addDisposableListener)(this.element,"mousedown",(e=>{2===e.button&&(0,s.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)}))):this._register((0,I.addDisposableListener)(this.element,"contextmenu",(e=>{(0,s.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)}))),y.isLinux&&this._register((0,I.addDisposableListener)(this.element,"auxclick",(e=>{1===e.button&&(0,s.moveTextAreaUnderMouseCursor)(e,this.textarea,this.screenElement)})))}_bindKeys(){this._register((0,I.addDisposableListener)(this.textarea,"keyup",(e=>this._keyUp(e)),!0)),this._register((0,I.addDisposableListener)(this.textarea,"keydown",(e=>this._keyDown(e)),!0)),this._register((0,I.addDisposableListener)(this.textarea,"keypress",(e=>this._keyPress(e)),!0)),this._register((0,I.addDisposableListener)(this.textarea,"compositionstart",(()=>this._compositionHelper.compositionstart()))),this._register((0,I.addDisposableListener)(this.textarea,"compositionupdate",(e=>this._compositionHelper.compositionupdate(e)))),this._register((0,I.addDisposableListener)(this.textarea,"compositionend",(()=>this._compositionHelper.compositionend()))),this._register((0,I.addDisposableListener)(this.textarea,"input",(e=>this._inputEvent(e)),!0)),this._register(this.onRender((()=>this._compositionHelper.updateCompositionElements())))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService)return void(this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView));this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),e.appendChild(this.element);const t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register((0,I.addDisposableListener)(this.screenElement,"mousemove",(e=>this.updateCursorStyle(e)))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);const i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",r.promptLabel.get()),y.isChromeOS||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",(()=>i.readOnly=this.optionsService.rawOptions.disableStdin))),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(_.CoreBrowserService,this.textarea,e.ownerDocument.defaultView??window,this._document??"undefined"!=typeof window?window.document:null)),this._instantiationService.setService(v.ICoreBrowserService,this._coreBrowserService),this._register((0,I.addDisposableListener)(this.textarea,"focus",(e=>this._handleTextAreaFocus(e)))),this._register((0,I.addDisposableListener)(this.textarea,"blur",(()=>this._handleTextAreaBlur()))),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(d.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(v.ICharSizeService,this._charSizeService),this._themeService=this._instantiationService.createInstance(S.ThemeService),this._instantiationService.setService(v.IThemeService,this._themeService),this._characterJoinerService=this._instantiationService.createInstance(u.CharacterJoinerService),this._instantiationService.setService(v.ICharacterJoinerService,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(g.RenderService,this.rows,this.screenElement)),this._instantiationService.setService(v.IRenderService,this._renderService),this._register(this._renderService.onRenderedViewportChange((e=>this._onRender.fire(e)))),this.onResize((e=>this._renderService.resize(e.cols,e.rows))),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(h.CompositionHelper,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseService=this._instantiationService.createInstance(p.MouseService),this._instantiationService.setService(v.IMouseService,this._mouseService);const s=this._linkifier.value=this._register(this._instantiationService.createInstance(M.Linkifier,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch{}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove((()=>{this._renderService.handleCursorMove(),this._syncTextArea()}))),this._register(this.onResize((()=>this._renderService.handleResize(this.cols,this.rows)))),this._register(this.onBlur((()=>this._renderService.handleBlur()))),this._register(this.onFocus((()=>this._renderService.handleFocus()))),this._viewport=this._register(this._instantiationService.createInstance(o.Viewport,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines((e=>{super.scrollLines(e,!1),this.refresh(0,this.rows-1)}))),this._selectionService=this._register(this._instantiationService.createInstance(m.SelectionService,this.element,this.screenElement,s)),this._instantiationService.setService(v.ISelectionService,this._selectionService),this._register(this._selectionService.onRequestScrollLines((e=>this.scrollLines(e.amount,e.suppressScrollEvent)))),this._register(this._selectionService.onSelectionChange((()=>this._onSelectionChange.fire()))),this._register(this._selectionService.onRequestRedraw((e=>this._renderService.handleSelectionChanged(e.start,e.end,e.columnSelectMode)))),this._register(this._selectionService.onLinuxMouseSelection((e=>{this.textarea.value=e,this.textarea.focus(),this.textarea.select()}))),this._register(O.Event.any(this._onScroll.event,this._inputHandler.onScroll)((()=>{this._selectionService.refresh(),this._viewport?.queueSync()}))),this._register(this._instantiationService.createInstance(a.BufferDecorationRenderer,this.screenElement)),this._register((0,I.addDisposableListener)(this.element,"mousedown",(e=>this._selectionService.handleMouseDown(e)))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(k.AccessibilityManager,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",(e=>this._handleScreenReaderModeOptionChange(e)))),this.options.overviewRuler.width&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(l.OverviewRulerRenderer,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("overviewRuler",(e=>{!this._overviewRulerRenderer&&e&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(l.OverviewRulerRenderer,this._viewportElement,this.screenElement)))})),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(c.DomRenderer,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}bindMouse(){const e=this,t=this.element;function i(t){const i=e._mouseService.getMouseReportCoords(t,e.screenElement);if(!i)return!1;let s,r;switch(t.overrideType||t.type){case"mousemove":r=32,void 0===t.buttons?(s=3,void 0!==t.button&&(s=t.button<3?t.button:3)):s=1&t.buttons?0:4&t.buttons?1:2&t.buttons?2:3;break;case"mouseup":r=0,s=t.button<3?t.button:3;break;case"mousedown":r=1,s=t.button<3?t.button:3;break;case"wheel":if(e._customWheelEventHandler&&!1===e._customWheelEventHandler(t))return!1;const i=t.deltaY;if(0===i)return!1;if(0===e.coreMouseService.consumeWheelEvent(t,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr))return!1;r=i<0?0:1,s=4;break;default:return!1}return!(void 0===r||void 0===s||s>4)&&e.coreMouseService.triggerMouseEvent({col:i.col,row:i.row,x:i.x,y:i.y,button:s,action:r,ctrl:t.ctrlKey,alt:t.altKey,shift:t.shiftKey})}const s={mouseup:null,wheel:null,mousedrag:null,mousemove:null},r={mouseup:e=>(i(e),e.buttons||(this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag)),this.cancel(e)),wheel:e=>(i(e),this.cancel(e,!0)),mousedrag:e=>{e.buttons&&i(e)},mousemove:e=>{e.buttons||i(e)}};this._register(this.coreMouseService.onProtocolChange((e=>{e?("debug"===this.optionsService.rawOptions.logLevel&&this._logService.debug("Binding to mouse events:",this.coreMouseService.explainEvents(e)),this.element.classList.add("enable-mouse-events"),this._selectionService.disable()):(this._logService.debug("Unbinding from mouse events."),this.element.classList.remove("enable-mouse-events"),this._selectionService.enable()),8&e?s.mousemove||(t.addEventListener("mousemove",r.mousemove),s.mousemove=r.mousemove):(t.removeEventListener("mousemove",s.mousemove),s.mousemove=null),16&e?s.wheel||(t.addEventListener("wheel",r.wheel,{passive:!1}),s.wheel=r.wheel):(t.removeEventListener("wheel",s.wheel),s.wheel=null),2&e?s.mouseup||(s.mouseup=r.mouseup):(this._document.removeEventListener("mouseup",s.mouseup),s.mouseup=null),4&e?s.mousedrag||(s.mousedrag=r.mousedrag):(this._document.removeEventListener("mousemove",s.mousedrag),s.mousedrag=null)}))),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this._register((0,I.addDisposableListener)(t,"mousedown",(e=>{if(e.preventDefault(),this.focus(),this.coreMouseService.areMouseEventsActive&&!this._selectionService.shouldForceSelection(e))return i(e),s.mouseup&&this._document.addEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.addEventListener("mousemove",s.mousedrag),this.cancel(e)}))),this._register((0,I.addDisposableListener)(t,"wheel",(t=>{if(!s.wheel){if(this._customWheelEventHandler&&!1===this._customWheelEventHandler(t))return!1;if(!this.buffer.hasScrollback){if(0===t.deltaY)return!1;if(0===e.coreMouseService.consumeWheelEvent(t,e._renderService?.dimensions?.device?.cell?.height,e._coreBrowserService?.dpr))return this.cancel(t,!0);const i=E.C0.ESC+(this.coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(t.deltaY<0?"A":"B");return this.coreService.triggerDataEvent(i,!0),this.cancel(t,!0)}}}),{passive:!1}))}refresh(e,t){this._renderService?.refreshRows(e,t)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}paste(e){(0,s.paste)(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this._customWheelEventHandler=e}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");const t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return!!this._selectionService&&this._selectionService.hasSelection}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(this._selectionService&&this._selectionService.hasSelection)return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;const t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;t||"Dead"!==e.key&&"AltGraph"!==e.key||(this._unprocessedDeadKey=!0);const i=(0,D.evaluateKeyboardEvent)(e,this.coreService.decPrivateModes.applicationCursorKeys,this.browser.isMac,this.options.macOptionIsMeta);if(this.updateCursorStyle(e),3===i.type||2===i.type){const t=this.rows-1;return this.scrollLines(2===i.type?-t:t),this.cancel(e,!0)}return 1===i.type&&this.selectAll(),!!this._isThirdLevelShift(this.browser,e)||(i.cancel&&this.cancel(e,!0),!i.key||!!(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&1===e.key.length&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)||(this._unprocessedDeadKey?(this._unprocessedDeadKey=!1,!0):(i.key!==E.C0.ETX&&i.key!==E.C0.CR||(this.textarea.value=""),this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!0),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey?this.cancel(e,!0):void(this._keyDownHandled=!0))))}_isThirdLevelShift(e,t){const i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return"keypress"===t.type?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){this._keyDownSeen=!1,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e)||(function(e){return 16===e.keyCode||17===e.keyCode||18===e.keyCode}(e)||this.focus(),this.updateCursorStyle(e),this._keyPressHandled=!1)}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled)return!1;if(this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;if(this.cancel(e),e.charCode)t=e.charCode;else if(null===e.which||void 0===e.which)t=e.keyCode;else{if(0===e.which||0===e.charCode)return!1;t=e.which}return!(!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)||(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,0))}_inputEvent(e){if(e.data&&"insertText"===e.inputType&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;const t=e.data;return this.coreService.triggerDataEvent(t,!0),this.cancel(e),!0}return!1}resize(e,t){e!==this.cols||t!==this.rows?super.resize(e,t):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()}_afterResize(e,t){this._charSizeService?.measure()}clear(){if(0!==this.buffer.ybase||0!==this.buffer.y){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Linkifier=void 0;const n=i(7150),o=i(6501),a=i(7098),l=i(802),h=i(7093);let c=class extends n.Disposable{get currentLink(){return this._currentLink}constructor(e,t,i,s,r){super(),this._element=e,this._mouseService=t,this._renderService=i,this._bufferService=s,this._linkProviderService=r,this._linkCacheDisposables=[],this._isMouseOut=!0,this._wasResized=!1,this._activeLine=-1,this._onShowLinkUnderline=this._register(new l.Emitter),this.onShowLinkUnderline=this._onShowLinkUnderline.event,this._onHideLinkUnderline=this._register(new l.Emitter),this.onHideLinkUnderline=this._onHideLinkUnderline.event,this._register((0,n.toDisposable)((()=>{(0,n.dispose)(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()}))),this._register(this._bufferService.onResize((()=>{this._clearCurrentLink(),this._wasResized=!0}))),this._register((0,h.addDisposableListener)(this._element,"mouseleave",(()=>{this._isMouseOut=!0,this._clearCurrentLink()}))),this._register((0,h.addDisposableListener)(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register((0,h.addDisposableListener)(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register((0,h.addDisposableListener)(this._element,"mouseup",this._handleMouseUp.bind(this)))}_handleMouseMove(e){this._lastMouseEvent=e;const t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!t)return;this._isMouseOut=!1;const i=e.composedPath();for(let e=0;e{e?.forEach((e=>{e.link.dispose&&e.link.dispose()}))})),this._activeProviderReplies=new Map,this._activeLine=e.y);let i=!1;for(const[s,r]of this._linkProviderService.linkProviders.entries())if(t){const t=this._activeProviderReplies?.get(s);t&&(i=this._checkLinkProviderResult(s,e,i))}else r.provideLinks(e.y,(t=>{if(this._isMouseOut)return;const r=t?.map((e=>({link:e})));this._activeProviderReplies?.set(s,r),i=this._checkLinkProviderResult(s,e,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)}))}_removeIntersectingLinks(e,t){const i=new Set;for(let s=0;se?this._bufferService.cols:s.link.range.end.x;for(let e=n;e<=o;e++){if(i.has(e)){r.splice(t--,1);break}i.add(e)}}}}_checkLinkProviderResult(e,t,i){if(!this._activeProviderReplies)return i;const s=this._activeProviderReplies.get(e);let r=!1;for(let t=0;tthis._linkAtPosition(e.link,t)));e&&(i=!0,this._handleNewLink(e))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let e=0;ethis._linkAtPosition(e.link,t)));if(s){i=!0,this._handleNewLink(s);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;const t=this._positionFromMouseEvent(e,this._element,this._mouseService);var i,s;t&&this._mouseDownLink&&(i=this._mouseDownLink.link,s=this._currentLink.link,i.text===s.text&&i.range.start.x===s.range.start.x&&i.range.start.y===s.range.start.y&&i.range.end.x===s.range.end.x&&i.range.end.y===s.range.end.y)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){this._currentLink&&this._lastMouseEvent&&(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,(0,n.dispose)(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;const t=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:void 0===e.link.decorations||e.link.decorations.underline,pointerCursor:void 0===e.link.decorations||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:e=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==e&&(this._currentLink.state.decorations.pointerCursor=e,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",e))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:t=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==t&&(this._currentLink.state.decorations.underline=t,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,t))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange((e=>{if(!this._currentLink)return;const t=0===e.start?0:e.start+1+this._bufferService.buffer.ydisp,i=this._bufferService.buffer.ydisp+1+e.end;if(this._currentLink.link.range.start.y>=t&&this._currentLink.link.range.end.y<=i&&(this._clearCurrentLink(t,i),this._lastMouseEvent)){const e=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);e&&this._askForLink(e,!1)}}))))}_linkHover(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){const i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){const i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t,i){const s=i.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(s)return{x:s[0],y:s[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};t.Linkifier=c,t.Linkifier=c=s([r(1,a.IMouseService),r(2,a.IRenderService),r(3,o.IBufferService),r(4,a.ILinkProviderService)],c)},7721:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.tooMuchOutput=t.promptLabel=void 0;let i="Terminal input";const s={get:()=>i,set:e=>i=e};t.promptLabel=s;let r="Too much output to announce, navigate to rows manually to read";const n={get:()=>r,set:e=>r=e};t.tooMuchOutput=n},3285:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkProvider=void 0;const n=i(3055),o=i(6501);let a=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i}provideLinks(e,t){const i=this._bufferService.buffer.lines.get(e-1);if(!i)return void t(void 0);const s=[],r=this._optionsService.rawOptions.linkHandler,o=new n.CellData,a=i.getTrimmedLength();let h=-1,c=-1,d=!1;for(let t=0;tr?r.activate(e,t,n):l(0,t),hover:(e,t)=>r?.hover?.(e,t,n),leave:(e,t)=>r?.leave?.(e,t,n)})}d=!1,o.hasExtendedAttrs()&&o.extended.urlId?(c=t,h=o.extended.urlId):(c=-1,h=-1)}}t(s)}};function l(e,t){if(confirm(`Do you want to navigate to ${t}?\n\nWARNING: This link could potentially be dangerous`)){const e=window.open();if(e){try{e.opener=null}catch{}e.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}t.OscLinkProvider=a,t.OscLinkProvider=a=s([r(0,o.IBufferService),r(1,o.IOptionsService),r(2,o.IOscLinkService)],a)},4852:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.RenderDebouncer=void 0,t.RenderDebouncer=class{constructor(e,t){this._renderCallback=e,this._coreBrowserService=t,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(e){return this._refreshCallbacks.push(e),this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._innerRefresh()))),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=void 0!==e?e:0,t=void 0!==t?t:this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t,this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._innerRefresh())))}_innerRefresh(){if(this._animationFrame=void 0,void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return void this._runRefreshCallbacks();const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(const e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}}},4292:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.TimeBasedDebouncer=void 0,t.TimeBasedDebouncer=class{constructor(e,t=1e3){this._renderCallback=e,this._debounceThresholdMS=t,this._lastRefreshMs=0,this._additionalRefreshRequested=!1}dispose(){this._refreshTimeoutID&&clearTimeout(this._refreshTimeoutID)}refresh(e,t,i){this._rowCount=i,e=void 0!==e?e:0,t=void 0!==t?t:this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t;const s=performance.now();if(s-this._lastRefreshMs>=this._debounceThresholdMS)this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){const e=s-this._lastRefreshMs,t=this._debounceThresholdMS-e;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout((()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0}),t)}}_innerRefresh(){if(void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return;const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}}},9302:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_ANSI_COLORS=void 0;const s=i(4103);t.DEFAULT_ANSI_COLORS=Object.freeze((()=>{const e=[s.css.toColor("#2e3436"),s.css.toColor("#cc0000"),s.css.toColor("#4e9a06"),s.css.toColor("#c4a000"),s.css.toColor("#3465a4"),s.css.toColor("#75507b"),s.css.toColor("#06989a"),s.css.toColor("#d3d7cf"),s.css.toColor("#555753"),s.css.toColor("#ef2929"),s.css.toColor("#8ae234"),s.css.toColor("#fce94f"),s.css.toColor("#729fcf"),s.css.toColor("#ad7fa8"),s.css.toColor("#34e2e2"),s.css.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){const r=t[i/36%6|0],n=t[i/6%6|0],o=t[i%6];e.push({css:s.channels.toCss(r,n,o),rgba:s.channels.toRgba(r,n,o)})}for(let t=0;t<24;t++){const i=8+10*t;e.push({css:s.channels.toCss(i,i,i),rgba:s.channels.toRgba(i,i,i)})}return e})())},4017:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Viewport=void 0;const n=i(7098),o=i(7150),a=i(6501),l=i(7093),h=i(8234),c=i(802),d=i(9881);let u=class extends o.Disposable{constructor(e,t,i,s,r,n,a,u){super(),this._bufferService=i,this._optionsService=a,this._renderService=u,this._onRequestScrollLines=this._register(new c.Emitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1;const _=this._register(new d.Scrollable({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:e=>(0,l.scheduleAtNextAnimationFrame)(s.window,e)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",(()=>{_.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)}))),this._scrollableElement=this._register(new h.SmoothScrollableElement(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,...this._getChangeOptions()},_)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","overviewRuler"],(()=>this._scrollableElement.updateOptions(this._getChangeOptions())))),this._register(r.onProtocolChange((e=>{this._scrollableElement.updateOptions({handleMouseWheel:!(16&e)})}))),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(c.Event.runAndSubscribe(n.onChangeColors,(()=>{this._scrollableElement.getDomNode().style.backgroundColor=n.colors.background.css}))),e.appendChild(this._scrollableElement.getDomNode()),this._register((0,o.toDisposable)((()=>this._scrollableElement.getDomNode().remove()))),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register((0,o.toDisposable)((()=>this._styleElement.remove()))),this._register(c.Event.runAndSubscribe(n.onChangeColors,(()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .scrollbar > .slider {",` background: ${n.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider:hover {",` background: ${n.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .scrollbar > .slider.active {",` background: ${n.colors.scrollbarSliderActiveBackground.css};`,"}"].join("\n")}))),this._register(this._bufferService.onResize((()=>this.queueSync()))),this._register(this._bufferService.buffers.onBufferActivate((()=>{this._latestYDisp=void 0,this.queueSync()}))),this._register(this._bufferService.onScroll((()=>this._sync()))),this._register(this._scrollableElement.onScroll((e=>this._handleScroll(e))))}scrollLines(e){const t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,verticalScrollbarSize:this._optionsService.rawOptions.overviewRuler?.width||14}}queueSync(e){void 0!==e&&(this._latestYDisp=e),void 0===this._queuedAnimationFrame&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback((()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)})))}_sync(e=this._bufferService.buffer.ydisp){this._renderService&&!this._isSyncing&&(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1)}_handleScroll(e){if(!this._renderService)return;if(this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;const t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),i=t-this._bufferService.buffer.ydisp;0!==i&&(this._latestYDisp=t,this._onRequestScrollLines.fire(i)),this._isHandlingScroll=!1}};t.Viewport=u,t.Viewport=u=s([r(2,a.IBufferService),r(3,n.ICoreBrowserService),r(4,a.ICoreMouseService),r(5,n.IThemeService),r(6,a.IOptionsService),r(7,n.IRenderService)],u)},4196:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferDecorationRenderer=void 0;const n=i(7098),o=i(7150),a=i(6501);let l=class extends o.Disposable{constructor(e,t,i,s,r){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=i,this._decorationService=s,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange((()=>this._doRefreshDecorations()))),this._register(this._renderService.onDimensionsChange((()=>{this._dimensionsChanged=!0,this._queueRefresh()}))),this._register(this._coreBrowserService.onDprChange((()=>this._queueRefresh()))),this._register(this._bufferService.buffers.onBufferActivate((()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt}))),this._register(this._decorationService.onDecorationRegistered((()=>this._queueRefresh()))),this._register(this._decorationService.onDecorationRemoved((e=>this._removeDecoration(e)))),this._register((0,o.toDisposable)((()=>{this._container.remove(),this._decorationElements.clear()})))}_queueRefresh(){void 0===this._animationFrame&&(this._animationFrame=this._renderService.addRefreshCallback((()=>{this._doRefreshDecorations(),this._animationFrame=void 0})))}_doRefreshDecorations(){for(const e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){const t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer","top"===e?.options?.layer),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",t.style.top=(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height+"px",t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;const i=e.options.x??0;return i&&i>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){const t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose((()=>{this._decorationElements.delete(e),i.remove()}))),i.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(i.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,i.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",i.style.top=t*this._renderService.dimensions.css.cell.height+"px",i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){if(!t)return;const i=e.options.x??0;"right"===(e.options.anchor||"left")?t.style.right=i?i*this._renderService.dimensions.css.cell.width+"px":"":t.style.left=i?i*this._renderService.dimensions.css.cell.width+"px":""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};t.BufferDecorationRenderer=l,t.BufferDecorationRenderer=l=s([r(1,a.IBufferService),r(2,n.ICoreBrowserService),r(3,a.IDecorationService),r(4,n.IRenderService)],l)},957:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ColorZoneStore=void 0,t.ColorZoneStore=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(const t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position))return void this._addLineToZone(t,e.marker.line)}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}}},9925:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OverviewRulerRenderer=void 0;const n=i(957),o=i(7098),a=i(7150),l=i(6501),h={full:0,left:0,center:0,right:0},c={full:0,left:0,center:0,right:0},d={full:0,left:0,center:0,right:0};let u=class extends a.Disposable{get _width(){return this._optionsService.options.overviewRuler?.width||0}constructor(e,t,i,s,r,o,l,h){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=o,this._themeService=l,this._coreBrowserService=h,this._colorZoneStore=new n.ColorZoneStore,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register((0,a.toDisposable)((()=>this._canvas?.remove())));const c=this._canvas.getContext("2d");if(!c)throw new Error("Ctx cannot be null");this._ctx=c,this._register(this._decorationService.onDecorationRegistered((()=>this._queueRefresh(void 0,!0)))),this._register(this._decorationService.onDecorationRemoved((()=>this._queueRefresh(void 0,!0)))),this._register(this._renderService.onRenderedViewportChange((()=>this._queueRefresh()))),this._register(this._bufferService.buffers.onBufferActivate((()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"}))),this._register(this._bufferService.onScroll((()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())}))),this._register(this._renderService.onRender((()=>{this._containerHeight&&this._containerHeight===this._screenElement.clientHeight||(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)}))),this._register(this._coreBrowserService.onDprChange((()=>this._queueRefresh(!0)))),this._register(this._optionsService.onSpecificOptionChange("overviewRuler",(()=>this._queueRefresh(!0)))),this._register(this._themeService.onChangeColors((()=>this._queueRefresh()))),this._queueRefresh(!0)}_refreshDrawConstants(){const e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);c.full=this._canvas.width,c.left=e,c.center=t,c.right=e,this._refreshDrawHeightConstants(),d.full=1,d.left=1,d.center=1+c.left,d.right=1+c.left+c.center}_refreshDrawHeightConstants(){h.full=Math.round(2*this._coreBrowserService.dpr);const e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);h.left=t,h.center=t,h.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*h.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*h.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*h.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*h.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowserService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(const e of this._decorationService.decorations)this._colorZoneStore.addDecoration(e);this._ctx.lineWidth=1,this._renderRulerOutline();const e=this._colorZoneStore.zones;for(const t of e)"full"!==t.position&&this._renderColorZone(t);for(const t of e)"full"===t.position&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.overviewRuler.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.overviewRuler.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(d[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-h[e.position||"full"]/2),c[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+h[e.position||"full"]))}_queueRefresh(e,t){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,void 0===this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>{this._refreshDecorations(),this._animationFrame=void 0})))}};t.OverviewRulerRenderer=u,t.OverviewRulerRenderer=u=s([r(2,l.IBufferService),r(3,l.IDecorationService),r(4,o.IRenderService),r(5,l.IOptionsService),r(6,o.IThemeService),r(7,o.ICoreBrowserService)],u)},3618:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CompositionHelper=void 0;const n=i(7098),o=i(6501),a=i(3534);let l=class{get isComposing(){return this._isComposing}constructor(e,t,i,s,r,n){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=n,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=""}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout((()=>{this._compositionPosition.end=this._textarea.value.length}),0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(20===e.keyCode||229===e.keyCode)return!1;if(16===e.keyCode||17===e.keyCode||18===e.keyCode)return!1;this._finalizeComposition(!1)}return 229!==e.keyCode||(this._handleAnyTextareaChanges(),!1)}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){const e={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout((()=>{if(this._isSendingComposition){let t;this._isSendingComposition=!1,e.start+=this._dataAlreadySent.length,t=this._isComposing?this._textarea.value.substring(e.start,this._compositionPosition.start):this._textarea.value.substring(e.start),t.length>0&&this._coreService.triggerDataEvent(t,!0)}}),0)}else{this._isSendingComposition=!1;const e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(e,!0)}}_handleAnyTextareaChanges(){const e=this._textarea.value;setTimeout((()=>{if(!this._isComposing){const t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0)),0)}}};t.CompositionHelper=l,t.CompositionHelper=l=s([r(2,o.IBufferService),r(3,o.IOptionsService),r(4,o.ICoreService),r(5,n.IRenderService)],l)},5251:(e,t)=>{function i(e,t,i){const s=i.getBoundingClientRect(),r=e.getComputedStyle(i),n=parseInt(r.getPropertyValue("padding-left")),o=parseInt(r.getPropertyValue("padding-top"));return[t.clientX-s.left-n,t.clientY-s.top-o]}Object.defineProperty(t,"__esModule",{value:!0}),t.getCoordsRelativeToElement=i,t.getCoords=function(e,t,s,r,n,o,a,l,h){if(!o)return;const c=i(e,t,s);return c?(c[0]=Math.ceil((c[0]+(h?a/2:0))/a),c[1]=Math.ceil(c[1]/l),c[0]=Math.min(Math.max(c[0],1),r+(h?1:0)),c[1]=Math.min(Math.max(c[1],1),n),c):void 0}},9686:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.moveToCellSequence=function(e,t,i,s){const o=i.buffer.x,c=i.buffer.y;if(!i.buffer.hasScrollback)return function(e,t,i,s,o,c){return 0===r(t,s,o,c).length?"":h(a(e,t,e,t-n(t,o),!1,o).length,l("D",c))}(o,c,0,t,i,s)+r(c,t,i,s)+function(e,t,i,s,o,c){let d;d=r(t,s,o,c).length>0?s-n(s,o):t;const u=s,_=function(e,t,i,s,o,a){let l;return l=r(i,s,o,a).length>0?s-n(s,o):t,e=i&&le?"D":"C",h(Math.abs(o-e),l(d,s));d=c>t?"D":"C";const u=Math.abs(c-t);return h(function(e,t){return t.cols-e}(c>t?e:o,i)+(u-1)*i.cols+1+((c>t?o:e)-1),l(d,s))};const s=i(3534);function r(e,t,i,s){const r=e-n(e,i),a=t-n(t,i),c=Math.abs(r-a)-function(e,t,i){let s=0;const r=e-n(e,i),a=t-n(t,i);for(let n=0;n=0&&et?"A":"B"}function a(e,t,i,s,r,n){let o=e,a=t,l="";for(;(o!==i||a!==s)&&a>=0&&an.cols-1?(l+=n.buffer.translateBufferLineToString(a,!1,e,o),o=0,e=0,a++):!r&&o<0&&(l+=n.buffer.translateBufferLineToString(a,!1,0,e+1),o=n.cols-1,e=o,a--);return l+n.buffer.translateBufferLineToString(a,!1,e,o)}function l(e,t){const i=t?"O":"[";return s.C0.ESC+i+e}function h(e,t){e=Math.floor(e);let i="";for(let s=0;s=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRenderer=void 0;const n=i(1433),o=i(2744),a=i(9176),l=i(6181),h=i(2274),c=i(7098),d=i(4103),u=i(7150),_=i(6501),f=i(802),p="xterm-dom-renderer-owner-",g="xterm-rows",m="xterm-fg-",v="xterm-bg-",S="xterm-focus",b="xterm-selection";let C=1,y=class extends u.Disposable{constructor(e,t,i,s,r,a,c,d,_,m,v,S,y,w){super(),this._terminal=e,this._document=t,this._element=i,this._screenElement=s,this._viewportElement=r,this._helperContainer=a,this._linkifier2=c,this._charSizeService=_,this._optionsService=m,this._bufferService=v,this._coreService=S,this._coreBrowserService=y,this._themeService=w,this._terminalClass=C++,this._rowElements=[],this._selectionRenderModel=(0,h.createSelectionRenderModel)(),this.onRequestRedraw=this._register(new f.Emitter).event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add(g),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add(b),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=(0,l.createRenderDimensions)(),this._updateDimensions(),this._register(this._optionsService.onOptionChange((()=>this._handleOptionsChanged()))),this._register(this._themeService.onChangeColors((e=>this._injectCss(e)))),this._injectCss(this._themeService.colors),this._rowFactory=d.createInstance(n.DomRendererRowFactory,document),this._element.classList.add(p+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline((e=>this._handleLinkHover(e)))),this._register(this._linkifier2.onHideLinkUnderline((e=>this._handleLinkLeave(e)))),this._register((0,u.toDisposable)((()=>{this._element.classList.remove(p+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()}))),this._widthCache=new o.WidthCache(this._document,this._helperContainer),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){const e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(const e of this._rowElements)e.style.width=`${this.dimensions.css.canvas.width}px`,e.style.height=`${this.dimensions.css.cell.height}px`,e.style.lineHeight=`${this.dimensions.css.cell.height}px`,e.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));const t=`${this._terminalSelector} .${g} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .${g} { pointer-events: none; color: ${e.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;t+=`${this._terminalSelector} .${g} .xterm-dim { color: ${d.color.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`;const i=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,r=`blink_block_${this._terminalClass}`;t+=`@keyframes ${i} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${r} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .${g}.${S} .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .${g}.${S} .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .${g}.${S} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .${g} .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .${g} .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .${g} .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .${g} .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .${g} .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .${b} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${b} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${b} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(const[i,s]of e.ansi.entries())t+=`${this._terminalSelector} .${m}${i} { color: ${s.css}; }${this._terminalSelector} .${m}${i}.xterm-dim { color: ${d.color.multiplyOpacity(s,.5).css}; }${this._terminalSelector} .${v}${i} { background-color: ${s.css}; }`;t+=`${this._terminalSelector} .${m}${a.INVERTED_DEFAULT_COLOR} { color: ${d.color.opaque(e.background).css}; }${this._terminalSelector} .${m}${a.INVERTED_DEFAULT_COLOR}.xterm-dim { color: ${d.color.multiplyOpacity(d.color.opaque(e.background),.5).css}; }${this._terminalSelector} .${v}${a.INVERTED_DEFAULT_COLOR} { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){const e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let e=this._rowElements.length;e<=t;e++){const e=this._document.createElement("div");this._rowContainer.appendChild(e),this._rowElements.push(e)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(S),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add(S),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,t,i){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i),this.renderRows(0,this._bufferService.rows-1),!e||!t)return;if(this._selectionRenderModel.update(this._terminal,e,t,i),!this._selectionRenderModel.hasSelection)return;const s=this._selectionRenderModel.viewportStartRow,r=this._selectionRenderModel.viewportEndRow,n=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow,a=this._document.createDocumentFragment();if(i){const i=e[0]>t[0];a.appendChild(this._createSelectionElement(n,i?t[0]:e[0],i?e[0]:t[0],o-n+1))}else{const i=s===n?e[0]:0,l=n===r?t[0]:this._bufferService.cols;a.appendChild(this._createSelectionElement(n,i,l));const h=o-n-1;if(a.appendChild(this._createSelectionElement(n+1,0,this._bufferService.cols,h)),n!==o){const e=r===o?t[0]:this._bufferService.cols;a.appendChild(this._createSelectionElement(o,0,e))}}this._selectionContainer.appendChild(a)}_createSelectionElement(e,t,i,s=1){const r=this._document.createElement("div"),n=t*this.dimensions.css.cell.width;let o=this.dimensions.css.cell.width*(i-t);return n+o>this.dimensions.css.canvas.width&&(o=this.dimensions.css.canvas.width-n),r.style.height=s*this.dimensions.css.cell.height+"px",r.style.top=e*this.dimensions.css.cell.height+"px",r.style.left=`${n}px`,r.style.width=`${o}px`,r}handleCursorMove(){}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(const e of this._rowElements)e.replaceChildren()}renderRows(e,t){const i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),n=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,o=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,a=this._optionsService.rawOptions.cursorInactiveStyle;for(let l=e;l<=t;l++){const e=l+i.ydisp,t=this._rowElements[l],h=i.lines.get(e);if(!t||!h)break;t.replaceChildren(...this._rowFactory.createRow(h,e,e===s,o,a,r,n,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${p}${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,n){i<0&&(e=0),s<0&&(t=0);const o=this._bufferService.rows-1;i=Math.max(Math.min(i,o),0),s=Math.max(Math.min(s,o),0),r=Math.min(r,this._bufferService.cols);const a=this._bufferService.buffer,l=a.ybase+a.y,h=Math.min(a.x,r-1),c=this._optionsService.rawOptions.cursorBlink,d=this._optionsService.rawOptions.cursorStyle,u=this._optionsService.rawOptions.cursorInactiveStyle;for(let o=i;o<=s;++o){const _=o+a.ydisp,f=this._rowElements[o],p=a.lines.get(_);if(!f||!p)break;f.replaceChildren(...this._rowFactory.createRow(p,_,_===l,d,u,h,c,this.dimensions.css.cell.width,this._widthCache,n?o===i?e:0:-1,n?(o===s?t:r)-1:-1))}}};t.DomRenderer=y,t.DomRenderer=y=s([r(7,_.IInstantiationService),r(8,c.ICharSizeService),r(9,_.IOptionsService),r(10,_.IBufferService),r(11,_.ICoreService),r(12,c.ICoreBrowserService),r(13,c.IThemeService)],y)},1433:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRendererRowFactory=void 0;const n=i(9176),o=i(8938),a=i(3055),l=i(6501),h=i(4103),c=i(7098),d=i(945),u=i(6181),_=i(5451);let f=class{constructor(e,t,i,s,r,n,o){this._document=e,this._characterJoinerService=t,this._optionsService=i,this._coreBrowserService=s,this._coreService=r,this._decorationService=n,this._themeService=o,this._workCell=new a.CellData,this._columnSelectMode=!1,this.defaultSpacing=0}handleSelectionChanged(e,t,i){this._selectionStart=e,this._selectionEnd=t,this._columnSelectMode=i}createRow(e,t,i,s,r,a,l,c,u,f,g){const m=[],v=this._characterJoinerService.getJoinedCharacters(t),S=this._themeService.colors;let b,C=e.getNoBgTrimmedLength();i&&C=O,U=x,F=this._workCell;if(v.length>0&&x===v[0][0]&&N){const s=v.shift(),r=this._isCellInSelection(s[0],t);for(E=s[0]+1;E=s[1],N?(B=!0,F=new d.JoinedCellData(this._workCell,e.translateToString(!0,s[0],s[1]),s[1]-s[0]),U=s[1]-1,C=F.getWidth()):O=s[1]}const W=this._isCellInSelection(x,t),H=i&&x===a,K=P&&x>=f&&x<=g;let z=!1;this._decorationService.forEachDecorationAtCell(x,t,void 0,(e=>{z=!0}));let j=F.getChars()||o.WHITESPACE_CELL_CHAR;if(" "===j&&(F.isUnderline()||F.isOverline())&&(j=" "),M=C*c-u.get(j,F.isBold(),F.isItalic()),b){if(y&&(W&&k||!W&&!k&&F.bg===D)&&(W&&k&&S.selectionForeground||F.fg===L)&&F.extended.ext===R&&K===A&&M===T&&!H&&!B&&!z&&N){F.isInvisible()?w+=o.WHITESPACE_CELL_CHAR:w+=j,y++;continue}y&&(b.textContent=w),b=this._document.createElement("span"),y=0,w=""}else b=this._document.createElement("span");if(D=F.bg,L=F.fg,R=F.extended.ext,A=K,T=M,k=W,B&&a>=x&&a<=U&&(a=x),!this._coreService.isCursorHidden&&H&&this._coreService.isCursorInitialized)if(I.push("xterm-cursor"),this._coreBrowserService.isFocused)l&&I.push("xterm-cursor-blink"),I.push("bar"===s?"xterm-cursor-bar":"underline"===s?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":I.push("xterm-cursor-outline");break;case"block":I.push("xterm-cursor-block");break;case"bar":I.push("xterm-cursor-bar");break;case"underline":I.push("xterm-cursor-underline")}if(F.isBold()&&I.push("xterm-bold"),F.isItalic()&&I.push("xterm-italic"),F.isDim()&&I.push("xterm-dim"),w=F.isInvisible()?o.WHITESPACE_CELL_CHAR:F.getChars()||o.WHITESPACE_CELL_CHAR,F.isUnderline()&&(I.push(`xterm-underline-${F.extended.underlineStyle}`)," "===w&&(w=" "),!F.isUnderlineColorDefault()))if(F.isUnderlineColorRGB())b.style.textDecorationColor=`rgb(${_.AttributeData.toColorRGB(F.getUnderlineColor()).join(",")})`;else{let e=F.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&F.isBold()&&e<8&&(e+=8),b.style.textDecorationColor=S.ansi[e].css}F.isOverline()&&(I.push("xterm-overline")," "===w&&(w=" ")),F.isStrikethrough()&&I.push("xterm-strikethrough"),K&&(b.style.textDecoration="underline");let $=F.getFgColor(),V=F.getFgColorMode(),G=F.getBgColor(),q=F.getBgColorMode();const X=!!F.isInverse();if(X){const e=$;$=G,G=e;const t=V;V=q,q=t}let Y,Z,J,Q=!1;switch(this._decorationService.forEachDecorationAtCell(x,t,void 0,(e=>{"top"!==e.options.layer&&Q||(e.backgroundColorRGB&&(q=50331648,G=e.backgroundColorRGB.rgba>>8&16777215,Y=e.backgroundColorRGB),e.foregroundColorRGB&&(V=50331648,$=e.foregroundColorRGB.rgba>>8&16777215,Z=e.foregroundColorRGB),Q="top"===e.options.layer)})),!Q&&W&&(Y=this._coreBrowserService.isFocused?S.selectionBackgroundOpaque:S.selectionInactiveBackgroundOpaque,G=Y.rgba>>8&16777215,q=50331648,Q=!0,S.selectionForeground&&(V=50331648,$=S.selectionForeground.rgba>>8&16777215,Z=S.selectionForeground)),Q&&I.push("xterm-decoration-top"),q){case 16777216:case 33554432:J=S.ansi[G],I.push(`xterm-bg-${G}`);break;case 50331648:J=h.channels.toColor(G>>16,G>>8&255,255&G),this._addStyle(b,`background-color:#${p((G>>>0).toString(16),"0",6)}`);break;default:X?(J=S.foreground,I.push(`xterm-bg-${n.INVERTED_DEFAULT_COLOR}`)):J=S.background}switch(Y||F.isDim()&&(Y=h.color.multiplyOpacity(J,.5)),V){case 16777216:case 33554432:F.isBold()&&$<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&($+=8),this._applyMinimumContrast(b,J,S.ansi[$],F,Y,void 0)||I.push(`xterm-fg-${$}`);break;case 50331648:const e=h.channels.toColor($>>16&255,$>>8&255,255&$);this._applyMinimumContrast(b,J,e,F,Y,Z)||this._addStyle(b,`color:#${p($.toString(16),"0",6)}`);break;default:this._applyMinimumContrast(b,J,S.foreground,F,Y,Z)||X&&I.push(`xterm-fg-${n.INVERTED_DEFAULT_COLOR}`)}I.length&&(b.className=I.join(" "),I.length=0),H||B||z||!N?b.textContent=w:y++,M!==this.defaultSpacing&&(b.style.letterSpacing=`${M}px`),m.push(b),x=U}return b&&y&&(b.textContent=w),m}_applyMinimumContrast(e,t,i,s,r,n){if(1===this._optionsService.rawOptions.minimumContrastRatio||(0,u.treatGlyphAsBackgroundColor)(s.getCode()))return!1;const o=this._getContrastCache(s);let a;if(r||n||(a=o.getColor(t.rgba,i.rgba)),void 0===a){const e=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);a=h.color.ensureContrastRatio(r||t,n||i,e),o.setColor((r||t).rgba,(n||i).rgba,a??null)}return!!a&&(this._addStyle(e,`color:${a.css}`),!0)}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){const i=this._selectionStart,s=this._selectionEnd;return!(!i||!s)&&(this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0])}};function p(e,t,i){for(;e.length{Object.defineProperty(t,"__esModule",{value:!0}),t.WidthCache=void 0,t.WidthCache=class{constructor(e,t){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._measureElements=[],this._container=e.createElement("div"),this._container.classList.add("xterm-width-cache-measure-container"),this._container.setAttribute("aria-hidden","true"),this._container.style.whiteSpace="pre",this._container.style.fontKerning="none";const i=e.createElement("span");i.classList.add("xterm-char-measure-element");const s=e.createElement("span");s.classList.add("xterm-char-measure-element"),s.style.fontWeight="bold";const r=e.createElement("span");r.classList.add("xterm-char-measure-element"),r.style.fontStyle="italic";const n=e.createElement("span");n.classList.add("xterm-char-measure-element"),n.style.fontWeight="bold",n.style.fontStyle="italic",this._measureElements=[i,s,r,n],this._container.appendChild(i),this._container.appendChild(s),this._container.appendChild(r),this._container.appendChild(n),t.appendChild(this._container),this.clear()}dispose(){this._container.remove(),this._measureElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._container.style.fontFamily=this._font,this._container.style.fontSize=`${this._fontSize}px`,this._measureElements[0].style.fontWeight=`${i}`,this._measureElements[1].style.fontWeight=`${s}`,this._measureElements[2].style.fontWeight=`${i}`,this._measureElements[3].style.fontWeight=`${s}`,this.clear())}get(e,t,i){let s=0;if(!t&&!i&&1===e.length&&(s=e.charCodeAt(0))<256){if(-9999!==this._flat[s])return this._flat[s];const t=this._measure(e,0);return t>0&&(this._flat[s]=t),t}let r=e;t&&(r+="B"),i&&(r+="I");let n=this._holey.get(r);if(void 0===n){let s=0;t&&(s|=1),i&&(s|=2),n=this._measure(e,s),n>0&&this._holey.set(r,n)}return n}_measure(e,t){const i=this._measureElements[t];return i.textContent=e.repeat(32),i.offsetWidth/32}}},9176:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.INVERTED_DEFAULT_COLOR=void 0,t.INVERTED_DEFAULT_COLOR=257},6181:(e,t)=>{function i(e){return 57508<=e&&e<=57558}function s(e){return e>=128512&&e<=128591||e>=127744&&e<=128511||e>=128640&&e<=128767||e>=9728&&e<=9983||e>=9984&&e<=10175||e>=65024&&e<=65039||e>=129280&&e<=129535||e>=127462&&e<=127487}Object.defineProperty(t,"__esModule",{value:!0}),t.throwIfFalsy=function(e){if(!e)throw new Error("value must not be falsy");return e},t.isPowerlineGlyph=i,t.isRestrictedPowerlineGlyph=function(e){return 57520<=e&&e<=57527},t.isEmoji=s,t.allowRescaling=function(e,t,r,n){return 1===t&&r>Math.ceil(1.5*n)&&void 0!==e&&e>255&&!s(e)&&!i(e)&&!function(e){return 57344<=e&&e<=63743}(e)},t.treatGlyphAsBackgroundColor=function(e){return i(e)||function(e){return 9472<=e&&e<=9631}(e)},t.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}},t.computeNextVariantOffset=function(e,t,i=0){return(e-(2*Math.round(t)-i))%(2*Math.round(t))}},2274:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.createSelectionRenderModel=function(){return new i};class i{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1])return void this.clear();const r=e.buffers.active.ydisp,n=t[1]-r,o=i[1]-r,a=Math.max(n,0),l=Math.min(o,e.rows-1);a>=e.rows||l<0?this.clear():(this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=n,this.viewportEndRow=o,this.viewportCappedStartRow=a,this.viewportCappedEndRow=l,this.startCol=t[0],this.endCol=i[0])}isCellSelected(e,t,i){return!!this.hasSelection&&(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol)}}},5959:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionModel=void 0,t.SelectionModel=class{constructor(e){this._bufferService=e,this.isSelectAllActive=!1,this.selectionStartLength=0}clearSelection(){this.selectionStart=void 0,this.selectionEnd=void 0,this.isSelectAllActive=!1,this.selectionStartLength=0}get finalSelectionStart(){return this.isSelectAllActive?[0,0]:this.selectionEnd&&this.selectionStart&&this.areSelectionValuesReversed()?this.selectionEnd:this.selectionStart}get finalSelectionEnd(){if(this.isSelectAllActive)return[this._bufferService.cols,this._bufferService.buffer.ybase+this._bufferService.rows-1];if(this.selectionStart){if(!this.selectionEnd||this.areSelectionValuesReversed()){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?e%this._bufferService.cols==0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){const e=this.selectionStart,t=this.selectionEnd;return!(!e||!t)&&(e[1]>t[1]||e[1]===t[1]&&e[0]>t[0])}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}}},4792:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharSizeService=void 0;const n=i(6501),o=i(7150),a=i(802);let l=class extends o.Disposable{get hasValidSize(){return this.width>0&&this.height>0}constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this._register(new a.Emitter),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new d(this._optionsService))}catch{this._measureStrategy=this._register(new c(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],(()=>this.measure())))}measure(){const e=this._measureStrategy.measure();e.width===this.width&&e.height===this.height||(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};t.CharSizeService=l,t.CharSizeService=l=s([r(2,n.IOptionsService)],l);class h extends o.Disposable{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){void 0!==e&&e>0&&void 0!==t&&t>0&&(this._result.width=e,this._result.height=t)}}class c extends h{constructor(e,t,i){super(),this._document=e,this._parentElement=t,this._optionsService=i,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}}class d extends h{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");const t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;const e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}}},945:function(e,t,i){var s,r=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},n=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharacterJoinerService=t.JoinedCellData=void 0;const o=i(5451),a=i(8938),l=i(3055),h=i(6501);class c extends o.AttributeData{constructor(e,t,i){super(),this.content=0,this.combinedData="",this.fg=e.fg,this.bg=e.bg,this.combinedData=t,this._width=i}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(e){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}t.JoinedCellData=c;let d=s=class{constructor(e){this._bufferService=e,this._characterJoiners=[],this._nextCharacterJoinerId=0,this._workCell=new l.CellData}register(e){const t={id:this._nextCharacterJoinerId++,handler:e};return this._characterJoiners.push(t),t.id}deregister(e){for(let t=0;t1){const e=this._getJoinedRanges(s,o,n,t,r);for(let t=0;t1){const e=this._getJoinedRanges(s,o,n,t,r);for(let t=0;t{Object.defineProperty(t,"__esModule",{value:!0}),t.CoreBrowserService=void 0;const s=i(802),r=i(7093),n=i(7150);class o extends n.Disposable{constructor(e,t,i){super(),this._textarea=e,this._window=t,this.mainDocument=i,this._isFocused=!1,this._cachedIsFocused=void 0,this._screenDprMonitor=this._register(new a(this._window)),this._onDprChange=this._register(new s.Emitter),this.onDprChange=this._onDprChange.event,this._onWindowChange=this._register(new s.Emitter),this.onWindowChange=this._onWindowChange.event,this._register(this.onWindowChange((e=>this._screenDprMonitor.setWindow(e)))),this._register(s.Event.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register((0,r.addDisposableListener)(this._textarea,"focus",(()=>this._isFocused=!0))),this._register((0,r.addDisposableListener)(this._textarea,"blur",(()=>this._isFocused=!1)))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return void 0===this._cachedIsFocused&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask((()=>this._cachedIsFocused=void 0))),this._cachedIsFocused}}t.CoreBrowserService=o;class a extends n.Disposable{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new n.MutableDisposable),this._onDprChange=this._register(new s.Emitter),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register((0,n.toDisposable)((()=>this.clearListener())))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=(0,r.addDisposableListener)(this._parentWindow,"resize",(()=>this._setDprAndFireIfDiffers()))}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){this._resolutionMediaMatchList&&this._outerListener&&(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}}},9820:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.LinkProviderService=void 0;const s=i(7150);class r extends s.Disposable{constructor(){super(),this.linkProviders=[],this._register((0,s.toDisposable)((()=>this.linkProviders.length=0)))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{const t=this.linkProviders.indexOf(e);-1!==t&&this.linkProviders.splice(t,1)}}}}t.LinkProviderService=r},9784:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseService=void 0;const n=i(7098),o=i(5251);let a=class{constructor(e,t){this._renderService=e,this._charSizeService=t}getCoords(e,t,i,s,r){return(0,o.getCoords)(window,e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){const i=(0,o.getCoordsRelativeToElement)(window,e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};t.MouseService=a,t.MouseService=a=s([r(0,n.IRenderService),r(1,n.ICharSizeService)],a)},5783:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.RenderService=void 0;const n=i(4852),o=i(7098),a=i(7150),l=i(6168),h=i(6501),c=i(802);let d=class extends a.Disposable{get dimensions(){return this._renderer.value.dimensions}constructor(e,t,i,s,r,o,h,d,_){super(),this._rowCount=e,this._optionsService=i,this._charSizeService=s,this._coreService=r,this._coreBrowserService=d,this._renderer=this._register(new a.MutableDisposable),this._pausedResizeTask=new l.DebouncedIdleTask,this._observerDisposable=this._register(new a.MutableDisposable),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new c.Emitter),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new c.Emitter),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new c.Emitter),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new c.Emitter),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new n.RenderDebouncer(((e,t)=>this._renderRows(e,t)),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new u(this._coreBrowserService,this._coreService,(()=>this._fullRefresh())),this._register((0,a.toDisposable)((()=>this._syncOutputHandler.dispose()))),this._register(this._coreBrowserService.onDprChange((()=>this.handleDevicePixelRatioChange()))),this._register(h.onResize((()=>this._fullRefresh()))),this._register(h.buffers.onBufferActivate((()=>this._renderer.value?.clear()))),this._register(this._optionsService.onOptionChange((()=>this._handleOptionsChanged()))),this._register(this._charSizeService.onCharSizeChange((()=>this.handleCharSizeChanged()))),this._register(o.onDecorationRegistered((()=>this._fullRefresh()))),this._register(o.onDecorationRemoved((()=>this._fullRefresh()))),this._register(this._optionsService.onMultipleOptionChange(["customGlyphs","drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],(()=>{this.clear(),this.handleResize(h.cols,h.rows),this._fullRefresh()}))),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],(()=>this.refreshRows(h.buffer.y,h.buffer.y,!0)))),this._register(_.onChangeColors((()=>this._fullRefresh()))),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange((e=>this._registerIntersectionObserver(e,t))))}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){const i=new e.IntersectionObserver((e=>this._handleIntersectionChange(e[e.length-1])),{threshold:0});i.observe(t),this._observerDisposable.value=(0,a.toDisposable)((()=>i.disconnect()))}}_handleIntersectionChange(e){this._isPaused=void 0===e.isIntersecting?0===e.intersectionRatio:!e.isIntersecting,this._isPaused||this._charSizeService.hasValidSize||this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1){if(this._isPaused)return void(this._needsFullRefresh=!0);if(this._coreService.decPrivateModes.synchronizedOutput)return void this._syncOutputHandler.bufferRows(e,t);const s=this._syncOutputHandler.flush();s&&(e=Math.min(e,s.start),t=Math.max(t,s.end)),i||(this._isNextRenderRedrawOnly=!1),this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){this._renderer.value&&(this._coreService.decPrivateModes.synchronizedOutput?this._syncOutputHandler.bufferRows(e,t):(e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0))}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw((e=>this.refreshRows(e.start,e.end,!0))),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set((()=>this._renderer.value?.handleResize(e,t))):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.value?.handleSelectionChanged(e,t,i)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};t.RenderService=d,t.RenderService=d=s([r(2,h.IOptionsService),r(3,o.ICharSizeService),r(4,h.ICoreService),r(5,h.IDecorationService),r(6,h.IBufferService),r(7,o.ICoreBrowserService),r(8,o.IThemeService)],d);class u{constructor(e,t,i){this._coreBrowserService=e,this._coreService=t,this._onTimeout=i,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),void 0===this._timeout&&(this._timeout=this._coreBrowserService.window.setTimeout((()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()}),1e3))}flush(){if(void 0!==this._timeout&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;const e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){void 0!==this._timeout&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}}},2079:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionService=void 0;const n=i(5251),o=i(9686),a=i(5959),l=i(7098),h=i(7150),c=i(701),d=i(9384),u=i(3055),_=i(6501),f=i(802),p=String.fromCharCode(160),g=new RegExp(p,"g");let m=class extends h.Disposable{constructor(e,t,i,s,r,n,o,l,c){super(),this._element=e,this._screenElement=t,this._linkifier=i,this._bufferService=s,this._coreService=r,this._mouseService=n,this._optionsService=o,this._renderService=l,this._coreBrowserService=c,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new u.CellData,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this._register(new f.Emitter),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this._register(new f.Emitter),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this._register(new f.Emitter),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this._register(new f.Emitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=e=>this._handleMouseMove(e),this._mouseUpListener=e=>this._handleMouseUp(e),this._coreService.onUserInput((()=>{this.hasSelection&&this.clearSelection()})),this._trimListener=this._bufferService.buffer.lines.onTrim((e=>this._handleTrim(e))),this._register(this._bufferService.buffers.onBufferActivate((e=>this._handleBufferActivate(e)))),this.enable(),this._model=new a.SelectionModel(this._bufferService),this._activeSelectionMode=0,this._register((0,h.toDisposable)((()=>{this._removeMouseDownListeners()}))),this._register(this._bufferService.onResize((e=>{e.rowsChanged&&this.clearSelection()})))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!(!e||!t||e[0]===t[0]&&e[1]===t[1])}get selectionText(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";const i=this._bufferService.buffer,s=[];if(3===this._activeSelectionMode){if(e[0]===t[0])return"";const r=e[0]e.replace(g," "))).join(c.isWindows?"\r\n":"\n")}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._refresh()))),c.isLinux&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:3===this._activeSelectionMode})}_isClickInSelection(e){const t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!!(i&&s&&t)&&this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){const i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!(!i||!s)&&this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){const i=this._linkifier.currentLink?.link?.range;if(i)return this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=(0,d.getRangeLength)(i,this._bufferService.cols),this._model.selectionEnd=void 0,!0;const s=this._getMouseBufferCoords(e);return!!s&&(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0)}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){const t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=(0,n.getCoordsRelativeToElement)(this._coreBrowserService.window,e,this._screenElement)[1];const i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-50),50),t/=50,t/Math.abs(t)+Math.round(14*t))}shouldForceSelection(e){return c.isMac?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,(2!==e.button||!this.hasSelection)&&0===e.button){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):1===e.detail?this._handleSingleClick(e):2===e.detail?this._handleDoubleClick(e):3===e.detail&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval((()=>this._dragScroll()),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0;const t=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);t&&t.length!==this._model.selectionStart[0]&&0===t.hasWidth(this._model.selectionStart[0])&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){const t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return e.altKey&&!(c.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;const t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd)return void this.refresh(!0);2===this._activeSelectionMode?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));const i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){const t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&t<500&&e.altKey&&this._optionsService.rawOptions.altClickMovesCursor){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){const t=this._mouseService.getCoords(e,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(t&&void 0!==t[0]&&void 0!==t[1]){const e=(0,o.moveToCellSequence)(t[0]-1,t[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(e,!0)}}}else this._fireEventIfSelectionChanged()}_fireEventIfSelectionChanged(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd,i=!(!e||!t||e[0]===t[0]&&e[1]===t[1]);i?e&&t&&(this._oldSelectionStart&&this._oldSelectionEnd&&e[0]===this._oldSelectionStart[0]&&e[1]===this._oldSelectionStart[1]&&t[0]===this._oldSelectionEnd[0]&&t[1]===this._oldSelectionEnd[1]||this._fireOnSelectionChange(e,t,i)):this._oldHasSelection&&this._fireOnSelectionChange(e,t,i)}_fireOnSelectionChange(e,t,i){this._oldSelectionStart=e,this._oldSelectionEnd=t,this._oldHasSelection=i,this._onSelectionChange.fire()}_handleBufferActivate(e){this.clearSelection(),this._trimListener.dispose(),this._trimListener=e.activeBuffer.lines.onTrim((e=>this._handleTrim(e)))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){const r=e.loadCell(s,this._workCell).getChars().length;0===this._workCell.getWidth()?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;const r=this._bufferService.buffer,n=r.lines.get(e[1]);if(!n)return;const o=r.translateBufferLineToString(e[1],!1);let a=this._convertViewportColToCharacterIndex(n,e[0]),l=a;const h=e[0]-a;let c=0,d=0,u=0,_=0;if(" "===o.charAt(a)){for(;a>0&&" "===o.charAt(a-1);)a--;for(;l1&&(_+=s-1,l+=s-1);t>0&&a>0&&!this._isCharWordSeparator(n.loadCell(t-1,this._workCell));){n.loadCell(t-1,this._workCell);const e=this._workCell.getChars().length;0===this._workCell.getWidth()?(c++,t--):e>1&&(u+=e-1,a-=e-1),a--,t--}for(;i1&&(_+=e-1,l+=e-1),l++,i++}}l++;let f=a+h-c+u,p=Math.min(this._bufferService.cols,l-a+c+d-u-_);if(t||""!==o.slice(a,l).trim()){if(i&&0===f&&32!==n.getCodePoint(0)){const t=r.lines.get(e[1]-1);if(t&&n.isWrapped&&32!==t.getCodePoint(this._bufferService.cols-1)){const t=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(t){const e=this._bufferService.cols-t.start;f-=e,p+=e}}}if(s&&f+p===this._bufferService.cols&&32!==n.getCodePoint(this._bufferService.cols-1)){const t=r.lines.get(e[1]+1);if(t?.isWrapped&&32!==t.getCodePoint(0)){const t=this._getWordAt([0,e[1]+1],!1,!1,!0);t&&(p+=t.length)}}return{start:f,length:p}}}_selectWordAt(e,t){const i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){const t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return 0!==e.getWidth()&&this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){const t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=(0,d.getRangeLength)(i,this._bufferService.cols)}};t.SelectionService=m,t.SelectionService=m=s([r(3,_.IBufferService),r(4,_.ICoreService),r(5,l.IMouseService),r(6,_.IOptionsService),r(7,l.IRenderService),r(8,l.ICoreBrowserService)],m)},7098:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ILinkProviderService=t.IThemeService=t.ICharacterJoinerService=t.ISelectionService=t.IRenderService=t.IMouseService=t.ICoreBrowserService=t.ICharSizeService=void 0;const s=i(6201);t.ICharSizeService=(0,s.createDecorator)("CharSizeService"),t.ICoreBrowserService=(0,s.createDecorator)("CoreBrowserService"),t.IMouseService=(0,s.createDecorator)("MouseService"),t.IRenderService=(0,s.createDecorator)("RenderService"),t.ISelectionService=(0,s.createDecorator)("SelectionService"),t.ICharacterJoinerService=(0,s.createDecorator)("CharacterJoinerService"),t.IThemeService=(0,s.createDecorator)("ThemeService"),t.ILinkProviderService=(0,s.createDecorator)("LinkProviderService")},9078:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.ThemeService=void 0;const n=i(7174),o=i(9302),a=i(4103),l=i(7150),h=i(6501),c=i(802),d=a.css.toColor("#ffffff"),u=a.css.toColor("#000000"),_=a.css.toColor("#ffffff"),f=u,p={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},g=d;let m=class extends l.Disposable{get colors(){return this._colors}constructor(e){super(),this._optionsService=e,this._contrastCache=new n.ColorContrastCache,this._halfContrastCache=new n.ColorContrastCache,this._onChangeColors=this._register(new c.Emitter),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:d,background:u,cursor:_,cursorAccent:f,selectionForeground:void 0,selectionBackgroundTransparent:p,selectionBackgroundOpaque:a.color.blend(u,p),selectionInactiveBackgroundTransparent:p,selectionInactiveBackgroundOpaque:a.color.blend(u,p),scrollbarSliderBackground:a.color.opacity(d,.2),scrollbarSliderHoverBackground:a.color.opacity(d,.4),scrollbarSliderActiveBackground:a.color.opacity(d,.5),overviewRulerBorder:d,ansi:o.DEFAULT_ANSI_COLORS.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",(()=>this._contrastCache.clear()))),this._register(this._optionsService.onSpecificOptionChange("theme",(()=>this._setTheme(this._optionsService.rawOptions.theme))))}_setTheme(e={}){const t=this._colors;if(t.foreground=v(e.foreground,d),t.background=v(e.background,u),t.cursor=a.color.blend(t.background,v(e.cursor,_)),t.cursorAccent=a.color.blend(t.background,v(e.cursorAccent,f)),t.selectionBackgroundTransparent=v(e.selectionBackground,p),t.selectionBackgroundOpaque=a.color.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=v(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=a.color.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?v(e.selectionForeground,a.NULL_COLOR):void 0,t.selectionForeground===a.NULL_COLOR&&(t.selectionForeground=void 0),a.color.isOpaque(t.selectionBackgroundTransparent)){const e=.3;t.selectionBackgroundTransparent=a.color.opacity(t.selectionBackgroundTransparent,e)}if(a.color.isOpaque(t.selectionInactiveBackgroundTransparent)){const e=.3;t.selectionInactiveBackgroundTransparent=a.color.opacity(t.selectionInactiveBackgroundTransparent,e)}if(t.scrollbarSliderBackground=v(e.scrollbarSliderBackground,a.color.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=v(e.scrollbarSliderHoverBackground,a.color.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=v(e.scrollbarSliderActiveBackground,a.color.opacity(t.foreground,.5)),t.overviewRulerBorder=v(e.overviewRulerBorder,g),t.ansi=o.DEFAULT_ANSI_COLORS.slice(),t.ansi[0]=v(e.black,o.DEFAULT_ANSI_COLORS[0]),t.ansi[1]=v(e.red,o.DEFAULT_ANSI_COLORS[1]),t.ansi[2]=v(e.green,o.DEFAULT_ANSI_COLORS[2]),t.ansi[3]=v(e.yellow,o.DEFAULT_ANSI_COLORS[3]),t.ansi[4]=v(e.blue,o.DEFAULT_ANSI_COLORS[4]),t.ansi[5]=v(e.magenta,o.DEFAULT_ANSI_COLORS[5]),t.ansi[6]=v(e.cyan,o.DEFAULT_ANSI_COLORS[6]),t.ansi[7]=v(e.white,o.DEFAULT_ANSI_COLORS[7]),t.ansi[8]=v(e.brightBlack,o.DEFAULT_ANSI_COLORS[8]),t.ansi[9]=v(e.brightRed,o.DEFAULT_ANSI_COLORS[9]),t.ansi[10]=v(e.brightGreen,o.DEFAULT_ANSI_COLORS[10]),t.ansi[11]=v(e.brightYellow,o.DEFAULT_ANSI_COLORS[11]),t.ansi[12]=v(e.brightBlue,o.DEFAULT_ANSI_COLORS[12]),t.ansi[13]=v(e.brightMagenta,o.DEFAULT_ANSI_COLORS[13]),t.ansi[14]=v(e.brightCyan,o.DEFAULT_ANSI_COLORS[14]),t.ansi[15]=v(e.brightWhite,o.DEFAULT_ANSI_COLORS[15]),e.extendedAnsi){const i=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;s{Object.defineProperty(t,"__esModule",{value:!0}),t.CircularList=void 0;const s=i(7150),r=i(802);class n extends s.Disposable{constructor(e){super(),this._maxLength=e,this.onDeleteEmitter=this._register(new r.Emitter),this.onDelete=this.onDeleteEmitter.event,this.onInsertEmitter=this._register(new r.Emitter),this.onInsert=this.onInsertEmitter.event,this.onTrimEmitter=this._register(new r.Emitter),this.onTrim=this.onTrimEmitter.event,this._array=new Array(this._maxLength),this._startIndex=0,this._length=0}get maxLength(){return this._maxLength}set maxLength(e){if(this._maxLength===e)return;const t=new Array(e);for(let i=0;ithis._length)for(let t=this._length;t=e;t--)this._array[this._getCyclicIndex(t+i.length)]=this._array[this._getCyclicIndex(t)];for(let t=0;tthis._maxLength){const e=this._length+i.length-this._maxLength;this._startIndex+=e,this._length=this._maxLength,this.onTrimEmitter.fire(e)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let s=t-1;s>=0;s--)this.set(e+s+i,this.get(e+s));const s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s{Object.defineProperty(t,"__esModule",{value:!0}),t.clone=function e(t,i=5){if("object"!=typeof t)return t;const s=Array.isArray(t)?[]:{};for(const r in t)s[r]=i<=1?t[r]:t[r]&&e(t[r],i-1);return s}},4103:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.rgba=t.rgb=t.css=t.color=t.channels=t.NULL_COLOR=void 0,t.toPaddedHex=d,t.contrastRatio=u;let i=0,s=0,r=0,n=0;var o,a,l,h,c;function d(e){const t=e.toString(16);return t.length<2?"0"+t:t}function u(e,t){return e>>0},e.toColor=function(t,i,s,r){return{css:e.toCss(t,i,s,r),rgba:e.toRgba(t,i,s,r)}}}(o||(t.channels=o={})),function(e){function t(e,t){return n=Math.round(255*t),[i,s,r]=c.toChannels(e.rgba),{css:o.toCss(i,s,r,n),rgba:o.toRgba(i,s,r,n)}}e.blend=function(e,t){if(n=(255&t.rgba)/255,1===n)return{css:t.css,rgba:t.rgba};const a=t.rgba>>24&255,l=t.rgba>>16&255,h=t.rgba>>8&255,c=e.rgba>>24&255,d=e.rgba>>16&255,u=e.rgba>>8&255;return i=c+Math.round((a-c)*n),s=d+Math.round((l-d)*n),r=u+Math.round((h-u)*n),{css:o.toCss(i,s,r),rgba:o.toRgba(i,s,r)}},e.isOpaque=function(e){return!(255&~e.rgba)},e.ensureContrastRatio=function(e,t,i){const s=c.ensureContrastRatio(e.rgba,t.rgba,i);if(s)return o.toColor(s>>24&255,s>>16&255,s>>8&255)},e.opaque=function(e){const t=(255|e.rgba)>>>0;return[i,s,r]=c.toChannels(t),{css:o.toCss(i,s,r),rgba:t}},e.opacity=t,e.multiplyOpacity=function(e,i){return n=255&e.rgba,t(e,n*i/255)},e.toColorRGB=function(e){return[e.rgba>>24&255,e.rgba>>16&255,e.rgba>>8&255]}}(a||(t.color=a={})),function(e){let t,a;try{const e=document.createElement("canvas");e.width=1,e.height=1;const i=e.getContext("2d",{willReadFrequently:!0});i&&(t=i,t.globalCompositeOperation="copy",a=t.createLinearGradient(0,0,1,1))}catch{}e.toColor=function(e){if(e.match(/#[\da-f]{3,8}/i))switch(e.length){case 4:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),o.toColor(i,s,r);case 5:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),n=parseInt(e.slice(4,5).repeat(2),16),o.toColor(i,s,r,n);case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}const l=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(l)return i=parseInt(l[1]),s=parseInt(l[2]),r=parseInt(l[3]),n=Math.round(255*(void 0===l[5]?1:parseFloat(l[5]))),o.toColor(i,s,r,n);if(!t||!a)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=a,t.fillStyle=e,"string"!=typeof t.fillStyle)throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[i,s,r,n]=t.getImageData(0,0,1,1).data,255!==n)throw new Error("css.toColor: Unsupported css format");return{rgba:o.toRgba(i,s,r,n),css:e}}}(l||(t.css=l={})),function(e){function t(e,t,i){const s=e/255,r=t/255,n=i/255;return.2126*(s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))+.0722*(n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4))}e.relativeLuminance=function(e){return t(e>>16&255,e>>8&255,255&e)},e.relativeLuminance2=t}(h||(t.rgb=h={})),function(e){function t(e,t,i){const s=e>>24&255,r=e>>16&255,n=e>>8&255;let o=t>>24&255,a=t>>16&255,l=t>>8&255,c=u(h.relativeLuminance2(o,a,l),h.relativeLuminance2(s,r,n));for(;c0||a>0||l>0);)o-=Math.max(0,Math.ceil(.1*o)),a-=Math.max(0,Math.ceil(.1*a)),l-=Math.max(0,Math.ceil(.1*l)),c=u(h.relativeLuminance2(o,a,l),h.relativeLuminance2(s,r,n));return(o<<24|a<<16|l<<8|255)>>>0}function a(e,t,i){const s=e>>24&255,r=e>>16&255,n=e>>8&255;let o=t>>24&255,a=t>>16&255,l=t>>8&255,c=u(h.relativeLuminance2(o,a,l),h.relativeLuminance2(s,r,n));for(;c>>0}e.blend=function(e,t){if(n=(255&t)/255,1===n)return t;const a=t>>24&255,l=t>>16&255,h=t>>8&255,c=e>>24&255,d=e>>16&255,u=e>>8&255;return i=c+Math.round((a-c)*n),s=d+Math.round((l-d)*n),r=u+Math.round((h-u)*n),o.toRgba(i,s,r)},e.ensureContrastRatio=function(e,i,s){const r=h.relativeLuminance(e>>8),n=h.relativeLuminance(i>>8);if(u(r,n)>8));if(ou(r,h.relativeLuminance(t>>8))?n:t}return n}const o=a(e,i,s),l=u(r,h.relativeLuminance(o>>8));if(lu(r,h.relativeLuminance(n>>8))?o:n}return o}},e.reduceLuminance=t,e.increaseLuminance=a,e.toChannels=function(e){return[e>>24&255,e>>16&255,e>>8&255,255&e]}}(c||(t.rgba=c={}))},5777:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CoreTerminal=void 0;const s=i(6501),r=i(6025),n=i(7276),o=i(9640),a=i(56),l=i(4071),h=i(7792),c=i(6415),d=i(5746),u=i(5882),_=i(2486),f=i(3562),p=i(8811),g=i(802),m=i(7150);let v=!1;class S extends m.Disposable{get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new g.Emitter),this._onScroll.event((e=>{this._onScrollApi?.fire(e.position)}))),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(const t in e)this.optionsService.options[t]=e[t]}constructor(e){super(),this._windowsWrappingHeuristics=this._register(new m.MutableDisposable),this._onBinary=this._register(new g.Emitter),this.onBinary=this._onBinary.event,this._onData=this._register(new g.Emitter),this.onData=this._onData.event,this._onLineFeed=this._register(new g.Emitter),this.onLineFeed=this._onLineFeed.event,this._onResize=this._register(new g.Emitter),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new g.Emitter),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new g.Emitter),this._instantiationService=new r.InstantiationService,this.optionsService=this._register(new a.OptionsService(e)),this._instantiationService.setService(s.IOptionsService,this.optionsService),this._bufferService=this._register(this._instantiationService.createInstance(o.BufferService)),this._instantiationService.setService(s.IBufferService,this._bufferService),this._logService=this._register(this._instantiationService.createInstance(n.LogService)),this._instantiationService.setService(s.ILogService,this._logService),this.coreService=this._register(this._instantiationService.createInstance(l.CoreService)),this._instantiationService.setService(s.ICoreService,this.coreService),this.coreMouseService=this._register(this._instantiationService.createInstance(h.CoreMouseService)),this._instantiationService.setService(s.ICoreMouseService,this.coreMouseService),this.unicodeService=this._register(this._instantiationService.createInstance(c.UnicodeService)),this._instantiationService.setService(s.IUnicodeService,this.unicodeService),this._charsetService=this._instantiationService.createInstance(d.CharsetService),this._instantiationService.setService(s.ICharsetService,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(p.OscLinkService),this._instantiationService.setService(s.IOscLinkService,this._oscLinkService),this._inputHandler=this._register(new _.InputHandler(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this._register(g.Event.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(this._inputHandler),this._register(g.Event.forward(this._bufferService.onResize,this._onResize)),this._register(g.Event.forward(this.coreService.onData,this._onData)),this._register(g.Event.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom((()=>this.scrollToBottom(!0)))),this._register(this.coreService.onUserInput((()=>this._writeBuffer.handleUserInput()))),this._register(this.optionsService.onMultipleOptionChange(["windowsMode","windowsPty"],(()=>this._handleWindowsPtyOptionChange()))),this._register(this._bufferService.onScroll((()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)}))),this._writeBuffer=this._register(new f.WriteBuffer(((e,t)=>this._inputHandler.parse(e,t)))),this._register(g.Event.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=s.LogLevelEnum.WARN&&!v&&(this._logService.warn("writeSync is unreliable and will be removed soon."),v=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,o.MINIMUM_COLS),t=Math.max(t,o.MINIMUM_ROWS),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1;const t=this.optionsService.rawOptions.windowsPty;t&&void 0!==t.buildNumber&&void 0!==t.buildNumber?e=!!("conpty"===t.backend&&t.buildNumber<21376):this.optionsService.rawOptions.windowsMode&&(e=!0),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){const e=[];e.push(this.onLineFeed(u.updateWindowsModeWrappedState.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},(()=>((0,u.updateWindowsModeWrappedState)(this._bufferService),!1)))),this._windowsWrappingHeuristics.value=(0,m.toDisposable)((()=>{for(const t of e)t.dispose()}))}}}t.CoreTerminal=S},2486:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.InputHandler=t.WindowsOptionsReportType=void 0,t.isValidColorIndex=R;const n=i(3534),o=i(6760),a=i(6717),l=i(7150),h=i(726),c=i(6107),d=i(8938),u=i(3055),_=i(5451),f=i(6501),p=i(6415),g=i(1346),m=i(9823),v=i(8693),S=i(802),b={"(":0,")":1,"*":2,"+":3,"-":1,".":2},C=131072;function y(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var w;!function(e){e[e.GET_WIN_SIZE_PIXELS=0]="GET_WIN_SIZE_PIXELS",e[e.GET_CELL_SIZE_PIXELS=1]="GET_CELL_SIZE_PIXELS"}(w||(t.WindowsOptionsReportType=w={}));let E=0;class D extends l.Disposable{getAttrData(){return this._curAttrData}constructor(e,t,i,s,r,l,d,u,_=new a.EscapeSequenceParser){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=l,this._coreMouseService=d,this._unicodeService=u,this._parser=_,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new h.StringToUtf32,this._utf8Decoder=new h.Utf8ToUtf32,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=c.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=c.DEFAULT_ATTR_DATA.clone(),this._onRequestBell=this._register(new S.Emitter),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new S.Emitter),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new S.Emitter),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new S.Emitter),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new S.Emitter),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new S.Emitter),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new S.Emitter),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new S.Emitter),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new S.Emitter),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new S.Emitter),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new S.Emitter),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new S.Emitter),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new S.Emitter),this.onColor=this._onColor.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new L(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate((e=>this._activeBuffer=e.activeBuffer))),this._parser.setCsiHandlerFallback(((e,t)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(e),params:t.toArray()})})),this._parser.setEscHandlerFallback((e=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(e)})})),this._parser.setExecuteHandlerFallback((e=>{this._logService.debug("Unknown EXECUTE code: ",{code:e})})),this._parser.setOscHandlerFallback(((e,t,i)=>{this._logService.debug("Unknown OSC code: ",{identifier:e,action:t,data:i})})),this._parser.setDcsHandlerFallback(((e,t,i)=>{"HOOK"===t&&(i=i.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(e),action:t,payload:i})})),this._parser.setPrintHandler(((e,t,i)=>this.print(e,t,i))),this._parser.registerCsiHandler({final:"@"},(e=>this.insertChars(e))),this._parser.registerCsiHandler({intermediates:" ",final:"@"},(e=>this.scrollLeft(e))),this._parser.registerCsiHandler({final:"A"},(e=>this.cursorUp(e))),this._parser.registerCsiHandler({intermediates:" ",final:"A"},(e=>this.scrollRight(e))),this._parser.registerCsiHandler({final:"B"},(e=>this.cursorDown(e))),this._parser.registerCsiHandler({final:"C"},(e=>this.cursorForward(e))),this._parser.registerCsiHandler({final:"D"},(e=>this.cursorBackward(e))),this._parser.registerCsiHandler({final:"E"},(e=>this.cursorNextLine(e))),this._parser.registerCsiHandler({final:"F"},(e=>this.cursorPrecedingLine(e))),this._parser.registerCsiHandler({final:"G"},(e=>this.cursorCharAbsolute(e))),this._parser.registerCsiHandler({final:"H"},(e=>this.cursorPosition(e))),this._parser.registerCsiHandler({final:"I"},(e=>this.cursorForwardTab(e))),this._parser.registerCsiHandler({final:"J"},(e=>this.eraseInDisplay(e,!1))),this._parser.registerCsiHandler({prefix:"?",final:"J"},(e=>this.eraseInDisplay(e,!0))),this._parser.registerCsiHandler({final:"K"},(e=>this.eraseInLine(e,!1))),this._parser.registerCsiHandler({prefix:"?",final:"K"},(e=>this.eraseInLine(e,!0))),this._parser.registerCsiHandler({final:"L"},(e=>this.insertLines(e))),this._parser.registerCsiHandler({final:"M"},(e=>this.deleteLines(e))),this._parser.registerCsiHandler({final:"P"},(e=>this.deleteChars(e))),this._parser.registerCsiHandler({final:"S"},(e=>this.scrollUp(e))),this._parser.registerCsiHandler({final:"T"},(e=>this.scrollDown(e))),this._parser.registerCsiHandler({final:"X"},(e=>this.eraseChars(e))),this._parser.registerCsiHandler({final:"Z"},(e=>this.cursorBackwardTab(e))),this._parser.registerCsiHandler({final:"`"},(e=>this.charPosAbsolute(e))),this._parser.registerCsiHandler({final:"a"},(e=>this.hPositionRelative(e))),this._parser.registerCsiHandler({final:"b"},(e=>this.repeatPrecedingCharacter(e))),this._parser.registerCsiHandler({final:"c"},(e=>this.sendDeviceAttributesPrimary(e))),this._parser.registerCsiHandler({prefix:">",final:"c"},(e=>this.sendDeviceAttributesSecondary(e))),this._parser.registerCsiHandler({final:"d"},(e=>this.linePosAbsolute(e))),this._parser.registerCsiHandler({final:"e"},(e=>this.vPositionRelative(e))),this._parser.registerCsiHandler({final:"f"},(e=>this.hVPosition(e))),this._parser.registerCsiHandler({final:"g"},(e=>this.tabClear(e))),this._parser.registerCsiHandler({final:"h"},(e=>this.setMode(e))),this._parser.registerCsiHandler({prefix:"?",final:"h"},(e=>this.setModePrivate(e))),this._parser.registerCsiHandler({final:"l"},(e=>this.resetMode(e))),this._parser.registerCsiHandler({prefix:"?",final:"l"},(e=>this.resetModePrivate(e))),this._parser.registerCsiHandler({final:"m"},(e=>this.charAttributes(e))),this._parser.registerCsiHandler({final:"n"},(e=>this.deviceStatus(e))),this._parser.registerCsiHandler({prefix:"?",final:"n"},(e=>this.deviceStatusPrivate(e))),this._parser.registerCsiHandler({intermediates:"!",final:"p"},(e=>this.softReset(e))),this._parser.registerCsiHandler({intermediates:" ",final:"q"},(e=>this.setCursorStyle(e))),this._parser.registerCsiHandler({final:"r"},(e=>this.setScrollRegion(e))),this._parser.registerCsiHandler({final:"s"},(e=>this.saveCursor(e))),this._parser.registerCsiHandler({final:"t"},(e=>this.windowOptions(e))),this._parser.registerCsiHandler({final:"u"},(e=>this.restoreCursor(e))),this._parser.registerCsiHandler({intermediates:"'",final:"}"},(e=>this.insertColumns(e))),this._parser.registerCsiHandler({intermediates:"'",final:"~"},(e=>this.deleteColumns(e))),this._parser.registerCsiHandler({intermediates:'"',final:"q"},(e=>this.selectProtected(e))),this._parser.registerCsiHandler({intermediates:"$",final:"p"},(e=>this.requestMode(e,!0))),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},(e=>this.requestMode(e,!1))),this._parser.setExecuteHandler(n.C0.BEL,(()=>this.bell())),this._parser.setExecuteHandler(n.C0.LF,(()=>this.lineFeed())),this._parser.setExecuteHandler(n.C0.VT,(()=>this.lineFeed())),this._parser.setExecuteHandler(n.C0.FF,(()=>this.lineFeed())),this._parser.setExecuteHandler(n.C0.CR,(()=>this.carriageReturn())),this._parser.setExecuteHandler(n.C0.BS,(()=>this.backspace())),this._parser.setExecuteHandler(n.C0.HT,(()=>this.tab())),this._parser.setExecuteHandler(n.C0.SO,(()=>this.shiftOut())),this._parser.setExecuteHandler(n.C0.SI,(()=>this.shiftIn())),this._parser.setExecuteHandler(n.C1.IND,(()=>this.index())),this._parser.setExecuteHandler(n.C1.NEL,(()=>this.nextLine())),this._parser.setExecuteHandler(n.C1.HTS,(()=>this.tabSet())),this._parser.registerOscHandler(0,new g.OscHandler((e=>(this.setTitle(e),this.setIconName(e),!0)))),this._parser.registerOscHandler(1,new g.OscHandler((e=>this.setIconName(e)))),this._parser.registerOscHandler(2,new g.OscHandler((e=>this.setTitle(e)))),this._parser.registerOscHandler(4,new g.OscHandler((e=>this.setOrReportIndexedColor(e)))),this._parser.registerOscHandler(8,new g.OscHandler((e=>this.setHyperlink(e)))),this._parser.registerOscHandler(10,new g.OscHandler((e=>this.setOrReportFgColor(e)))),this._parser.registerOscHandler(11,new g.OscHandler((e=>this.setOrReportBgColor(e)))),this._parser.registerOscHandler(12,new g.OscHandler((e=>this.setOrReportCursorColor(e)))),this._parser.registerOscHandler(104,new g.OscHandler((e=>this.restoreIndexedColor(e)))),this._parser.registerOscHandler(110,new g.OscHandler((e=>this.restoreFgColor(e)))),this._parser.registerOscHandler(111,new g.OscHandler((e=>this.restoreBgColor(e)))),this._parser.registerOscHandler(112,new g.OscHandler((e=>this.restoreCursorColor(e)))),this._parser.registerEscHandler({final:"7"},(()=>this.saveCursor())),this._parser.registerEscHandler({final:"8"},(()=>this.restoreCursor())),this._parser.registerEscHandler({final:"D"},(()=>this.index())),this._parser.registerEscHandler({final:"E"},(()=>this.nextLine())),this._parser.registerEscHandler({final:"H"},(()=>this.tabSet())),this._parser.registerEscHandler({final:"M"},(()=>this.reverseIndex())),this._parser.registerEscHandler({final:"="},(()=>this.keypadApplicationMode())),this._parser.registerEscHandler({final:">"},(()=>this.keypadNumericMode())),this._parser.registerEscHandler({final:"c"},(()=>this.fullReset())),this._parser.registerEscHandler({final:"n"},(()=>this.setgLevel(2))),this._parser.registerEscHandler({final:"o"},(()=>this.setgLevel(3))),this._parser.registerEscHandler({final:"|"},(()=>this.setgLevel(3))),this._parser.registerEscHandler({final:"}"},(()=>this.setgLevel(2))),this._parser.registerEscHandler({final:"~"},(()=>this.setgLevel(1))),this._parser.registerEscHandler({intermediates:"%",final:"@"},(()=>this.selectDefaultCharset())),this._parser.registerEscHandler({intermediates:"%",final:"G"},(()=>this.selectDefaultCharset()));for(const e in o.CHARSETS)this._parser.registerEscHandler({intermediates:"(",final:e},(()=>this.selectCharset("("+e))),this._parser.registerEscHandler({intermediates:")",final:e},(()=>this.selectCharset(")"+e))),this._parser.registerEscHandler({intermediates:"*",final:e},(()=>this.selectCharset("*"+e))),this._parser.registerEscHandler({intermediates:"+",final:e},(()=>this.selectCharset("+"+e))),this._parser.registerEscHandler({intermediates:"-",final:e},(()=>this.selectCharset("-"+e))),this._parser.registerEscHandler({intermediates:".",final:e},(()=>this.selectCharset("."+e))),this._parser.registerEscHandler({intermediates:"/",final:e},(()=>this.selectCharset("/"+e)));this._parser.registerEscHandler({intermediates:"#",final:"8"},(()=>this.screenAlignmentPattern())),this._parser.setErrorHandler((e=>(this._logService.error("Parsing error: ",e),e))),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new m.DcsHandler(((e,t)=>this.requestStatusString(e,t))))}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){this._logService.logLevel<=f.LogLevelEnum.WARN&&Promise.race([e,new Promise(((e,t)=>setTimeout((()=>t("#SLOW_TIMEOUT")),5e3)))]).catch((e=>{if("#SLOW_TIMEOUT"!==e)throw e;console.warn("async parser handler taking longer than 5000 ms")}))}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,n=0;const o=this._parseStack.paused;if(o){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>C&&(n=this._parseStack.position+C)}if(this._logService.logLevel<=f.LogLevelEnum.DEBUG&&this._logService.debug("parsing data "+("string"==typeof e?` "${e}"`:` "${Array.prototype.map.call(e,(e=>String.fromCharCode(e))).join("")}"`)),this._logService.logLevel===f.LogLevelEnum.TRACE&&this._logService.trace("parsing data (codes)","string"==typeof e?e.split("").map((e=>e.charCodeAt(0))):e),this._parseBuffer.lengthC)for(let t=n;t0&&2===f.getWidth(this._activeBuffer.x-1)&&f.setCellFromCodepoint(this._activeBuffer.x-1,0,1,_);let g=this._parser.precedingJoinState;for(let m=t;ma)if(l){const e=f;let t=this._activeBuffer.x-v;for(this._activeBuffer.x=v,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),f=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),v>0&&f instanceof c.BufferLine&&f.copyCellsFrom(e,t,0,v,!1);t=0;)f.setCellFromCodepoint(this._activeBuffer.x++,0,0,_)}else if(u&&(f.insertCells(this._activeBuffer.x,r-v,this._activeBuffer.getNullCell(_)),2===f.getWidth(a-1)&&f.setCellFromCodepoint(a-1,d.NULL_CELL_CODE,d.NULL_CELL_WIDTH,_)),f.setCellFromCodepoint(this._activeBuffer.x++,s,r,_),r>0)for(;--r;)f.setCellFromCodepoint(this._activeBuffer.x++,0,0,_)}this._parser.precedingJoinState=g,this._activeBuffer.x0&&0===f.getWidth(this._activeBuffer.x)&&!f.hasContent(this._activeBuffer.x)&&f.setCellFromCodepoint(this._activeBuffer.x,0,1,_),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return"t"!==e.final||e.prefix||e.intermediates?this._parser.registerCsiHandler(e,t):this._parser.registerCsiHandler(e,(e=>!y(e.params[0],this._optionsService.rawOptions.windowOptions)||t(e)))}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new m.DcsHandler(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new g.OscHandler(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(0===this._activeBuffer.x&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;const e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){const t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){const t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){const t=e.params[0];return 0===t?delete this._activeBuffer.tabs[this._activeBuffer.x]:3===t&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){const t=e.params[0];return 1===t&&(this._curAttrData.bg|=536870912),2!==t&&0!==t||(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){const n=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);n.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),r),s&&(n.isWrapped=!1)}_resetBufferLine(e,t=!1){const i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){let i;switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);i=this._bufferService.cols&&(this._activeBuffer.lines.get(i+1).isWrapped=!1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(i=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,i-1);i--;){const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+i);if(e?.getTrimmedLength())break}for(;i>=0;i--)this._bufferService.scroll(this._eraseAttrData())}else{for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0)}break;case 3:const e=this._activeBuffer.lines.length-this._bufferService.rows;e>0&&(this._activeBuffer.lines.trimStart(e),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-e,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-e,0),this._onScroll.fire(0))}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t)}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let l=a;for(let e=1;e0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent(n.C0.ESC+"[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent(n.C0.ESC+"[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent(n.C0.ESC+"[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent(n.C0.ESC+"[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent(n.C0.ESC+"[>83;40003;0c")),!0}_is(e){return 0===(this._optionsService.rawOptions.termName+"").indexOf(e)}setMode(e){for(let t=0;te?1:2,_=e.params[0];return f=_,p=t?2===_?4:4===_?u(o.modes.insertMode):12===_?3:20===_?u(d.convertEol):0:1===_?u(i.applicationCursorKeys):3===_?d.windowOptions.setWinLines?80===l?2:132===l?1:0:0:6===_?u(i.origin):7===_?u(i.wraparound):8===_?3:9===_?u("X10"===s):12===_?u(d.cursorBlink):25===_?u(!o.isCursorHidden):45===_?u(i.reverseWraparound):66===_?u(i.applicationKeypad):67===_?4:1e3===_?u("VT200"===s):1002===_?u("DRAG"===s):1003===_?u("ANY"===s):1004===_?u(i.sendFocus):1005===_?4:1006===_?u("SGR"===r):1015===_?4:1016===_?u("SGR_PIXELS"===r):1048===_?1:47===_||1047===_||1049===_?u(h===c):2004===_?u(i.bracketedPasteMode):2026===_?u(i.synchronizedOutput):0,o.triggerDataEvent(`${n.C0.ESC}[${t?"":"?"}${f};${p}$y`),!0;var f,p}_updateAttrColor(e,t,i,s,r){return 2===t?(e|=50331648,e&=-16777216,e|=_.AttributeData.fromColorRGB([i,s,r])):5===t&&(e&=-50331904,e|=33554432|255&i),e}_extractColor(e,t,i){const s=[0,0,-1,0,0,0];let r=0,n=0;do{if(s[n+r]=e.params[t+n],e.hasSubParams(t+n)){const i=e.getSubParams(t+n);let o=0;do{5===s[1]&&(r=1),s[n+o+1+r]=i[o]}while(++o=2||2===s[1]&&n+r>=5)break;s[1]&&(r=1)}while(++n+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,0===e&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=c.DEFAULT_ATTR_DATA.fg,e.bg=c.DEFAULT_ATTR_DATA.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(1===e.length&&0===e.params[0])return this._processSGR0(this._curAttrData),!0;const t=e.length;let i;const s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-50331904,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-50331904,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-50331904,s.fg|=16777224|i-90):i>=100&&i<=107?(s.bg&=-50331904,s.bg|=16777224|i-100):0===i?this._processSGR0(s):1===i?s.fg|=134217728:3===i?s.bg|=67108864:4===i?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):5===i?s.fg|=536870912:7===i?s.fg|=67108864:8===i?s.fg|=1073741824:9===i?s.fg|=2147483648:2===i?s.bg|=134217728:21===i?this._processUnderline(2,s):22===i?(s.fg&=-134217729,s.bg&=-134217729):23===i?s.bg&=-67108865:24===i?(s.fg&=-268435457,this._processUnderline(0,s)):25===i?s.fg&=-536870913:27===i?s.fg&=-67108865:28===i?s.fg&=-1073741825:29===i?s.fg&=2147483647:39===i?(s.fg&=-67108864,s.fg|=16777215&c.DEFAULT_ATTR_DATA.fg):49===i?(s.bg&=-67108864,s.bg|=16777215&c.DEFAULT_ATTR_DATA.bg):38===i||48===i||58===i?r+=this._extractColor(e,r,s):53===i?s.bg|=1073741824:55===i?s.bg&=-1073741825:59===i?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):100===i?(s.fg&=-67108864,s.fg|=16777215&c.DEFAULT_ATTR_DATA.fg,s.bg&=-67108864,s.bg|=16777215&c.DEFAULT_ATTR_DATA.bg):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent(`${n.C0.ESC}[0n`);break;case 6:const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${n.C0.ESC}[${e};${t}R`)}return!0}deviceStatusPrivate(e){if(6===e.params[0]){const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${n.C0.ESC}[?${e};${t}R`)}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=c.DEFAULT_ATTR_DATA.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){const t=0===e.length?1:e.params[0];if(0===t)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar"}const e=t%2==1;this._coreService.decPrivateModes.cursorBlink=e}return!0}setScrollRegion(e){const t=e.params[0]||1;let i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||0===i)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!y(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;const t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:2!==t&&this._onRequestWindowsOptionsReport.fire(w.GET_WIN_SIZE_PIXELS);break;case 16:this._onRequestWindowsOptionsReport.fire(w.GET_CELL_SIZE_PIXELS);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`${n.C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:0!==t&&2!==t||(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),0!==t&&1!==t||(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:0!==t&&2!==t||this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),0!==t&&1!==t||this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop())}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,!0}restoreCursor(e){return this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg,this._charsetService.charset=this._savedCharset,this._activeBuffer.savedCharset&&(this._charsetService.charset=this._activeBuffer.savedCharset),this._restrictCursor(),!0}setTitle(e){return this._windowTitle=e,this._onTitleChange.fire(e),!0}setIconName(e){return this._iconName=e,!0}setOrReportIndexedColor(e){const t=[],i=e.split(";");for(;i.length>1;){const e=i.shift(),s=i.shift();if(/^\d+$/.exec(e)){const i=parseInt(e);if(R(i))if("?"===s)t.push({type:0,index:i});else{const e=(0,v.parseColor)(s);e&&t.push({type:1,index:i,color:e})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){const t=e.indexOf(";");if(-1===t)return!0;const i=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(i,s):!i.trim()&&this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();const i=e.split(":");let s;const r=i.findIndex((e=>e.startsWith("id=")));return-1!==r&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){const i=e.split(";");for(let e=0;e=this._specialColors.length);++e,++t)if("?"===i[e])this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{const s=(0,v.parseColor)(i[e]);s&&this._onColor.fire([{type:1,index:this._specialColors[t],color:s}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;const t=[],i=e.split(";");for(let e=0;e=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){const e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=c.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=c.DEFAULT_ATTR_DATA.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=67108863&this._curAttrData.bg,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){const e=new u.CellData;e.content=1<<22|"E".charCodeAt(0),e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`${n.C0.ESC}${e}${n.C0.ESC}\\`),!0))('"q'===e?`P1$r${this._curAttrData.isProtected()?1:0}"q`:'"p'===e?'P1$r61;1"p':"r"===e?`P1$r${i.scrollTop+1};${i.scrollBottom+1}r`:"m"===e?"P1$r0m":" q"===e?`P1$r${{block:2,underline:4,bar:6}[s.cursorStyle]-(s.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}}t.InputHandler=D;let L=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(E=e,e=t,t=E),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};function R(e){return 0<=e&&e<256}L=s([r(0,f.IBufferService)],L)},7710:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.FourKeyMap=t.TwoKeyMap=void 0;class i{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}}t.TwoKeyMap=i,t.FourKeyMap=class{constructor(){this._data=new i}set(e,t,s,r,n){this._data.get(e,t)||this._data.set(e,t,new i),this._data.get(e,t).set(s,r,n)}get(e,t,i,s){return this._data.get(e,t)?.get(i,s)}clear(){this._data.clear()}}},701:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.isChromeOS=t.isLinux=t.isWindows=t.isIphone=t.isIpad=t.isMac=t.isSafari=t.isLegacyEdge=t.isFirefox=t.isNode=void 0,t.getSafariVersion=function(){if(!t.isSafari)return 0;const e=i.match(/Version\/(\d+)/);return null===e||e.length<2?0:parseInt(e[1])},t.isNode="undefined"!=typeof process&&"title"in process;const i=t.isNode?"node":navigator.userAgent,s=t.isNode?"node":navigator.platform;t.isFirefox=i.includes("Firefox"),t.isLegacyEdge=i.includes("Edge"),t.isSafari=/^((?!chrome|android).)*safari/i.test(i),t.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(s),t.isIpad="iPad"===s,t.isIphone="iPhone"===s,t.isWindows=["Windows","Win16","Win32","WinCE"].includes(s),t.isLinux=s.indexOf("Linux")>=0,t.isChromeOS=/\bCrOS\b/.test(i)},3087:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SortedList=void 0;const s=i(6168);let r=0;t.SortedList=class{constructor(e){this._getKey=e,this._array=[],this._insertedValues=[],this._flushInsertedTask=new s.IdleTaskQueue,this._isFlushingInserted=!1,this._deletedIndices=[],this._flushDeletedTask=new s.IdleTaskQueue,this._isFlushingDeleted=!1}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),0===this._insertedValues.length&&this._flushInsertedTask.enqueue((()=>this._flushInserted())),this._insertedValues.push(e)}_flushInserted(){const e=this._insertedValues.sort(((e,t)=>this._getKey(e)-this._getKey(t)));let t=0,i=0;const s=new Array(this._array.length+this._insertedValues.length);for(let r=0;r=this._array.length||this._getKey(e[t])<=this._getKey(this._array[i])?(s[r]=e[t],t++):s[r]=this._array[i++];this._array=s,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),0===this._array.length)return!1;const t=this._getKey(e);if(void 0===t)return!1;if(r=this._search(t),-1===r)return!1;if(this._getKey(this._array[r])!==t)return!1;do{if(this._array[r]===e)return 0===this._deletedIndices.length&&this._flushDeletedTask.enqueue((()=>this._flushDeleted())),this._deletedIndices.push(r),!0}while(++re-t));let t=0;const i=new Array(this._array.length-e.length);let s=0;for(let r=0;r0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),0!==this._array.length&&(r=this._search(e),!(r<0||r>=this._array.length)&&this._getKey(this._array[r])===e))do{yield this._array[r]}while(++r=this._array.length)&&this._getKey(this._array[r])===e))do{t(this._array[r])}while(++r=t;){let s=t+i>>1;const r=this._getKey(this._array[s]);if(r>e)i=s-1;else{if(!(r0&&this._getKey(this._array[s-1])===e;)s--;return s}t=s+1}}return t}}},6168:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DebouncedIdleTask=t.IdleTaskQueue=t.PriorityTaskQueue=void 0;const s=i(701);class r{constructor(){this._tasks=[],this._i=0}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ir)return s-t<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),void this._start();s=r}this.clear()}}class n extends r{_requestCallback(e){return setTimeout((()=>e(this._createDeadline(16))))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){const t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}}t.PriorityTaskQueue=n,t.IdleTaskQueue=!s.isNode&&"requestIdleCallback"in window?class extends r{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}}:n,t.DebouncedIdleTask=class{constructor(){this._queue=new t.IdleTaskQueue}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}}},5882:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.updateWindowsModeWrappedState=function(e){const t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1),i=t?.get(e.cols-1),r=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);r&&i&&(r.isWrapped=i[s.CHAR_DATA_CODE_INDEX]!==s.NULL_CELL_CODE&&i[s.CHAR_DATA_CODE_INDEX]!==s.WHITESPACE_CELL_CODE)};const s=i(8938)},5451:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ExtendedAttrs=t.AttributeData=void 0;class i{constructor(){this.fg=0,this.bg=0,this.extended=new s}static toColorRGB(e){return[e>>>16&255,e>>>8&255,255&e]}static fromColorRGB(e){return(255&e[0])<<16|(255&e[1])<<8|255&e[2]}clone(){const e=new i;return e.fg=this.fg,e.bg=this.bg,e.extended=this.extended.clone(),e}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&0!==this.extended.underlineStyle?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return!(50331648&~this.fg)}isBgRGB(){return!(50331648&~this.bg)}isFgPalette(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)}isBgPalette(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)}isFgDefault(){return!(50331648&this.fg)}isBgDefault(){return!(50331648&this.bg)}isAttributeDefault(){return 0===this.fg&&0===this.bg}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&~this.extended.underlineColor):this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&this.extended.underlineColor):this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}t.AttributeData=i;class s{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(e){this._ext&=-67108864,this._ext|=67108863&e}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){const e=(3758096384&this._ext)>>29;return e<0?4294967288^e:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}constructor(e=0,t=0){this._ext=0,this._urlId=0,this._ext=e,this._urlId=t}clone(){return new s(this._ext,this._urlId)}isEmpty(){return 0===this.underlineStyle&&0===this._urlId}}t.ExtendedAttrs=s},1073:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Buffer=t.MAX_BUFFER_SIZE=void 0;const s=i(5639),r=i(6168),n=i(5451),o=i(6107),a=i(732),l=i(3055),h=i(8938),c=i(8158),d=i(6760);t.MAX_BUFFER_SIZE=4294967295,t.Buffer=class{constructor(e,t,i){this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=o.DEFAULT_ATTR_DATA.clone(),this.savedCharset=d.DEFAULT_CHARSET,this.markers=[],this._nullCell=l.CellData.fromCharData([0,h.NULL_CELL_CHAR,h.NULL_CELL_WIDTH,h.NULL_CELL_CODE]),this._whitespaceCell=l.CellData.fromCharData([0,h.WHITESPACE_CELL_CHAR,h.WHITESPACE_CELL_WIDTH,h.WHITESPACE_CELL_CODE]),this._isClearing=!1,this._memoryCleanupQueue=new r.IdleTaskQueue,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new n.ExtendedAttrs),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new n.ExtendedAttrs),this._whitespaceCell}getBlankLine(e,t){return new o.BufferLine(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){const e=this.ybase+this.y-this.ydisp;return e>=0&&et.MAX_BUFFER_SIZE?t.MAX_BUFFER_SIZE:i}fillViewportRows(e){if(0===this.lines.length){void 0===e&&(e=o.DEFAULT_ATTR_DATA);let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){const i=this.getNullCell(o.DEFAULT_ATTR_DATA);let s=0;const r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+n+1?(this.ybase--,n++,this.ydisp>0&&this.ydisp--):this.lines.push(new o.BufferLine(e,i)));else for(let e=this._rows;e>t;e--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(e),this.ybase=Math.max(this.ybase-e,0),this.ydisp=Math.max(this.ydisp-e,0),this.savedY=Math.max(this.savedY-e,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),n&&(this.y+=n),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let t=0;t.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue((()=>this._batchedMemoryCleanup())))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){const e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&"conpty"===e.backend&&e.buildNumber>=21376:this._hasScrollback&&!this._optionsService.rawOptions.windowsMode}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){const i=this._optionsService.rawOptions.reflowCursorLine,s=(0,a.reflowLargerGetLinesToRemove)(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(o.DEFAULT_ATTR_DATA),i);if(s.length>0){const i=(0,a.reflowLargerCreateNewLayout)(this.lines,s);(0,a.reflowLargerApplyNewLayout)(this.lines,i.layout),this._reflowLargerAdjustViewport(e,t,i.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){const s=this.getNullCell(o.DEFAULT_ATTR_DATA);let r=i;for(;r-- >0;)0===this.ybase?(this.y>0&&this.y--,this.lines.length=0;l--){let h=this.lines.get(l);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;const c=[h];for(;h.isWrapped&&l>0;)h=this.lines.get(--l),c.unshift(h);if(!i){const e=this.ybase+this.y;if(e>=l&&e0&&(r.push({start:l+c.length+n,newLines:p}),n+=p.length),c.push(...p);let g=u.length-1,m=u[g];0===m&&(g--,m=u[g]);let v=c.length-_-1,S=d;for(;v>=0;){const e=Math.min(S,m);if(void 0===c[g])break;if(c[g].copyCellsFrom(c[v],S-e,m-e,e,!0),m-=e,0===m&&(g--,m=u[g]),S-=e,0===S){v--;const e=Math.max(v,0);S=(0,a.getWrappedLineTrimmedLength)(c,e,this._cols)}}for(let t=0;t0;)0===this.ybase?this.y0){const e=[],t=[];for(let e=0;e=0;h--)if(a&&a.start>s+l){for(let e=a.newLines.length-1;e>=0;e--)this.lines.set(h--,a.newLines[e]);h++,e.push({index:s+1,amount:a.newLines.length}),l+=a.newLines.length,a=r[++o]}else this.lines.set(h,t[s--]);let h=0;for(let t=e.length-1;t>=0;t--)e[t].index+=h,this.lines.onInsertEmitter.fire(e[t]),h+=e[t].amount;const c=Math.max(0,i+n-this.lines.maxLength);c>0&&this.lines.onTrimEmitter.fire(c)}}translateBufferLineToString(e,t,i=0,s){const r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(null==e&&(e=this.x);!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=e,t.line<0&&t.dispose()}))),t.register(this.lines.onInsert((e=>{t.line>=e.index&&(t.line+=e.amount)}))),t.register(this.lines.onDelete((e=>{t.line>=e.index&&t.linee.index&&(t.line-=e.amount)}))),t.register(t.onDispose((()=>this._removeMarker(t)))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}}},6107:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferLine=t.DEFAULT_ATTR_DATA=void 0;const s=i(5451),r=i(3055),n=i(8938),o=i(726);t.DEFAULT_ATTR_DATA=Object.freeze(new s.AttributeData);let a=0;class l{constructor(e,t,i=!1){this.isWrapped=i,this._combined={},this._extendedAttrs={},this._data=new Uint32Array(3*e);const s=t||r.CellData.fromCharData([0,n.NULL_CELL_CHAR,n.NULL_CELL_WIDTH,n.NULL_CELL_CODE]);for(let t=0;t>22,2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):i]}set(e,t){this._data[3*e+1]=t[n.CHAR_DATA_ATTR_INDEX],t[n.CHAR_DATA_CHAR_INDEX].length>1?(this._combined[e]=t[1],this._data[3*e+0]=2097152|e|t[n.CHAR_DATA_WIDTH_INDEX]<<22):this._data[3*e+0]=t[n.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[n.CHAR_DATA_WIDTH_INDEX]<<22}getWidth(e){return this._data[3*e+0]>>22}hasWidth(e){return 12582912&this._data[3*e+0]}getFg(e){return this._data[3*e+1]}getBg(e){return this._data[3*e+2]}hasContent(e){return 4194303&this._data[3*e+0]}getCodePoint(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):2097151&t}isCombined(e){return 2097152&this._data[3*e+0]}getString(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e]:2097151&t?(0,o.stringFromCodePoint)(2097151&t):""}isProtected(e){return 536870912&this._data[3*e+2]}loadCell(e,t){return a=3*e,t.content=this._data[a+0],t.fg=this._data[a+1],t.bg=this._data[a+2],2097152&t.content&&(t.combinedData=this._combined[e]),268435456&t.bg&&(t.extended=this._extendedAttrs[e]),t}setCell(e,t){2097152&t.content&&(this._combined[e]=t.combinedData),268435456&t.bg&&(this._extendedAttrs[e]=t.extended),this._data[3*e+0]=t.content,this._data[3*e+1]=t.fg,this._data[3*e+2]=t.bg}setCellFromCodepoint(e,t,i,s){268435456&s.bg&&(this._extendedAttrs[e]=s.extended),this._data[3*e+0]=t|i<<22,this._data[3*e+1]=s.fg,this._data[3*e+2]=s.bg}addCodepointToCell(e,t,i){let s=this._data[3*e+0];2097152&s?this._combined[e]+=(0,o.stringFromCodePoint)(t):2097151&s?(this._combined[e]=(0,o.stringFromCodePoint)(2097151&s)+(0,o.stringFromCodePoint)(t),s&=-2097152,s|=2097152):s=t|1<<22,i&&(s&=-12582913,s|=i<<22),this._data[3*e+0]=s}insertCells(e,t,i){if((e%=this.length)&&2===this.getWidth(e-1)&&this.setCellFromCodepoint(e-1,0,1,i),t=0;--i)this.setCell(e+t+i,this.loadCell(e+i,s));for(let s=0;sthis.length){if(this._data.buffer.byteLength>=4*i)this._data=new Uint32Array(this._data.buffer,0,i);else{const e=new Uint32Array(i);e.set(this._data),this._data=e}for(let i=this.length;i=e&&delete this._combined[s]}const s=Object.keys(this._extendedAttrs);for(let t=0;t=e&&delete this._extendedAttrs[i]}}return this.length=e,4*i*2=0;--e)if(4194303&this._data[3*e+0])return e+(this._data[3*e+0]>>22);return 0}getNoBgTrimmedLength(){for(let e=this.length-1;e>=0;--e)if(4194303&this._data[3*e+0]||50331648&this._data[3*e+2])return e+(this._data[3*e+0]>>22);return 0}copyCellsFrom(e,t,i,s,r){const n=e._data;if(r)for(let r=s-1;r>=0;r--){for(let e=0;e<3;e++)this._data[3*(i+r)+e]=n[3*(t+r)+e];268435456&n[3*(t+r)+2]&&(this._extendedAttrs[i+r]=e._extendedAttrs[t+r])}else for(let r=0;r=t&&(this._combined[r-t+i]=e._combined[r])}}translateToString(e,t,i,s){t=t??0,i=i??this.length,e&&(i=Math.min(i,this.getTrimmedLength())),s&&(s.length=0);let r="";for(;t>22||1}return s&&s.push(t),r}}t.BufferLine=l},9384:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.getRangeLength=function(e,t){if(e.start.y>e.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}},732:(e,t)=>{function i(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();const s=!e[t].hasContent(i-1)&&1===e[t].getWidth(i-1),r=2===e[t+1].getWidth(0);return s&&r?i-1:i}Object.defineProperty(t,"__esModule",{value:!0}),t.reflowLargerGetLinesToRemove=function(e,t,s,r,n,o){const a=[];for(let l=0;l=l&&r0&&(e>u||0===d[e].getTrimmedLength());e--)g++;g>0&&(a.push(l+d.length-g),a.push(g)),l+=d.length-1}return a},t.reflowLargerCreateNewLayout=function(e,t){const i=[];let s=0,r=t[s],n=0;for(let o=0;oi(e,r,t))).reduce(((e,t)=>e+t));let o=0,a=0,l=0;for(;lh&&(o-=h,a++);const c=2===e[a].getWidth(o-1);c&&o--;const d=c?s-1:s;r.push(d),l+=d}return r},t.getWrappedLineTrimmedLength=i},4097:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferSet=void 0;const s=i(7150),r=i(1073),n=i(802);class o extends s.Disposable{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this._register(new n.Emitter),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",(()=>this.resize(this._bufferService.cols,this._bufferService.rows)))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",(()=>this.setupTabStops())))}reset(){this._normal=new r.Buffer(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new r.Buffer(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}}t.BufferSet=o},3055:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CellData=void 0;const s=i(726),r=i(8938),n=i(5451);class o extends n.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new n.ExtendedAttrs,this.combinedData=""}static fromCharData(e){const t=new o;return t.setFromCharData(e),t}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,s.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(e){this.fg=e[r.CHAR_DATA_ATTR_INDEX],this.bg=0;let t=!1;if(e[r.CHAR_DATA_CHAR_INDEX].length>2)t=!0;else if(2===e[r.CHAR_DATA_CHAR_INDEX].length){const i=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i&&i<=56319){const s=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=s&&s<=57343?this.content=1024*(i-55296)+s-56320+65536|e[r.CHAR_DATA_WIDTH_INDEX]<<22:t=!0}else t=!0}else this.content=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|e[r.CHAR_DATA_WIDTH_INDEX]<<22;t&&(this.combinedData=e[r.CHAR_DATA_CHAR_INDEX],this.content=2097152|e[r.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}t.CellData=o},8938:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.WHITESPACE_CELL_CODE=t.WHITESPACE_CELL_WIDTH=t.WHITESPACE_CELL_CHAR=t.NULL_CELL_CODE=t.NULL_CELL_WIDTH=t.NULL_CELL_CHAR=t.CHAR_DATA_CODE_INDEX=t.CHAR_DATA_WIDTH_INDEX=t.CHAR_DATA_CHAR_INDEX=t.CHAR_DATA_ATTR_INDEX=t.DEFAULT_EXT=t.DEFAULT_ATTR=t.DEFAULT_COLOR=void 0,t.DEFAULT_COLOR=0,t.DEFAULT_ATTR=t.DEFAULT_COLOR<<9|256,t.DEFAULT_EXT=0,t.CHAR_DATA_ATTR_INDEX=0,t.CHAR_DATA_CHAR_INDEX=1,t.CHAR_DATA_WIDTH_INDEX=2,t.CHAR_DATA_CODE_INDEX=3,t.NULL_CELL_CHAR="",t.NULL_CELL_WIDTH=1,t.NULL_CELL_CODE=0,t.WHITESPACE_CELL_CHAR=" ",t.WHITESPACE_CELL_WIDTH=1,t.WHITESPACE_CELL_CODE=32},8158:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Marker=void 0;const s=i(802),r=i(7150);class n{get id(){return this._id}constructor(e){this.line=e,this.isDisposed=!1,this._disposables=[],this._id=n._nextId++,this._onDispose=this.register(new s.Emitter),this.onDispose=this._onDispose.event}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),(0,r.dispose)(this._disposables),this._disposables.length=0)}register(e){return this._disposables.push(e),e}}t.Marker=n,n._nextId=1},6760:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_CHARSET=t.CHARSETS=void 0,t.CHARSETS={},t.DEFAULT_CHARSET=t.CHARSETS.B,t.CHARSETS[0]={"`":"◆",a:"▒",b:"␉",c:"␌",d:"␍",e:"␊",f:"°",g:"±",h:"␤",i:"␋",j:"┘",k:"┐",l:"┌",m:"└",n:"┼",o:"⎺",p:"⎻",q:"─",r:"⎼",s:"⎽",t:"├",u:"┤",v:"┴",w:"┬",x:"│",y:"≤",z:"≥","{":"π","|":"≠","}":"£","~":"·"},t.CHARSETS.A={"#":"£"},t.CHARSETS.B=void 0,t.CHARSETS[4]={"#":"£","@":"¾","[":"ij","\\":"½","]":"|","{":"¨","|":"f","}":"¼","~":"´"},t.CHARSETS.C=t.CHARSETS[5]={"[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS.R={"#":"£","@":"à","[":"°","\\":"ç","]":"§","{":"é","|":"ù","}":"è","~":"¨"},t.CHARSETS.Q={"@":"à","[":"â","\\":"ç","]":"ê","^":"î","`":"ô","{":"é","|":"ù","}":"è","~":"û"},t.CHARSETS.K={"@":"§","[":"Ä","\\":"Ö","]":"Ü","{":"ä","|":"ö","}":"ü","~":"ß"},t.CHARSETS.Y={"#":"£","@":"§","[":"°","\\":"ç","]":"é","`":"ù","{":"à","|":"ò","}":"è","~":"ì"},t.CHARSETS.E=t.CHARSETS[6]={"@":"Ä","[":"Æ","\\":"Ø","]":"Å","^":"Ü","`":"ä","{":"æ","|":"ø","}":"å","~":"ü"},t.CHARSETS.Z={"#":"£","@":"§","[":"¡","\\":"Ñ","]":"¿","{":"°","|":"ñ","}":"ç"},t.CHARSETS.H=t.CHARSETS[7]={"@":"É","[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS["="]={"#":"ù","@":"à","[":"é","\\":"ç","]":"ê","^":"î",_:"è","`":"ô","{":"ä","|":"ö","}":"ü","~":"û"}},3534:(e,t)=>{var i,s,r;Object.defineProperty(t,"__esModule",{value:!0}),t.C1_ESCAPED=t.C1=t.C0=void 0,function(e){e.NUL="\0",e.SOH="",e.STX="",e.ETX="",e.EOT="",e.ENQ="",e.ACK="",e.BEL="",e.BS="\b",e.HT="\t",e.LF="\n",e.VT="\v",e.FF="\f",e.CR="\r",e.SO="",e.SI="",e.DLE="",e.DC1="",e.DC2="",e.DC3="",e.DC4="",e.NAK="",e.SYN="",e.ETB="",e.CAN="",e.EM="",e.SUB="",e.ESC="",e.FS="",e.GS="",e.RS="",e.US="",e.SP=" ",e.DEL=""}(i||(t.C0=i={})),function(e){e.PAD="€",e.HOP="",e.BPH="‚",e.NBH="ƒ",e.IND="„",e.NEL="…",e.SSA="†",e.ESA="‡",e.HTS="ˆ",e.HTJ="‰",e.VTS="Š",e.PLD="‹",e.PLU="Œ",e.RI="",e.SS2="Ž",e.SS3="",e.DCS="",e.PU1="‘",e.PU2="’",e.STS="“",e.CCH="”",e.MW="•",e.SPA="–",e.EPA="—",e.SOS="˜",e.SGCI="™",e.SCI="š",e.CSI="›",e.ST="œ",e.OSC="",e.PM="ž",e.APC="Ÿ"}(s||(t.C1=s={})),function(e){e.ST=`${i.ESC}\\`}(r||(t.C1_ESCAPED=r={}))},706:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.evaluateKeyboardEvent=function(e,t,i,n){const o={type:0,cancel:!1,key:void 0},a=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:"UIKeyInputUpArrow"===e.key?o.key=t?s.C0.ESC+"OA":s.C0.ESC+"[A":"UIKeyInputLeftArrow"===e.key?o.key=t?s.C0.ESC+"OD":s.C0.ESC+"[D":"UIKeyInputRightArrow"===e.key?o.key=t?s.C0.ESC+"OC":s.C0.ESC+"[C":"UIKeyInputDownArrow"===e.key&&(o.key=t?s.C0.ESC+"OB":s.C0.ESC+"[B");break;case 8:o.key=e.ctrlKey?"\b":s.C0.DEL,e.altKey&&(o.key=s.C0.ESC+o.key);break;case 9:if(e.shiftKey){o.key=s.C0.ESC+"[Z";break}o.key=s.C0.HT,o.cancel=!0;break;case 13:o.key=e.altKey?s.C0.ESC+s.C0.CR:s.C0.CR,o.cancel=!0;break;case 27:o.key=s.C0.ESC,e.altKey&&(o.key=s.C0.ESC+s.C0.ESC),o.cancel=!0;break;case 37:if(e.metaKey)break;o.key=a?s.C0.ESC+"[1;"+(a+1)+"D":t?s.C0.ESC+"OD":s.C0.ESC+"[D";break;case 39:if(e.metaKey)break;o.key=a?s.C0.ESC+"[1;"+(a+1)+"C":t?s.C0.ESC+"OC":s.C0.ESC+"[C";break;case 38:if(e.metaKey)break;o.key=a?s.C0.ESC+"[1;"+(a+1)+"A":t?s.C0.ESC+"OA":s.C0.ESC+"[A";break;case 40:if(e.metaKey)break;o.key=a?s.C0.ESC+"[1;"+(a+1)+"B":t?s.C0.ESC+"OB":s.C0.ESC+"[B";break;case 45:e.shiftKey||e.ctrlKey||(o.key=s.C0.ESC+"[2~");break;case 46:o.key=a?s.C0.ESC+"[3;"+(a+1)+"~":s.C0.ESC+"[3~";break;case 36:o.key=a?s.C0.ESC+"[1;"+(a+1)+"H":t?s.C0.ESC+"OH":s.C0.ESC+"[H";break;case 35:o.key=a?s.C0.ESC+"[1;"+(a+1)+"F":t?s.C0.ESC+"OF":s.C0.ESC+"[F";break;case 33:e.shiftKey?o.type=2:e.ctrlKey?o.key=s.C0.ESC+"[5;"+(a+1)+"~":o.key=s.C0.ESC+"[5~";break;case 34:e.shiftKey?o.type=3:e.ctrlKey?o.key=s.C0.ESC+"[6;"+(a+1)+"~":o.key=s.C0.ESC+"[6~";break;case 112:o.key=a?s.C0.ESC+"[1;"+(a+1)+"P":s.C0.ESC+"OP";break;case 113:o.key=a?s.C0.ESC+"[1;"+(a+1)+"Q":s.C0.ESC+"OQ";break;case 114:o.key=a?s.C0.ESC+"[1;"+(a+1)+"R":s.C0.ESC+"OR";break;case 115:o.key=a?s.C0.ESC+"[1;"+(a+1)+"S":s.C0.ESC+"OS";break;case 116:o.key=a?s.C0.ESC+"[15;"+(a+1)+"~":s.C0.ESC+"[15~";break;case 117:o.key=a?s.C0.ESC+"[17;"+(a+1)+"~":s.C0.ESC+"[17~";break;case 118:o.key=a?s.C0.ESC+"[18;"+(a+1)+"~":s.C0.ESC+"[18~";break;case 119:o.key=a?s.C0.ESC+"[19;"+(a+1)+"~":s.C0.ESC+"[19~";break;case 120:o.key=a?s.C0.ESC+"[20;"+(a+1)+"~":s.C0.ESC+"[20~";break;case 121:o.key=a?s.C0.ESC+"[21;"+(a+1)+"~":s.C0.ESC+"[21~";break;case 122:o.key=a?s.C0.ESC+"[23;"+(a+1)+"~":s.C0.ESC+"[23~";break;case 123:o.key=a?s.C0.ESC+"[24;"+(a+1)+"~":s.C0.ESC+"[24~";break;default:if(!e.ctrlKey||e.shiftKey||e.altKey||e.metaKey)if(i&&!n||!e.altKey||e.metaKey)!i||e.altKey||e.ctrlKey||e.shiftKey||!e.metaKey?e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&1===e.key.length?o.key=e.key:e.key&&e.ctrlKey&&("_"===e.key&&(o.key=s.C0.US),"@"===e.key&&(o.key=s.C0.NUL)):65===e.keyCode&&(o.type=1);else{const t=r[e.keyCode],i=t?.[e.shiftKey?1:0];if(i)o.key=s.C0.ESC+i;else if(e.keyCode>=65&&e.keyCode<=90){const t=e.ctrlKey?e.keyCode-64:e.keyCode+32;let i=String.fromCharCode(t);e.shiftKey&&(i=i.toUpperCase()),o.key=s.C0.ESC+i}else if(32===e.keyCode)o.key=s.C0.ESC+(e.ctrlKey?s.C0.NUL:" ");else if("Dead"===e.key&&e.code.startsWith("Key")){let t=e.code.slice(3,4);e.shiftKey||(t=t.toLowerCase()),o.key=s.C0.ESC+t,o.cancel=!0}}else e.keyCode>=65&&e.keyCode<=90?o.key=String.fromCharCode(e.keyCode-64):32===e.keyCode?o.key=s.C0.NUL:e.keyCode>=51&&e.keyCode<=55?o.key=String.fromCharCode(e.keyCode-51+27):56===e.keyCode?o.key=s.C0.DEL:219===e.keyCode?o.key=s.C0.ESC:220===e.keyCode?o.key=s.C0.FS:221===e.keyCode&&(o.key=s.C0.GS)}return o};const s=i(3534),r={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']}},726:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Utf8ToUtf32=t.StringToUtf32=void 0,t.stringFromCodePoint=function(e){return e>65535?(e-=65536,String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)},t.utf32ToString=function(e,t=0,i=e.length){let s="";for(let r=t;r65535?(t-=65536,s+=String.fromCharCode(55296+(t>>10))+String.fromCharCode(t%1024+56320)):s+=String.fromCharCode(t)}return s},t.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){const i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){const i=e.charCodeAt(r++);56320<=i&&i<=57343?t[s++]=1024*(this._interim-55296)+i-56320+65536:(t[s++]=this._interim,t[s++]=i),this._interim=0}for(let n=r;n=i)return this._interim=r,s;const o=e.charCodeAt(n);56320<=o&&o<=57343?t[s++]=1024*(r-55296)+o-56320+65536:(t[s++]=r,t[s++]=o)}else 65279!==r&&(t[s++]=r)}return s}},t.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){const i=e.length;if(!i)return 0;let s,r,n,o,a=0,l=0,h=0;if(this.interim[0]){let s=!1,r=this.interim[0];r&=192==(224&r)?31:224==(240&r)?15:7;let n,o=0;for(;(n=63&this.interim[++o])&&o<4;)r<<=6,r|=n;const l=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,c=l-o;for(;h=i)return 0;if(n=e[h++],128!=(192&n)){h--,s=!0;break}this.interim[o++]=n,r<<=6,r|=63&n}s||(2===l?r<128?h--:t[a++]=r:3===l?r<2048||r>=55296&&r<=57343||65279===r||(t[a++]=r):r<65536||r>1114111||(t[a++]=r)),this.interim.fill(0)}const c=i-4;let d=h;for(;d=i)return this.interim[0]=s,a;if(r=e[d++],128!=(192&r)){d--;continue}if(l=(31&s)<<6|63&r,l<128){d--;continue}t[a++]=l}else if(224==(240&s)){if(d>=i)return this.interim[0]=s,a;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,a;if(n=e[d++],128!=(192&n)){d--;continue}if(l=(15&s)<<12|(63&r)<<6|63&n,l<2048||l>=55296&&l<=57343||65279===l)continue;t[a++]=l}else if(240==(248&s)){if(d>=i)return this.interim[0]=s,a;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,a;if(n=e[d++],128!=(192&n)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,this.interim[2]=n,a;if(o=e[d++],128!=(192&o)){d--;continue}if(l=(7&s)<<18|(63&r)<<12|(63&n)<<6|63&o,l<65536||l>1114111)continue;t[a++]=l}}return a}}},7428:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;const s=i(6415),r=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],n=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]];let o;t.UnicodeV6=class{constructor(){if(this.version="6",!o){o=new Uint8Array(65536),o.fill(1),o[0]=0,o.fill(0,1,32),o.fill(0,127,160),o.fill(2,4352,4448),o[9001]=2,o[9002]=2,o.fill(2,11904,42192),o[12351]=1,o.fill(2,44032,55204),o.fill(2,63744,64256),o.fill(2,65040,65050),o.fill(2,65072,65136),o.fill(2,65280,65377),o.fill(2,65504,65511);for(let e=0;et[r][1])return!1;for(;r>=s;)if(i=s+r>>1,e>t[i][1])s=i+1;else{if(!(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),r=0===i&&0!==t;if(r){const e=s.UnicodeService.extractWidth(t);0===e?r=!1:e>i&&(i=e)}return s.UnicodeService.createPropertyValue(0,i,r)}}},3562:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.WriteBuffer=void 0;const s=i(7150),r=i(802);class n extends s.Disposable{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this._register(new r.Emitter),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}writeSync(e,t){if(void 0!==t&&this._syncCalls>t)return void(this._syncCalls=0);if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;let i;for(this._isSyncWriting=!0;i=this._writeBuffer.shift();){this._action(i);const e=this._callbacks.shift();e&&e()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(this._pendingData>5e7)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput)return this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),void this._innerWrite();setTimeout((()=>this._innerWrite()))}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}_innerWrite(e=0,t=!0){const i=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){const e=this._writeBuffer[this._bufferOffset],s=this._action(e,t);if(s){const e=e=>performance.now()-i>=12?setTimeout((()=>this._innerWrite(0,e))):this._innerWrite(i,e);return void s.catch((e=>(queueMicrotask((()=>{throw e})),Promise.resolve(!1)))).then(e)}const r=this._callbacks[this._bufferOffset];if(r&&r(),this._bufferOffset++,this._pendingData-=e.length,performance.now()-i>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout((()=>this._innerWrite()))):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}}t.WriteBuffer=n},8693:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.parseColor=function(e){if(!e)return;let t=e.toLowerCase();if(0===t.indexOf("rgb:")){t=t.slice(4);const e=i.exec(t);if(e){const t=e[1]?15:e[4]?255:e[7]?4095:65535;return[Math.round(parseInt(e[1]||e[4]||e[7]||e[10],16)/t*255),Math.round(parseInt(e[2]||e[5]||e[8]||e[11],16)/t*255),Math.round(parseInt(e[3]||e[6]||e[9]||e[12],16)/t*255)]}}else if(0===t.indexOf("#")&&(t=t.slice(1),s.exec(t)&&[3,6,9,12].includes(t.length))){const e=t.length/3,i=[0,0,0];for(let s=0;s<3;++s){const r=parseInt(t.slice(e*s,e*s+e),16);i[s]=1===e?r<<4:2===e?r:3===e?r>>4:r>>8}return i}},t.toRgbString=function(e,t=16){const[i,s,n]=e;return`rgb:${r(i,t)}/${r(s,t)}/${r(n,t)}`};const i=/^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/,s=/^[\da-f]+$/;function r(e,t){const i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}},1263:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PAYLOAD_LIMIT=void 0,t.PAYLOAD_LIMIT=1e7},9823:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DcsHandler=t.DcsParser=void 0;const s=i(726),r=i(7262),n=i(1263),o=[];t.DcsParser=class{constructor(){this._handlers=Object.create(null),this._active=o,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=o}registerHandler(e,t){void 0===this._handlers[e]&&(this._handlers[e]=[]);const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=o,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||o,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].hook(t);else this._handlerFb(this._ident,"HOOK",t)}put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._ident,"PUT",(0,s.utf32ToString)(e,t,i))}unhook(e,t=!0){if(this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].unhook(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._ident,"UNHOOK",e);this._active=o,this._ident=0}};const a=new r.Params;a.addParam(0),t.DcsHandler=class{constructor(e){this._handler=e,this._data="",this._params=a,this._hitLimit=!1}hook(e){this._params=e.length>1||e.params[0]?e.clone():a,this._data="",this._hitLimit=!1}put(e,t,i){this._hitLimit||(this._data+=(0,s.utf32ToString)(e,t,i),this._data.length>n.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))}unhook(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data,this._params),t instanceof Promise))return t.then((e=>(this._params=a,this._data="",this._hitLimit=!1,e)));return this._params=a,this._data="",this._hitLimit=!1,t}}},6717:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.EscapeSequenceParser=t.VT500_TRANSITION_TABLE=t.TransitionTable=void 0;const s=i(7150),r=i(7262),n=i(1346),o=i(9823);class a{constructor(e){this.table=new Uint8Array(e)}setDefault(e,t){this.table.fill(e<<4|t)}add(e,t,i,s){this.table[t<<8|e]=i<<4|s}addMany(e,t,i,s){for(let r=0;rt)),i=(e,i)=>t.slice(e,i),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));const n=i(0,14);let o;for(o in e.setDefault(1,0),e.addMany(s,0,2,0),n)e.addMany([24,26,153,154],o,3,0),e.addMany(i(128,144),o,3,0),e.addMany(i(144,152),o,3,0),e.add(156,o,0,0),e.add(27,o,11,1),e.add(157,o,4,8),e.addMany([152,158,159],o,0,7),e.add(155,o,11,3),e.add(144,o,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94,95],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(28,32),9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(i(28,32),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(28,32),10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(28,32),12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(l,0,2,0),e.add(l,8,5,8),e.add(l,6,0,6),e.add(l,11,0,11),e.add(l,13,13,13),e}();class h extends s.Disposable{constructor(e=t.VT500_TRANSITION_TABLE){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new r.Params,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(e,t,i)=>{},this._executeHandlerFb=e=>{},this._csiHandlerFb=(e,t)=>{},this._escHandlerFb=e=>{},this._errorHandlerFb=e=>e,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register((0,s.toDisposable)((()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)}))),this._oscParser=this._register(new n.OscParser),this._dcsParser=this._register(new o.DcsParser),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},(()=>!0))}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i&&60>i||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let t=0;ts||s>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=s}}if(1!==e.final.length)throw new Error("final must be a single byte");const s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){const t=[];for(;e;)t.push(String.fromCharCode(255&e)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){const i=this._identifier(e,[48,126]);void 0===this._escHandlers[i]&&(this._escHandlers[i]=[]);const s=this._escHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){this._executeHandlers[e.charCodeAt(0)]=t}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){const i=this._identifier(e);void 0===this._csiHandlers[i]&&(this._csiHandlers[i]=[]);const s=this._csiHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingJoinState=0,0!==this._parseStack.state&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s,r=0,n=0,o=0;if(this._parseStack.state)if(2===this._parseStack.state)this._parseStack.state=0,o=this._parseStack.chunkPos+1;else{if(void 0===i||1===this._parseStack.state)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");const t=this._parseStack.handlers;let n=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(!1===i&&n>-1)for(;n>=0&&(s=t[n](this._params),!0!==s);n--)if(s instanceof Promise)return this._parseStack.handlerPos=n,s;this._parseStack.handlers=[];break;case 4:if(!1===i&&n>-1)for(;n>=0&&(s=t[n](),!0!==s);n--)if(s instanceof Promise)return this._parseStack.handlerPos=n,s;this._parseStack.handlers=[];break;case 6:if(r=e[this._parseStack.chunkPos],s=this._dcsParser.unhook(24!==r&&26!==r,i),s)return s;27===r&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(r=e[this._parseStack.chunkPos],s=this._oscParser.end(24!==r&&26!==r,i),s)return s;27===r&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0}this._parseStack.state=0,o=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=15&this._parseStack.transition}for(let i=o;i>4){case 2:for(let s=i+1;;++s){if(s>=t||(r=e[s])<32||r>126&&r=t||(r=e[s])<32||r>126&&r=t||(r=e[s])<32||r>126&&r=t||(r=e[s])<32||r>126&&r=0&&(s=o[a](this._params),!0!==s);a--)if(s instanceof Promise)return this._preserveStack(3,o,a,n,i),s;a<0&&this._csiHandlerFb(this._collect<<8|r,this._params),this.precedingJoinState=0;break;case 8:do{switch(r){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(r-48)}}while(++i47&&r<60);i--;break;case 9:this._collect<<=8,this._collect|=r;break;case 10:const h=this._escHandlers[this._collect<<8|r];let c=h?h.length-1:-1;for(;c>=0&&(s=h[c](),!0!==s);c--)if(s instanceof Promise)return this._preserveStack(4,h,c,n,i),s;c<0&&this._escHandlerFb(this._collect<<8|r),this.precedingJoinState=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|r,this._params);break;case 13:for(let s=i+1;;++s)if(s>=t||24===(r=e[s])||26===r||27===r||r>127&&r=t||(r=e[s])<32||r>127&&r{Object.defineProperty(t,"__esModule",{value:!0}),t.OscHandler=t.OscParser=void 0;const s=i(1263),r=i(726),n=[];t.OscParser=class{constructor(){this._state=0,this._active=n,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){void 0===this._handlers[e]&&(this._handlers[e]=[]);const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=n}reset(){if(2===this._state)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=n,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||n,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].start();else this._handlerFb(this._id,"START")}_put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._id,"PUT",(0,r.utf32ToString)(e,t,i))}start(){this.reset(),this._state=1}put(e,t,i){if(3!==this._state){if(1===this._state)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(0!==this._state){if(3!==this._state)if(1===this._state&&this._start(),this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].end(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._id,"END",e);this._active=n,this._id=-1,this._state=0}}},t.OscHandler=class{constructor(e){this._handler=e,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(e,t,i){this._hitLimit||(this._data+=(0,r.utf32ToString)(e,t,i),this._data.length>s.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data),t instanceof Promise))return t.then((e=>(this._data="",this._hitLimit=!1,e)));return this._data="",this._hitLimit=!1,t}}},7262:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Params=void 0;const i=2147483647;class s{static fromArray(e){const t=new s;if(!e.length)return t;for(let i=Array.isArray(e[0])?1:0;i256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}clone(){const e=new s(this.maxLength,this.maxSubParamsLength);return e.params.set(this.params),e.length=this.length,e._subParams.set(this._subParams),e._subParamsLength=this._subParamsLength,e._subParamsIdx.set(this._subParamsIdx),e._rejectDigits=this._rejectDigits,e._rejectSubDigits=this._rejectSubDigits,e._digitIsSub=this._digitIsSub,e}toArray(){const e=[];for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&e.push(Array.prototype.slice.call(this._subParams,i,s))}return e}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(e){if(this._digitIsSub=!1,this.length>=this.maxLength)this._rejectDigits=!0;else{if(e<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>i?i:e}}addSubParam(e){if(this._digitIsSub=!0,this.length)if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength)this._rejectSubDigits=!0;else{if(e<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=e>i?i:e,this._subParamsIdx[this.length-1]++}}hasSubParams(e){return(255&this._subParamsIdx[e])-(this._subParamsIdx[e]>>8)>0}getSubParams(e){const t=this._subParamsIdx[e]>>8,i=255&this._subParamsIdx[e];return i-t>0?this._subParams.subarray(t,i):null}getSubParamsAll(){const e={};for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&(e[t]=this._subParams.slice(i,s))}return e}addDigit(e){let t;if(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;const s=this._digitIsSub?this._subParams:this.params,r=s[t-1];s[t-1]=~r?Math.min(10*r+e,i):e}}t.Params=s},3027:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.AddonManager=void 0,t.AddonManager=class{constructor(){this._addons=[]}dispose(){for(let e=this._addons.length-1;e>=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){const i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferApiView=void 0;const s=i(793),r=i(3055);t.BufferApiView=class{constructor(e,t){this._buffer=e,this.type=t}init(e){return this._buffer=e,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(e){const t=this._buffer.lines.get(e);if(t)return new s.BufferLineApiView(t)}getNullCell(){return new r.CellData}}},793:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferLineApiView=void 0;const s=i(3055);t.BufferLineApiView=class{constructor(e){this._line=e}get isWrapped(){return this._line.isWrapped}get length(){return this._line.length}getCell(e,t){if(!(e<0||e>=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new s.CellData)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}}},5101:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferNamespaceApi=void 0;const s=i(3235),r=i(7150),n=i(802);class o extends r.Disposable{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new n.Emitter),this.onBufferChange=this._onBufferChange.event,this._normal=new s.BufferApiView(this._core.buffers.normal,"normal"),this._alternate=new s.BufferApiView(this._core.buffers.alt,"alternate"),this._core.buffers.onBufferActivate((()=>this._onBufferChange.fire(this.active)))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}}t.BufferNamespaceApi=o},6097:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ParserApi=void 0,t.ParserApi=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,(e=>t(e.toArray())))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,((e,i)=>t(e,i.toArray())))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}}},4335:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeApi=void 0,t.UnicodeApi=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}}},9640:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferService=t.MINIMUM_ROWS=t.MINIMUM_COLS=void 0;const n=i(7150),o=i(4097),a=i(6501),l=i(802);t.MINIMUM_COLS=2,t.MINIMUM_ROWS=1;let h=class extends n.Disposable{get buffer(){return this.buffers.active}constructor(e){super(),this.isUserScrolling=!1,this._onResize=this._register(new l.Emitter),this.onResize=this._onResize.event,this._onScroll=this._register(new l.Emitter),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,t.MINIMUM_COLS),this.rows=Math.max(e.rawOptions.rows||0,t.MINIMUM_ROWS),this.buffers=this._register(new o.BufferSet(e,this)),this._register(this.buffers.onBufferActivate((e=>{this._onScroll.fire(e.activeBuffer.ydisp)})))}resize(e,t){const i=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:i,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){const i=this.buffer;let s;s=this._cachedBlankLine,s&&s.length===this.cols&&s.getFg(0)===e.fg&&s.getBg(0)===e.bg||(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;const r=i.ybase+i.scrollTop,n=i.ybase+i.scrollBottom;if(0===i.scrollTop){const e=i.lines.isFull;n===i.lines.length-1?e?i.lines.recycle().copyFrom(s):i.lines.push(s.clone()):i.lines.splice(n+1,0,s.clone()),e?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{const e=n-r+1;i.lines.shiftElements(r+1,e-1,-1),i.lines.set(n,s.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t){const i=this.buffer;if(e<0){if(0===i.ydisp)return;this.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this.isUserScrolling=!1);const s=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),s!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))}};t.BufferService=h,t.BufferService=h=s([r(0,a.IOptionsService)],h)},5746:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CharsetService=void 0,t.CharsetService=class{constructor(){this.glevel=0,this._charsets=[]}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}}},7792:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreMouseService=void 0;const n=i(6501),o=i(7150),a=i(802),l={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:e=>4!==e.button&&1===e.action&&(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>32!==e.action},DRAG:{events:23,restrict:e=>32!==e.action||3!==e.button},ANY:{events:31,restrict:e=>!0}};function h(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return 4===e.button?(i|=64,i|=e.action):(i|=3&e.button,4&e.button&&(i|=64),8&e.button&&(i|=128),32===e.action?i|=32:0!==e.action||t||(i|=3)),i}const c=String.fromCharCode,d={DEFAULT:e=>{const t=[h(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`${c(t[0])}${c(t[1])}${c(t[2])}`},SGR:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${h(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${h(e,!0)};${e.x};${e.y}${t}`}};let u=class extends o.Disposable{constructor(e,t,i){super(),this._bufferService=e,this._coreService=t,this._optionsService=i,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._lastEvent=null,this._wheelPartialScroll=0,this._onProtocolChange=this._register(new a.Emitter),this.onProtocolChange=this._onProtocolChange.event;for(const e of Object.keys(l))this.addProtocol(e,l[e]);for(const e of Object.keys(d))this.addEncoding(e,d[e]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return 0!==this._protocols[this._activeProtocol].events}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT",this._lastEvent=null,this._wheelPartialScroll=0}consumeWheelEvent(e,t,i){if(0===e.deltaY||e.shiftKey)return 0;if(void 0===t||void 0===i)return 0;const s=t/i;let r=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(r/=s+0,Math.abs(e.deltaY)<50&&(r*=.3),this._wheelPartialScroll+=r,r=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(r*=this._bufferService.rows),r}_applyScrollModifier(e,t){return t.altKey||t.ctrlKey||t.shiftKey?e*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:e*this._optionsService.rawOptions.scrollSensitivity}triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows)return!1;if(4===e.button&&32===e.action)return!1;if(3===e.button&&32!==e.action)return!1;if(4!==e.button&&(2===e.action||3===e.action))return!1;if(e.col++,e.row++,32===e.action&&this._lastEvent&&this._equalEvents(this._lastEvent,e,"SGR_PIXELS"===this._activeEncoding))return!1;if(!this._protocols[this._activeProtocol].restrict(e))return!1;const t=this._encodings[this._activeEncoding](e);return t&&("DEFAULT"===this._activeEncoding?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}explainEvents(e){return{down:!!(1&e),up:!!(2&e),drag:!!(4&e),move:!!(8&e),wheel:!!(16&e)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x)return!1;if(e.y!==t.y)return!1}else{if(e.col!==t.col)return!1;if(e.row!==t.row)return!1}return e.button===t.button&&e.action===t.action&&e.ctrl===t.ctrl&&e.alt===t.alt&&e.shift===t.shift}};t.CoreMouseService=u,t.CoreMouseService=u=s([r(0,n.IBufferService),r(1,n.ICoreService),r(2,n.IOptionsService)],u)},4071:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreService=void 0;const n=i(7453),o=i(7150),a=i(6501),l=i(802),h=Object.freeze({insertMode:!1}),c=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,wraparound:!0});let d=class extends o.Disposable{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorInitialized=!1,this.isCursorHidden=!1,this._onData=this._register(new l.Emitter),this.onData=this._onData.event,this._onUserInput=this._register(new l.Emitter),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new l.Emitter),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new l.Emitter),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.modes=(0,n.clone)(h),this.decPrivateModes=(0,n.clone)(c)}reset(){this.modes=(0,n.clone)(h),this.decPrivateModes=(0,n.clone)(c)}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;const i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",(()=>e.split("").map((e=>e.charCodeAt(0))))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",(()=>e.split("").map((e=>e.charCodeAt(0))))),this._onBinary.fire(e))}};t.CoreService=d,t.CoreService=d=s([r(0,a.IBufferService),r(1,a.ILogService),r(2,a.IOptionsService)],d)},4720:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DecorationService=void 0;const s=i(4103),r=i(7150),n=i(3087),o=i(802);let a=0,l=0;class h extends r.Disposable{get decorations(){return this._decorations.values()}constructor(){super(),this._decorations=new n.SortedList((e=>e?.marker.line)),this._onDecorationRegistered=this._register(new o.Emitter),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new o.Emitter),this.onDecorationRemoved=this._onDecorationRemoved.event,this._register((0,r.toDisposable)((()=>this.reset())))}registerDecoration(e){if(e.marker.isDisposed)return;const t=new c(e);if(t){const e=t.marker.onDispose((()=>t.dispose())),i=t.onDispose((()=>{i.dispose(),t&&(this._decorations.delete(t)&&this._onDecorationRemoved.fire(t),e.dispose())}));this._decorations.insert(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(const e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,t,i){let s=0,r=0;for(const n of this._decorations.getKeyIterator(t))s=n.options.x??0,r=s+(n.options.width??1),e>=s&&e{a=t.options.x??0,l=a+(t.options.width??1),e>=a&&e{Object.defineProperty(t,"__esModule",{value:!0}),t.InstantiationService=t.ServiceCollection=void 0;const s=i(6501),r=i(6201);class n{constructor(...e){this._entries=new Map;for(const[t,i]of e)this.set(t,i)}set(e,t){const i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(const[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}}t.ServiceCollection=n,t.InstantiationService=class{constructor(){this._services=new n,this._services.set(s.IInstantiationService,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){const i=(0,r.getServiceDependencies)(e).sort(((e,t)=>e.index-t.index)),s=[];for(const t of i){const i=this._services.get(t.id);if(!i)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${t.id._id}.`);s.push(i)}const n=i.length>0?i[0].index:t.length;if(t.length!==n)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${n+1} conflicts with ${t.length} static arguments`);return new e(...[...t,...s])}}},7276:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.LogService=void 0,t.setTraceLogger=function(e){l=e},t.traceCall=function(e,t,i){if("function"!=typeof i.value)throw new Error("not supported");const s=i.value;i.value=function(...e){if(l.logLevel!==o.LogLevelEnum.TRACE)return s.apply(this,e);l.trace(`GlyphRenderer#${s.name}(${e.map((e=>JSON.stringify(e))).join(", ")})`);const t=s.apply(this,e);return l.trace(`GlyphRenderer#${s.name} return`,t),t}};const n=i(7150),o=i(6501),a={trace:o.LogLevelEnum.TRACE,debug:o.LogLevelEnum.DEBUG,info:o.LogLevelEnum.INFO,warn:o.LogLevelEnum.WARN,error:o.LogLevelEnum.ERROR,off:o.LogLevelEnum.OFF};let l,h=class extends n.Disposable{get logLevel(){return this._logLevel}constructor(e){super(),this._optionsService=e,this._logLevel=o.LogLevelEnum.OFF,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",(()=>this._updateLogLevel()))),l=this}_updateLogLevel(){this._logLevel=a[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;t{Object.defineProperty(t,"__esModule",{value:!0}),t.OptionsService=t.DEFAULT_OPTIONS=void 0;const s=i(7150),r=i(701),n=i(802);t.DEFAULT_OPTIONS={cols:80,rows:24,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",customGlyphs:!0,drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollModifier:"alt",fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:r.isMac,windowOptions:{},windowsMode:!1,windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",cancelEvents:!1,overviewRuler:{}};const o=["normal","bold","100","200","300","400","500","600","700","800","900"];class a extends s.Disposable{constructor(e){super(),this._onOptionChange=this._register(new n.Emitter),this.onOptionChange=this._onOptionChange.event;const i={...t.DEFAULT_OPTIONS};for(const t in e)if(t in i)try{const s=e[t];i[t]=this._sanitizeAndValidateOption(t,s)}catch(e){console.error(e)}this.rawOptions=i,this.options={...i},this._setupOptions(),this._register((0,s.toDisposable)((()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null})))}onSpecificOptionChange(e,t){return this.onOptionChange((i=>{i===e&&t(this.rawOptions[e])}))}onMultipleOptionChange(e,t){return this.onOptionChange((i=>{-1!==e.indexOf(i)&&t()}))}_setupOptions(){const e=e=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);return this.rawOptions[e]},i=(e,i)=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);i=this._sanitizeAndValidateOption(e,i),this.rawOptions[e]!==i&&(this.rawOptions[e]=i,this._onOptionChange.fire(e))};for(const t in this.rawOptions){const s={get:e.bind(this,t),set:i.bind(this,t)};Object.defineProperty(this.options,t,s)}}_sanitizeAndValidateOption(e,i){switch(e){case"cursorStyle":if(i||(i=t.DEFAULT_OPTIONS[e]),!function(e){return"block"===e||"underline"===e||"bar"===e}(i))throw new Error(`"${i}" is not a valid value for ${e}`);break;case"wordSeparator":i||(i=t.DEFAULT_OPTIONS[e]);break;case"fontWeight":case"fontWeightBold":if("number"==typeof i&&1<=i&&i<=1e3)break;i=o.includes(i)?i:t.DEFAULT_OPTIONS[e];break;case"cursorWidth":i=Math.floor(i);case"lineHeight":case"tabStopWidth":if(i<1)throw new Error(`${e} cannot be less than 1, value: ${i}`);break;case"minimumContrastRatio":i=Math.max(1,Math.min(21,Math.round(10*i)/10));break;case"scrollback":if((i=Math.min(i,4294967295))<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(i<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${i}`);break;case"rows":case"cols":if(!i&&0!==i)throw new Error(`${e} must be numeric, value: ${i}`);break;case"windowsPty":i=i??{}}return i}}t.OptionsService=a},8811:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkService=void 0;const n=i(6501);let o=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){const t=this._bufferService.buffer;if(void 0===e.id){const i=t.addMarker(t.ybase+t.y),s={data:e,id:this._nextId++,lines:[i]};return i.onDispose((()=>this._removeMarkerFromLink(s,i))),this._dataByLinkId.set(s.id,s),s.id}const i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;const n=t.addMarker(t.ybase+t.y),o={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[n]};return n.onDispose((()=>this._removeMarkerFromLink(o,n))),this._entriesWithId.set(o.key,o),this._dataByLinkId.set(o.id,o),o.id}addLineToLink(e,t){const i=this._dataByLinkId.get(e);if(i&&i.lines.every((e=>e.line!==t))){const e=this._bufferService.buffer.addMarker(t);i.lines.push(e),e.onDispose((()=>this._removeMarkerFromLink(i,e)))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){const i=e.lines.indexOf(t);-1!==i&&(e.lines.splice(i,1),0===e.lines.length&&(void 0!==e.data.id&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};t.OscLinkService=o,t.OscLinkService=o=s([r(0,n.IBufferService)],o)},6201:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.serviceRegistry=void 0,t.getServiceDependencies=function(e){return e[s]||[]},t.createDecorator=function(e){if(t.serviceRegistry.has(e))return t.serviceRegistry.get(e);const r=function(e,t,n){if(3!==arguments.length)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");!function(e,t,r){t[i]===t?t[s].push({id:e,index:r}):(t[s]=[{id:e,index:r}],t[i]=t)}(r,e,n)};return r._id=e,t.serviceRegistry.set(e,r),r};const i="di$target",s="di$dependencies";t.serviceRegistry=new Map},6501:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.IDecorationService=t.IUnicodeService=t.IOscLinkService=t.IOptionsService=t.ILogService=t.LogLevelEnum=t.IInstantiationService=t.ICharsetService=t.ICoreService=t.ICoreMouseService=t.IBufferService=void 0;const s=i(6201);var r;t.IBufferService=(0,s.createDecorator)("BufferService"),t.ICoreMouseService=(0,s.createDecorator)("CoreMouseService"),t.ICoreService=(0,s.createDecorator)("CoreService"),t.ICharsetService=(0,s.createDecorator)("CharsetService"),t.IInstantiationService=(0,s.createDecorator)("InstantiationService"),function(e){e[e.TRACE=0]="TRACE",e[e.DEBUG=1]="DEBUG",e[e.INFO=2]="INFO",e[e.WARN=3]="WARN",e[e.ERROR=4]="ERROR",e[e.OFF=5]="OFF"}(r||(t.LogLevelEnum=r={})),t.ILogService=(0,s.createDecorator)("LogService"),t.IOptionsService=(0,s.createDecorator)("OptionsService"),t.IOscLinkService=(0,s.createDecorator)("OscLinkService"),t.IUnicodeService=(0,s.createDecorator)("UnicodeService"),t.IDecorationService=(0,s.createDecorator)("DecorationService")},6415:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;const s=i(7428),r=i(802);class n{static extractShouldJoin(e){return!!(1&e)}static extractWidth(e){return e>>1&3}static extractCharKind(e){return e>>3}static createPropertyValue(e,t,i=!1){return(16777215&e)<<3|(3&t)<<1|(i?1:0)}constructor(){this._providers=Object.create(null),this._active="",this._onChange=new r.Emitter,this.onChange=this._onChange.event;const e=new s.UnicodeV6;this.register(e),this._active=e.version,this._activeProvider=e}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(e){if(!this._providers[e])throw new Error(`unknown Unicode version "${e}"`);this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)}register(e){this._providers[e.version]=e}wcwidth(e){return this._activeProvider.wcwidth(e)}getStringCellWidth(e){let t=0,i=0;const s=e.length;for(let r=0;r=s)return t+this.wcwidth(o);const i=e.charCodeAt(r);56320<=i&&i<=57343?o=1024*(o-55296)+i-56320+65536:t+=this.wcwidth(i)}const a=this.charProperties(o,i);let l=n.extractWidth(a);n.extractShouldJoin(a)&&(l-=n.extractWidth(i)),t+=l,i=a}return t}charProperties(e,t){return this._activeProvider.charProperties(e,t)}}t.UnicodeService=n},4333:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.isAndroid=t.isElectron=t.isWebkitWebView=t.isSafari=t.isChrome=t.isWebKit=t.isFirefox=t.onDidChangeFullscreen=t.onDidChangeZoomLevel=void 0,t.addMatchMediaChangeListener=o,t.setZoomLevel=function(e,t){n.INSTANCE.setZoomLevel(e,t)},t.getZoomLevel=function(e){return n.INSTANCE.getZoomLevel(e)},t.getZoomFactor=function(e){return n.INSTANCE.getZoomFactor(e)},t.setZoomFactor=function(e,t){n.INSTANCE.setZoomFactor(e,t)},t.setFullscreen=function(e,t){n.INSTANCE.setFullscreen(e,t)},t.isFullscreen=function(e){return n.INSTANCE.isFullscreen(e)},t.isStandalone=function(){return l},t.isWCOEnabled=function(){return navigator?.windowControlsOverlay?.visible},t.getWCOBoundingRect=function(){return navigator?.windowControlsOverlay?.getTitlebarAreaRect()};const s=i(4693),r=i(802);class n{constructor(){this.mapWindowIdToZoomLevel=new Map,this._onDidChangeZoomLevel=new r.Emitter,this.onDidChangeZoomLevel=this._onDidChangeZoomLevel.event,this.mapWindowIdToZoomFactor=new Map,this._onDidChangeFullscreen=new r.Emitter,this.onDidChangeFullscreen=this._onDidChangeFullscreen.event,this.mapWindowIdToFullScreen=new Map}static{this.INSTANCE=new n}getZoomLevel(e){return this.mapWindowIdToZoomLevel.get(this.getWindowId(e))??0}setZoomLevel(e,t){if(this.getZoomLevel(t)===e)return;const i=this.getWindowId(t);this.mapWindowIdToZoomLevel.set(i,e),this._onDidChangeZoomLevel.fire(i)}getZoomFactor(e){return this.mapWindowIdToZoomFactor.get(this.getWindowId(e))??1}setZoomFactor(e,t){this.mapWindowIdToZoomFactor.set(this.getWindowId(t),e)}setFullscreen(e,t){if(this.isFullscreen(t)===e)return;const i=this.getWindowId(t);this.mapWindowIdToFullScreen.set(i,e),this._onDidChangeFullscreen.fire(i)}isFullscreen(e){return!!this.mapWindowIdToFullScreen.get(this.getWindowId(e))}getWindowId(e){return e.vscodeWindowId}}function o(e,t,i){"string"==typeof t&&(t=e.matchMedia(t)),t.addEventListener("change",i)}t.onDidChangeZoomLevel=n.INSTANCE.onDidChangeZoomLevel,t.onDidChangeFullscreen=n.INSTANCE.onDidChangeFullscreen;const a="object"==typeof navigator?navigator.userAgent:"";t.isFirefox=a.indexOf("Firefox")>=0,t.isWebKit=a.indexOf("AppleWebKit")>=0,t.isChrome=a.indexOf("Chrome")>=0,t.isSafari=!t.isChrome&&a.indexOf("Safari")>=0,t.isWebkitWebView=!t.isChrome&&!t.isSafari&&t.isWebKit,t.isElectron=a.indexOf("Electron/")>=0,t.isAndroid=a.indexOf("Android")>=0;let l=!1;if("function"==typeof s.mainWindow.matchMedia){const e=s.mainWindow.matchMedia("(display-mode: standalone) or (display-mode: window-controls-overlay)"),t=s.mainWindow.matchMedia("(display-mode: fullscreen)");l=e.matches,o(s.mainWindow,e,(({matches:e})=>{l&&t.matches||(l=e)}))}},7745:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.BrowserFeatures=t.KeyboardSupport=void 0;const o=n(i(4333)),a=i(4693),l=n(i(8163));var h;!function(e){e[e.Always=0]="Always",e[e.FullScreen=1]="FullScreen",e[e.None=2]="None"}(h||(t.KeyboardSupport=h={}));const c="object"==typeof navigator?navigator:{};t.BrowserFeatures={clipboard:{writeText:l.isNative||document.queryCommandSupported&&document.queryCommandSupported("copy")||!!(c&&c.clipboard&&c.clipboard.writeText),readText:l.isNative||!!(c&&c.clipboard&&c.clipboard.readText)},keyboard:l.isNative||o.isStandalone()?h.Always:c.keyboard||o.isSafari?h.FullScreen:h.None,touch:"ontouchstart"in a.mainWindow||c.maxTouchPoints>0,pointerEvents:a.mainWindow.PointerEvent&&("ontouchstart"in a.mainWindow||navigator.maxTouchPoints>0)}},7093:function(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),n=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&r(t,e,i);return n(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.SafeTriangle=t.DragAndDropObserver=t.ModifierKeyEmitter=t.DetectedFullscreenMode=t.Namespace=t.EventHelper=t.EventType=t.sharedMutationObserver=t.Dimension=t.WindowIntervalTimer=t.scheduleAtNextAnimationFrame=t.runAtThisOrScheduleAtNextAnimationFrame=t.WindowIdleValue=t.addStandardDisposableGenericMouseUpListener=t.addStandardDisposableGenericMouseDownListener=t.addStandardDisposableListener=t.onDidUnregisterWindow=t.onWillUnregisterWindow=t.onDidRegisterWindow=t.hasWindow=t.getWindowById=t.getWindowId=t.getWindowsCount=t.getWindows=t.getDocument=t.getWindow=t.registerWindow=void 0,t.clearNode=function(e){for(;e.firstChild;)e.firstChild.remove()},t.clearNodeRecursively=function e(t){for(;t.firstChild;){const i=t.firstChild;i.remove(),e(i)}},t.addDisposableListener=C,t.addDisposableGenericMouseDownListener=w,t.addDisposableGenericMouseMoveListener=function(e,i,s){return C(e,g.isIOS&&l.BrowserFeatures.pointerEvents?t.EventType.POINTER_MOVE:t.EventType.MOUSE_MOVE,i,s)},t.addDisposableGenericMouseUpListener=E,t.runWhenWindowIdle=function(e,t,i){return(0,d._runWhenIdle)(e,t,i)},t.disposableWindowInterval=function(e,t,i,s){let r=0;const n=e.setInterval((()=>{r++,("number"==typeof s&&r>=s||!0===t())&&o.dispose()}),i),o=(0,p.toDisposable)((()=>{e.clearInterval(n)}));return o},t.measure=function(e,i){return(0,t.scheduleAtNextAnimationFrame)(e,i,1e4)},t.modify=function(e,i){return(0,t.scheduleAtNextAnimationFrame)(e,i,-1e4)},t.addDisposableThrottledListener=function(e,t,i,s,r){return new T(e,t,i,s,r)},t.getComputedStyle=k,t.getClientArea=function e(i,s){const r=(0,t.getWindow)(i),n=r.document;if(i!==n.body)return new O(i.clientWidth,i.clientHeight);if(g.isIOS&&r?.visualViewport)return new O(r.visualViewport.width,r.visualViewport.height);if(r?.innerWidth&&r.innerHeight)return new O(r.innerWidth,r.innerHeight);if(n.body&&n.body.clientWidth&&n.body.clientHeight)return new O(n.body.clientWidth,n.body.clientHeight);if(n.documentElement&&n.documentElement.clientWidth&&n.documentElement.clientHeight)return new O(n.documentElement.clientWidth,n.documentElement.clientHeight);if(s)return e(s);throw new Error("Unable to figure out browser width and height")},t.getTopLeftOffset=I,t.size=function(e,t,i){"number"==typeof t&&(e.style.width=`${t}px`),"number"==typeof i&&(e.style.height=`${i}px`)},t.position=function(e,t,i,s,r,n="absolute"){"number"==typeof t&&(e.style.top=`${t}px`),"number"==typeof i&&(e.style.right=`${i}px`),"number"==typeof s&&(e.style.bottom=`${s}px`),"number"==typeof r&&(e.style.left=`${r}px`),e.style.position=n},t.getDomNodePagePosition=function(e){const i=e.getBoundingClientRect(),s=(0,t.getWindow)(e);return{left:i.left+s.scrollX,top:i.top+s.scrollY,width:i.width,height:i.height}},t.getDomNodeZoomLevel=function(e){let t=e,i=1;do{const e=k(t).zoom;null!=e&&"1"!==e&&(i*=e),t=t.parentElement}while(null!==t&&t!==t.ownerDocument.documentElement);return i},t.getTotalWidth=P,t.getContentWidth=function(e){const t=M.getBorderLeftWidth(e)+M.getBorderRightWidth(e),i=M.getPaddingLeft(e)+M.getPaddingRight(e);return e.offsetWidth-t-i},t.getTotalScrollWidth=x,t.getContentHeight=function(e){const t=M.getBorderTopWidth(e)+M.getBorderBottomWidth(e),i=M.getPaddingTop(e)+M.getPaddingBottom(e);return e.offsetHeight-t-i},t.getTotalHeight=function(e){const t=M.getMarginTop(e)+M.getMarginBottom(e);return e.offsetHeight+t},t.getLargestChildWidth=function(e,t){const i=t.map((t=>Math.max(x(t),P(t))+function(e,t){if(null===e)return 0;const i=I(e),s=I(t);return i.left-s.left}(t,e)||0));return Math.max(...i)},t.isAncestor=B,t.setParentFlowTo=function(e,t){e.dataset[N]=t.id},t.isAncestorUsingFlowTo=function(e,t){let i=e;for(;i;){if(i===t)return!0;if(Q(i)){const e=U(i);if(e){i=e;continue}}i=i.parentNode}return!1},t.findParentWithClass=F,t.hasParentWithClass=function(e,t,i){return!!F(e,t,i)},t.isShadowRoot=W,t.isInShadowDOM=function(e){return!!H(e)},t.getShadowRoot=H,t.getActiveElement=K,t.isActiveElement=function(e){return K()===e},t.isAncestorOfActiveElement=function(e){return B(K(),e)},t.isActiveDocument=function(e){return e.ownerDocument===z()},t.getActiveDocument=z,t.getActiveWindow=function(){const e=z();return e.defaultView?.window??v.mainWindow},t.isGlobalStylesheet=function(e){return j.has(e)},t.createStyleSheet2=function(){return new $},t.createStyleSheet=V,t.cloneGlobalStylesheets=function(e){const t=new p.DisposableStore;for(const[i,s]of j)t.add(G(i,s,e));return t},t.createMetaElement=function(e=v.mainWindow.document.head){return q("meta",e)},t.createLinkElement=function(e=v.mainWindow.document.head){return q("link",e)},t.createCSSRule=function e(t,i,s=Y()){if(s&&i){s.sheet?.insertRule(`${t} {${i}}`,0);for(const r of j.get(s)??[])e(t,i,r)}},t.removeCSSRulesContainingSelector=function e(t,i=Y()){if(!i)return;const s=Z(i),r=[];for(let e=0;e=0;e--)i.sheet?.deleteRule(r[e]);for(const s of j.get(i)??[])e(t,s)},t.isHTMLElement=Q,t.isHTMLAnchorElement=function(e){return e instanceof HTMLAnchorElement||e instanceof(0,t.getWindow)(e).HTMLAnchorElement},t.isHTMLSpanElement=function(e){return e instanceof HTMLSpanElement||e instanceof(0,t.getWindow)(e).HTMLSpanElement},t.isHTMLTextAreaElement=function(e){return e instanceof HTMLTextAreaElement||e instanceof(0,t.getWindow)(e).HTMLTextAreaElement},t.isHTMLInputElement=function(e){return e instanceof HTMLInputElement||e instanceof(0,t.getWindow)(e).HTMLInputElement},t.isHTMLButtonElement=function(e){return e instanceof HTMLButtonElement||e instanceof(0,t.getWindow)(e).HTMLButtonElement},t.isHTMLDivElement=function(e){return e instanceof HTMLDivElement||e instanceof(0,t.getWindow)(e).HTMLDivElement},t.isSVGElement=function(e){return e instanceof SVGElement||e instanceof(0,t.getWindow)(e).SVGElement},t.isMouseEvent=function(e){return e instanceof MouseEvent||e instanceof(0,t.getWindow)(e).MouseEvent},t.isKeyboardEvent=function(e){return e instanceof KeyboardEvent||e instanceof(0,t.getWindow)(e).KeyboardEvent},t.isPointerEvent=function(e){return e instanceof PointerEvent||e instanceof(0,t.getWindow)(e).PointerEvent},t.isDragEvent=function(e){return e instanceof DragEvent||e instanceof(0,t.getWindow)(e).DragEvent},t.isEventLike=function(e){const t=e;return!(!t||"function"!=typeof t.preventDefault||"function"!=typeof t.stopPropagation)},t.saveParentsScrollTop=function(e){const t=[];for(let i=0;e&&e.nodeType===e.ELEMENT_NODE;i++)t[i]=e.scrollTop,e=e.parentNode;return t},t.restoreParentsScrollTop=function(e,t){for(let i=0;e&&e.nodeType===e.ELEMENT_NODE;i++)e.scrollTop!==t[i]&&(e.scrollTop=t[i]),e=e.parentNode},t.trackFocus=function(e){return new ee(e)},t.after=function(e,t){return e.after(t),t},t.append=te,t.prepend=function(e,t){return e.insertBefore(t,e.firstChild),t},t.reset=function(e,...t){e.innerText="",te(e,...t)},t.$=ne,t.join=function(e,t){const i=[];return e.forEach(((e,s)=>{s>0&&(t instanceof Node?i.push(t.cloneNode()):i.push(document.createTextNode(t))),i.push(e)})),i},t.setVisibility=function(e,...t){e?oe(...t):ae(...t)},t.show=oe,t.hide=ae,t.removeTabIndexAndUpdateFocus=function(e){if(e&&e.hasAttribute("tabIndex")){if(e.ownerDocument.activeElement===e){const t=function(e){for(;e&&e.nodeType===e.ELEMENT_NODE;){if(Q(e)&&e.hasAttribute("tabIndex"))return e;e=e.parentNode}return null}(e.parentElement);t?.focus()}e.removeAttribute("tabindex")}},t.finalHandler=function(e){return t=>{t.preventDefault(),t.stopPropagation(),e(t)}},t.domContentLoaded=function(e){return new Promise((t=>{if("complete"===e.document.readyState||e.document&&null!==e.document.body)t(void 0);else{const i=()=>{e.window.removeEventListener("DOMContentLoaded",i,!1),t()};e.window.addEventListener("DOMContentLoaded",i,!1)}}))},t.computeScreenAwareSize=function(e,t){const i=e.devicePixelRatio*t;return Math.max(1,Math.floor(i))/e.devicePixelRatio},t.windowOpenNoOpener=function(e){v.mainWindow.open(e,"_blank","noopener")},t.windowOpenPopup=function(e){const t=Math.floor(v.mainWindow.screenLeft+v.mainWindow.innerWidth/2-le/2),i=Math.floor(v.mainWindow.screenTop+v.mainWindow.innerHeight/2-he/2);v.mainWindow.open(e,"_blank",`width=${le},height=${he},top=${i},left=${t}`)},t.windowOpenWithSuccess=function(e,t=!0){const i=v.mainWindow.open();return!!i&&(t&&(i.opener=null),i.location.href=e,!0)},t.animate=function(e,i){const s=()=>{i(),r=(0,t.scheduleAtNextAnimationFrame)(e,s)};let r=(0,t.scheduleAtNextAnimationFrame)(e,s);return(0,p.toDisposable)((()=>r.dispose()))},t.asCSSPropertyValue=function(e){return`'${e.replace(/'/g,"%27")}'`},t.asCssValueWithDefault=function e(t,i){if(void 0!==t){const s=t.match(/^\s*var\((.+)\)$/);if(s){const t=s[1].split(",",2);return 2===t.length&&(i=e(t[1].trim(),i)),`var(${t[0]}, ${i})`}return t}return i},t.detectFullscreen=function(e){return e.document.fullscreenElement||e.document.webkitFullscreenElement||e.document.webkitIsFullScreen?{mode:ce.DOCUMENT,guess:!1}:e.innerHeight===e.screen.height?{mode:ce.BROWSER,guess:!1}:(g.isMacintosh||g.isLinux)&&e.outerHeight===e.screen.height&&e.outerWidth===e.screen.width?{mode:ce.BROWSER,guess:!0}:null},t.multibyteAwareBtoa=function(e){return btoa(function(e){const t=new Uint16Array(e.length);for(let i=0;i0&&(o.className=a.join(" "));const l={};if(r.groups.name&&(l[r.groups.name]=o),s)for(const e of s)Q(e)?o.appendChild(e):"string"==typeof e?o.append(e):"root"in e&&(Object.assign(l,e),o.appendChild(e.root));for(const[e,t]of Object.entries(i))if("className"!==e)if("style"===e)for(const[e,i]of Object.entries(t))o.style.setProperty(fe(e),"number"==typeof i?i+"px":""+i);else"tabIndex"===e?o.tabIndex=t:o.setAttribute(fe(e),t.toString());return l.root=o,l},t.svgElem=function(e,...t){let i,s;Array.isArray(t[0])?(i={},s=t[0]):(i=t[0]||{},s=t[1]);const r=_e.exec(e);if(!r||!r.groups)throw new Error("Bad use of h");const n=r.groups.tag||"div",o=document.createElementNS("http://www.w3.org/2000/svg",n);r.groups.id&&(o.id=r.groups.id);const a=[];if(r.groups.class)for(const e of r.groups.class.split("."))""!==e&&a.push(e);if(void 0!==i.className)for(const e of i.className.split("."))""!==e&&a.push(e);a.length>0&&(o.className=a.join(" "));const l={};if(r.groups.name&&(l[r.groups.name]=o),s)for(const e of s)Q(e)?o.appendChild(e):"string"==typeof e?o.append(e):"root"in e&&(Object.assign(l,e),o.appendChild(e.root));for(const[e,t]of Object.entries(i))if("className"!==e)if("style"===e)for(const[e,i]of Object.entries(t))o.style.setProperty(fe(e),"number"==typeof i?i+"px":""+i);else"tabIndex"===e?o.tabIndex=t:o.setAttribute(fe(e),t.toString());return l.root=o,l},t.copyAttributes=pe,t.trackAttributes=function(e,i,s){pe(e,i,s);const r=new p.DisposableStore;return r.add(t.sharedMutationObserver.observe(e,r,{attributes:!0,attributeFilter:s})((t=>{for(const s of t)"attributes"===s.type&&s.attributeName&&ge(e,i,s.attributeName)}))),r};const a=o(i(4333)),l=i(7745),h=i(5394),c=i(5964),d=i(1758),u=i(9807),_=o(i(802)),f=i(7883),p=i(7150),g=o(i(8163)),m=i(6304),v=i(4693),S=i(7704);s=function(){const e=new Map;(0,v.ensureCodeWindow)(v.mainWindow,1);const i={window:v.mainWindow,disposables:new p.DisposableStore};e.set(v.mainWindow.vscodeWindowId,i);const s=new _.Emitter,r=new _.Emitter,n=new _.Emitter;return{onDidRegisterWindow:s.event,onWillUnregisterWindow:n.event,onDidUnregisterWindow:r.event,registerWindow(i){if(e.has(i.vscodeWindowId))return p.Disposable.None;const o=new p.DisposableStore,a={window:i,disposables:o.add(new p.DisposableStore)};return e.set(i.vscodeWindowId,a),o.add((0,p.toDisposable)((()=>{e.delete(i.vscodeWindowId),r.fire(i)}))),o.add(C(i,t.EventType.BEFORE_UNLOAD,(()=>{n.fire(i)}))),s.fire(a),o},getWindows:()=>e.values(),getWindowsCount:()=>e.size,getWindowId:e=>e.vscodeWindowId,hasWindow:t=>e.has(t),getWindowById:function(t,s){return("number"==typeof t?e.get(t):void 0)??(s?i:void 0)},getWindow(e){const t=e;if(t?.ownerDocument?.defaultView)return t.ownerDocument.defaultView.window;const i=e;return i?.view?i.view.window:v.mainWindow},getDocument(e){const i=e;return(0,t.getWindow)(i).document}}}(),t.registerWindow=s.registerWindow,t.getWindow=s.getWindow,t.getDocument=s.getDocument,t.getWindows=s.getWindows,t.getWindowsCount=s.getWindowsCount,t.getWindowId=s.getWindowId,t.getWindowById=s.getWindowById,t.hasWindow=s.hasWindow,t.onDidRegisterWindow=s.onDidRegisterWindow,t.onWillUnregisterWindow=s.onWillUnregisterWindow,t.onDidUnregisterWindow=s.onDidUnregisterWindow;class b{constructor(e,t,i,s){this._node=e,this._type=t,this._handler=i,this._options=s||!1,this._node.addEventListener(this._type,this._handler,this._options)}dispose(){this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}}function C(e,t,i,s){return new b(e,t,i,s)}function y(e,t){return function(i){return t(new c.StandardMouseEvent(e,i))}}function w(e,i,s){return C(e,g.isIOS&&l.BrowserFeatures.pointerEvents?t.EventType.POINTER_DOWN:t.EventType.MOUSE_DOWN,i,s)}function E(e,i,s){return C(e,g.isIOS&&l.BrowserFeatures.pointerEvents?t.EventType.POINTER_UP:t.EventType.MOUSE_UP,i,s)}t.addStandardDisposableListener=function(e,i,s,r){let n=s;return"click"===i||"mousedown"===i||"contextmenu"===i?n=y((0,t.getWindow)(e),s):"keydown"!==i&&"keypress"!==i&&"keyup"!==i||(n=function(e){return function(t){return e(new h.StandardKeyboardEvent(t))}}(s)),C(e,i,n,r)},t.addStandardDisposableGenericMouseDownListener=function(e,i,s){return w(e,y((0,t.getWindow)(e),i),s)},t.addStandardDisposableGenericMouseUpListener=function(e,i,s){return E(e,y((0,t.getWindow)(e),i),s)};class D extends d.AbstractIdleValue{constructor(e,t){super(e,t)}}t.WindowIdleValue=D;class L extends d.IntervalTimer{constructor(e){super(),this.defaultTarget=e&&(0,t.getWindow)(e)}cancelAndSet(e,t,i){return super.cancelAndSet(e,t,i??this.defaultTarget)}}t.WindowIntervalTimer=L;class R{constructor(e,t=0){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){(0,u.onUnexpectedError)(e)}}static sort(e,t){return t.priority-e.priority}}!function(){const e=new Map,i=new Map,s=new Map,r=new Map;t.scheduleAtNextAnimationFrame=(n,o,a=0)=>{const l=(0,t.getWindowId)(n),h=new R(o,a);let c=e.get(l);return c||(c=[],e.set(l,c)),c.push(h),s.get(l)||(s.set(l,!0),n.requestAnimationFrame((()=>(t=>{s.set(t,!1);const n=e.get(t)??[];for(i.set(t,n),e.set(t,[]),r.set(t,!0);n.length>0;)n.sort(R.sort),n.shift().execute();r.set(t,!1)})(l)))),h},t.runAtThisOrScheduleAtNextAnimationFrame=(e,s,n)=>{const o=(0,t.getWindowId)(e);if(r.get(o)){const e=new R(s,n);let t=i.get(o);return t||(t=[],i.set(o,t)),t.push(e),e}return(0,t.scheduleAtNextAnimationFrame)(e,s,n)}}();const A=function(e,t){return t};class T extends p.Disposable{constructor(e,t,i,s=A,r=8){super();let n=null,o=0;const a=this._register(new d.TimeoutTimer),l=()=>{o=(new Date).getTime(),i(n),n=null};this._register(C(e,t,(e=>{n=s(n,e);const t=(new Date).getTime()-o;t>=r?(a.cancel(),l()):a.setIfNotSet(l,r-t)})))}}function k(e){return(0,t.getWindow)(e).getComputedStyle(e,null)}class M{static convertToPixels(e,t){return parseFloat(t)||0}static getDimension(e,t,i){const s=k(e),r=s?s.getPropertyValue(t):"0";return M.convertToPixels(e,r)}static getBorderLeftWidth(e){return M.getDimension(e,"border-left-width","borderLeftWidth")}static getBorderRightWidth(e){return M.getDimension(e,"border-right-width","borderRightWidth")}static getBorderTopWidth(e){return M.getDimension(e,"border-top-width","borderTopWidth")}static getBorderBottomWidth(e){return M.getDimension(e,"border-bottom-width","borderBottomWidth")}static getPaddingLeft(e){return M.getDimension(e,"padding-left","paddingLeft")}static getPaddingRight(e){return M.getDimension(e,"padding-right","paddingRight")}static getPaddingTop(e){return M.getDimension(e,"padding-top","paddingTop")}static getPaddingBottom(e){return M.getDimension(e,"padding-bottom","paddingBottom")}static getMarginLeft(e){return M.getDimension(e,"margin-left","marginLeft")}static getMarginTop(e){return M.getDimension(e,"margin-top","marginTop")}static getMarginRight(e){return M.getDimension(e,"margin-right","marginRight")}static getMarginBottom(e){return M.getDimension(e,"margin-bottom","marginBottom")}}class O{static{this.None=new O(0,0)}constructor(e,t){this.width=e,this.height=t}with(e=this.width,t=this.height){return e!==this.width||t!==this.height?new O(e,t):this}static is(e){return"object"==typeof e&&"number"==typeof e.height&&"number"==typeof e.width}static lift(e){return e instanceof O?e:new O(e.width,e.height)}static equals(e,t){return e===t||!(!e||!t)&&e.width===t.width&&e.height===t.height}}function I(e){let t=e.offsetParent,i=e.offsetTop,s=e.offsetLeft;for(;null!==(e=e.parentNode)&&e!==e.ownerDocument.body&&e!==e.ownerDocument.documentElement;){i-=e.scrollTop;const r=W(e)?null:k(e);r&&(s-="rtl"!==r.direction?e.scrollLeft:-e.scrollLeft),e===t&&(s+=M.getBorderLeftWidth(e),i+=M.getBorderTopWidth(e),i+=e.offsetTop,s+=e.offsetLeft,t=e.offsetParent)}return{left:s,top:i}}function P(e){const t=M.getMarginLeft(e)+M.getMarginRight(e);return e.offsetWidth+t}function x(e){const t=M.getMarginLeft(e)+M.getMarginRight(e);return e.scrollWidth+t}function B(e,t){return Boolean(t?.contains(e))}t.Dimension=O;const N="parentFlowToElementId";function U(e){const t=e.dataset[N];return"string"==typeof t?e.ownerDocument.getElementById(t):null}function F(e,t,i){for(;e&&e.nodeType===e.ELEMENT_NODE;){if(e.classList.contains(t))return e;if(i)if("string"==typeof i){if(e.classList.contains(i))return null}else if(e===i)return null;e=e.parentNode}return null}function W(e){return e&&!!e.host&&!!e.mode}function H(e){for(;e.parentNode;){if(e===e.ownerDocument?.body)return null;e=e.parentNode}return W(e)?e:null}function K(){let e=z().activeElement;for(;e?.shadowRoot;)e=e.shadowRoot.activeElement;return e}function z(){return(0,t.getWindowsCount)()<=1?v.mainWindow.document:Array.from((0,t.getWindows)()).map((({window:e})=>e.document)).find((e=>e.hasFocus()))??v.mainWindow.document}const j=new Map;class ${constructor(){this._currentCssStyle="",this._styleSheet=void 0}setStyle(e){e!==this._currentCssStyle&&(this._currentCssStyle=e,this._styleSheet?this._styleSheet.innerText=e:this._styleSheet=V(v.mainWindow.document.head,(t=>t.innerText=e)))}dispose(){this._styleSheet&&(this._styleSheet.remove(),this._styleSheet=void 0)}}function V(e=v.mainWindow.document.head,i,s){const r=document.createElement("style");if(r.type="text/css",r.media="screen",i?.(r),e.appendChild(r),s&&s.add((0,p.toDisposable)((()=>r.remove()))),e===v.mainWindow.document.head){const e=new Set;j.set(r,e);for(const{window:i,disposables:n}of(0,t.getWindows)()){if(i===v.mainWindow)continue;const t=n.add(G(r,e,i));s?.add(t)}}return r}function G(e,i,s){const r=new p.DisposableStore,n=e.cloneNode(!0);s.document.head.appendChild(n),r.add((0,p.toDisposable)((()=>n.remove())));for(const t of Z(e))n.sheet?.insertRule(t.cssText,n.sheet?.cssRules.length);return r.add(t.sharedMutationObserver.observe(e,r,{childList:!0})((()=>{n.textContent=e.textContent}))),i.add(n),r.add((0,p.toDisposable)((()=>i.delete(n)))),r}function q(e,t=v.mainWindow.document.head){const i=document.createElement(e);return t.appendChild(i),i}t.sharedMutationObserver=new class{constructor(){this.mutationObservers=new Map}observe(e,t,i){let s=this.mutationObservers.get(e);s||(s=new Map,this.mutationObservers.set(e,s));const r=(0,m.hash)(i);let n=s.get(r);if(n)n.users+=1;else{const o=new _.Emitter,a=new MutationObserver((e=>o.fire(e)));a.observe(e,i);const l=n={users:1,observer:a,onDidMutate:o.event};t.add((0,p.toDisposable)((()=>{l.users-=1,0===l.users&&(o.dispose(),a.disconnect(),s?.delete(r),0===s?.size&&this.mutationObservers.delete(e))}))),s.set(r,n)}return n.onDidMutate}};let X=null;function Y(){return X||(X=V()),X}function Z(e){return e?.sheet?.rules?e.sheet.rules:e?.sheet?.cssRules?e.sheet.cssRules:[]}function J(e){return"string"==typeof e.selectorText}function Q(e){return e instanceof HTMLElement||e instanceof(0,t.getWindow)(e).HTMLElement}t.EventType={CLICK:"click",AUXCLICK:"auxclick",DBLCLICK:"dblclick",MOUSE_UP:"mouseup",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_MOVE:"mousemove",MOUSE_OUT:"mouseout",MOUSE_ENTER:"mouseenter",MOUSE_LEAVE:"mouseleave",MOUSE_WHEEL:"wheel",POINTER_UP:"pointerup",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_LEAVE:"pointerleave",CONTEXT_MENU:"contextmenu",WHEEL:"wheel",KEY_DOWN:"keydown",KEY_PRESS:"keypress",KEY_UP:"keyup",LOAD:"load",BEFORE_UNLOAD:"beforeunload",UNLOAD:"unload",PAGE_SHOW:"pageshow",PAGE_HIDE:"pagehide",PASTE:"paste",ABORT:"abort",ERROR:"error",RESIZE:"resize",SCROLL:"scroll",FULLSCREEN_CHANGE:"fullscreenchange",WK_FULLSCREEN_CHANGE:"webkitfullscreenchange",SELECT:"select",CHANGE:"change",SUBMIT:"submit",RESET:"reset",FOCUS:"focus",FOCUS_IN:"focusin",FOCUS_OUT:"focusout",BLUR:"blur",INPUT:"input",STORAGE:"storage",DRAG_START:"dragstart",DRAG:"drag",DRAG_ENTER:"dragenter",DRAG_LEAVE:"dragleave",DRAG_OVER:"dragover",DROP:"drop",DRAG_END:"dragend",ANIMATION_START:a.isWebKit?"webkitAnimationStart":"animationstart",ANIMATION_END:a.isWebKit?"webkitAnimationEnd":"animationend",ANIMATION_ITERATION:a.isWebKit?"webkitAnimationIteration":"animationiteration"},t.EventHelper={stop:(e,t)=>(e.preventDefault(),t&&e.stopPropagation(),e)};class ee extends p.Disposable{static hasFocusWithin(e){if(Q(e)){const t=H(e);return B(t?t.activeElement:e.ownerDocument.activeElement,e)}{const t=e;return B(t.document.activeElement,t.document)}}constructor(e){super(),this._onDidFocus=this._register(new _.Emitter),this.onDidFocus=this._onDidFocus.event,this._onDidBlur=this._register(new _.Emitter),this.onDidBlur=this._onDidBlur.event;let i=ee.hasFocusWithin(e),s=!1;const r=()=>{s=!1,i||(i=!0,this._onDidFocus.fire())},n=()=>{i&&(s=!0,(Q(e)?(0,t.getWindow)(e):e).setTimeout((()=>{s&&(s=!1,i=!1,this._onDidBlur.fire())}),0))};this._refreshStateHandler=()=>{ee.hasFocusWithin(e)!==i&&(i?n():r())},this._register(C(e,t.EventType.FOCUS,r,!0)),this._register(C(e,t.EventType.BLUR,n,!0)),Q(e)&&(this._register(C(e,t.EventType.FOCUS_IN,(()=>this._refreshStateHandler()))),this._register(C(e,t.EventType.FOCUS_OUT,(()=>this._refreshStateHandler()))))}refreshState(){this._refreshStateHandler()}}function te(e,...t){if(e.append(...t),1===t.length&&"string"!=typeof t[0])return t[0]}const ie=/([\w\-]+)?(#([\w\-]+))?((\.([\w\-]+))*)/;var se;function re(e,t,i,...s){const r=ie.exec(t);if(!r)throw new Error("Bad use of emmet");const n=r[1]||"div";let o;return o=e!==se.HTML?document.createElementNS(e,n):document.createElement(n),r[3]&&(o.id=r[3]),r[4]&&(o.className=r[4].replace(/\./g," ").trim()),i&&Object.entries(i).forEach((([e,t])=>{void 0!==t&&(/^on\w+$/.test(e)?o[e]=t:"selected"===e?t&&o.setAttribute(e,"true"):o.setAttribute(e,t))})),o.append(...s),o}function ne(e,t,...i){return re(se.HTML,e,t,...i)}function oe(...e){for(const t of e)t.style.display="",t.removeAttribute("aria-hidden")}function ae(...e){for(const t of e)t.style.display="none",t.setAttribute("aria-hidden","true")}!function(e){e.HTML="http://www.w3.org/1999/xhtml",e.SVG="http://www.w3.org/2000/svg"}(se||(t.Namespace=se={})),ne.SVG=function(e,t,...i){return re(se.SVG,e,t,...i)};const le=780,he=640;var ce;!function(e){e[e.DOCUMENT=1]="DOCUMENT",e[e.BROWSER=2]="BROWSER"}(ce||(t.DetectedFullscreenMode=ce={}));class de extends _.Emitter{constructor(){super(),this._subscriptions=new p.DisposableStore,this._keyStatus={altKey:!1,shiftKey:!1,ctrlKey:!1,metaKey:!1},this._subscriptions.add(_.Event.runAndSubscribe(t.onDidRegisterWindow,(({window:e,disposables:t})=>this.registerListeners(e,t)),{window:v.mainWindow,disposables:this._subscriptions}))}registerListeners(e,t){t.add(C(e,"keydown",(e=>{if(e.defaultPrevented)return;const t=new h.StandardKeyboardEvent(e);if(t.keyCode!==f.KeyCode.Alt||!e.repeat){if(e.altKey&&!this._keyStatus.altKey)this._keyStatus.lastKeyPressed="alt";else if(e.ctrlKey&&!this._keyStatus.ctrlKey)this._keyStatus.lastKeyPressed="ctrl";else if(e.metaKey&&!this._keyStatus.metaKey)this._keyStatus.lastKeyPressed="meta";else if(e.shiftKey&&!this._keyStatus.shiftKey)this._keyStatus.lastKeyPressed="shift";else{if(t.keyCode===f.KeyCode.Alt)return;this._keyStatus.lastKeyPressed=void 0}this._keyStatus.altKey=e.altKey,this._keyStatus.ctrlKey=e.ctrlKey,this._keyStatus.metaKey=e.metaKey,this._keyStatus.shiftKey=e.shiftKey,this._keyStatus.lastKeyPressed&&(this._keyStatus.event=e,this.fire(this._keyStatus))}}),!0)),t.add(C(e,"keyup",(e=>{e.defaultPrevented||(!e.altKey&&this._keyStatus.altKey?this._keyStatus.lastKeyReleased="alt":!e.ctrlKey&&this._keyStatus.ctrlKey?this._keyStatus.lastKeyReleased="ctrl":!e.metaKey&&this._keyStatus.metaKey?this._keyStatus.lastKeyReleased="meta":!e.shiftKey&&this._keyStatus.shiftKey?this._keyStatus.lastKeyReleased="shift":this._keyStatus.lastKeyReleased=void 0,this._keyStatus.lastKeyPressed!==this._keyStatus.lastKeyReleased&&(this._keyStatus.lastKeyPressed=void 0),this._keyStatus.altKey=e.altKey,this._keyStatus.ctrlKey=e.ctrlKey,this._keyStatus.metaKey=e.metaKey,this._keyStatus.shiftKey=e.shiftKey,this._keyStatus.lastKeyReleased&&(this._keyStatus.event=e,this.fire(this._keyStatus)))}),!0)),t.add(C(e.document.body,"mousedown",(()=>{this._keyStatus.lastKeyPressed=void 0}),!0)),t.add(C(e.document.body,"mouseup",(()=>{this._keyStatus.lastKeyPressed=void 0}),!0)),t.add(C(e.document.body,"mousemove",(e=>{e.buttons&&(this._keyStatus.lastKeyPressed=void 0)}),!0)),t.add(C(e,"blur",(()=>{this.resetKeyStatus()})))}get keyStatus(){return this._keyStatus}get isModifierPressed(){return this._keyStatus.altKey||this._keyStatus.ctrlKey||this._keyStatus.metaKey||this._keyStatus.shiftKey}resetKeyStatus(){this.doResetKeyStatus(),this.fire(this._keyStatus)}doResetKeyStatus(){this._keyStatus={altKey:!1,shiftKey:!1,ctrlKey:!1,metaKey:!1}}static getInstance(){return de.instance||(de.instance=new de),de.instance}dispose(){super.dispose(),this._subscriptions.dispose()}}t.ModifierKeyEmitter=de;class ue extends p.Disposable{constructor(e,t){super(),this.element=e,this.callbacks=t,this.counter=0,this.dragStartTime=0,this.registerListeners()}registerListeners(){this.callbacks.onDragStart&&this._register(C(this.element,t.EventType.DRAG_START,(e=>{this.callbacks.onDragStart?.(e)}))),this.callbacks.onDrag&&this._register(C(this.element,t.EventType.DRAG,(e=>{this.callbacks.onDrag?.(e)}))),this._register(C(this.element,t.EventType.DRAG_ENTER,(e=>{this.counter++,this.dragStartTime=e.timeStamp,this.callbacks.onDragEnter?.(e)}))),this._register(C(this.element,t.EventType.DRAG_OVER,(e=>{e.preventDefault(),this.callbacks.onDragOver?.(e,e.timeStamp-this.dragStartTime)}))),this._register(C(this.element,t.EventType.DRAG_LEAVE,(e=>{this.counter--,0===this.counter&&(this.dragStartTime=0,this.callbacks.onDragLeave?.(e))}))),this._register(C(this.element,t.EventType.DRAG_END,(e=>{this.counter=0,this.dragStartTime=0,this.callbacks.onDragEnd?.(e)}))),this._register(C(this.element,t.EventType.DROP,(e=>{this.counter=0,this.dragStartTime=0,this.callbacks.onDrop?.(e)})))}}t.DragAndDropObserver=ue;const _e=/(?[\w\-]+)?(?:#(?[\w\-]+))?(?(?:\.(?:[\w\-]+))*)(?:@(?(?:[\w\_])+))?/;function fe(e){return e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}function pe(e,t,i){for(const{name:s,value:r}of e.attributes)i&&!i.includes(s)||t.setAttribute(s,r)}function ge(e,t,i){const s=e.getAttribute(i);s?t.setAttribute(i,s):t.removeAttribute(i)}t.SafeTriangle=class{constructor(e,t,i){this.originX=e,this.originY=t,this.triangles=[];const{top:s,left:r,right:n,bottom:o}=i.getBoundingClientRect(),a=this.triangles;let l=0;a[l++]=r,a[l++]=s,a[l++]=n,a[l++]=s,a[l++]=r,a[l++]=s,a[l++]=r,a[l++]=o,a[l++]=n,a[l++]=s,a[l++]=n,a[l++]=o,a[l++]=r,a[l++]=o,a[l++]=n,a[l++]=o}contains(e,t){const{triangles:i,originX:s,originY:r}=this;for(let n=0;n<4;n++)if((0,S.isPointWithinTriangle)(e,t,s,r,i[2*n],i[2*n+1],i[2*n+2],i[2*n+3]))return!0;return!1}}},9675:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.FastDomNode=void 0,t.createFastDomNode=function(e){return new i(e)};class i{constructor(e){this.domNode=e,this._maxWidth="",this._width="",this._height="",this._top="",this._left="",this._bottom="",this._right="",this._paddingTop="",this._paddingLeft="",this._paddingBottom="",this._paddingRight="",this._fontFamily="",this._fontWeight="",this._fontSize="",this._fontStyle="",this._fontFeatureSettings="",this._fontVariationSettings="",this._textDecoration="",this._lineHeight="",this._letterSpacing="",this._className="",this._display="",this._position="",this._visibility="",this._color="",this._backgroundColor="",this._layerHint=!1,this._contain="none",this._boxShadow=""}setMaxWidth(e){const t=s(e);this._maxWidth!==t&&(this._maxWidth=t,this.domNode.style.maxWidth=this._maxWidth)}setWidth(e){const t=s(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){const t=s(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){const t=s(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){const t=s(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){const t=s(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){const t=s(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setPaddingTop(e){const t=s(e);this._paddingTop!==t&&(this._paddingTop=t,this.domNode.style.paddingTop=this._paddingTop)}setPaddingLeft(e){const t=s(e);this._paddingLeft!==t&&(this._paddingLeft=t,this.domNode.style.paddingLeft=this._paddingLeft)}setPaddingBottom(e){const t=s(e);this._paddingBottom!==t&&(this._paddingBottom=t,this.domNode.style.paddingBottom=this._paddingBottom)}setPaddingRight(e){const t=s(e);this._paddingRight!==t&&(this._paddingRight=t,this.domNode.style.paddingRight=this._paddingRight)}setFontFamily(e){this._fontFamily!==e&&(this._fontFamily=e,this.domNode.style.fontFamily=this._fontFamily)}setFontWeight(e){this._fontWeight!==e&&(this._fontWeight=e,this.domNode.style.fontWeight=this._fontWeight)}setFontSize(e){const t=s(e);this._fontSize!==t&&(this._fontSize=t,this.domNode.style.fontSize=this._fontSize)}setFontStyle(e){this._fontStyle!==e&&(this._fontStyle=e,this.domNode.style.fontStyle=this._fontStyle)}setFontFeatureSettings(e){this._fontFeatureSettings!==e&&(this._fontFeatureSettings=e,this.domNode.style.fontFeatureSettings=this._fontFeatureSettings)}setFontVariationSettings(e){this._fontVariationSettings!==e&&(this._fontVariationSettings=e,this.domNode.style.fontVariationSettings=this._fontVariationSettings)}setTextDecoration(e){this._textDecoration!==e&&(this._textDecoration=e,this.domNode.style.textDecoration=this._textDecoration)}setLineHeight(e){const t=s(e);this._lineHeight!==t&&(this._lineHeight=t,this.domNode.style.lineHeight=this._lineHeight)}setLetterSpacing(e){const t=s(e);this._letterSpacing!==t&&(this._letterSpacing=t,this.domNode.style.letterSpacing=this._letterSpacing)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setDisplay(e){this._display!==e&&(this._display=e,this.domNode.style.display=this._display)}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setVisibility(e){this._visibility!==e&&(this._visibility=e,this.domNode.style.visibility=this._visibility)}setColor(e){this._color!==e&&(this._color=e,this.domNode.style.color=this._color)}setBackgroundColor(e){this._backgroundColor!==e&&(this._backgroundColor=e,this.domNode.style.backgroundColor=this._backgroundColor)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=this._layerHint?"translate3d(0px, 0px, 0px)":"")}setBoxShadow(e){this._boxShadow!==e&&(this._boxShadow=e,this.domNode.style.boxShadow=e)}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}removeAttribute(e){this.domNode.removeAttribute(e)}appendChild(e){this.domNode.appendChild(e.domNode)}removeChild(e){this.domNode.removeChild(e.domNode)}}function s(e){return"number"==typeof e?`${e}px`:e}t.FastDomNode=i},8328:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.GlobalPointerMoveMonitor=void 0;const o=n(i(7093)),a=i(7150);t.GlobalPointerMoveMonitor=class{constructor(){this._hooks=new a.DisposableStore,this._pointerMoveCallback=null,this._onStopCallback=null}dispose(){this.stopMonitoring(!1),this._hooks.dispose()}stopMonitoring(e,t){if(!this.isMonitoring())return;this._hooks.clear(),this._pointerMoveCallback=null;const i=this._onStopCallback;this._onStopCallback=null,e&&i&&i(t)}isMonitoring(){return!!this._pointerMoveCallback}startMonitoring(e,t,i,s,r){this.isMonitoring()&&this.stopMonitoring(!1),this._pointerMoveCallback=s,this._onStopCallback=r;let n=e;try{e.setPointerCapture(t),this._hooks.add((0,a.toDisposable)((()=>{try{e.releasePointerCapture(t)}catch(e){}})))}catch(t){n=o.getWindow(e)}this._hooks.add(o.addDisposableListener(n,o.EventType.POINTER_MOVE,(e=>{e.buttons===i?(e.preventDefault(),this._pointerMoveCallback(e)):this.stopMonitoring(!0)}))),this._hooks.add(o.addDisposableListener(n,o.EventType.POINTER_UP,(e=>this.stopMonitoring(!0))))}}},6609:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.IframeUtils=void 0,t.parentOriginHash=async function(e,t){if(!crypto.subtle)throw new Error("'crypto.subtle' is not available so webviews will not work. This is likely because the editor is not running in a secure context (https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).");const i=JSON.stringify({parentOrigin:e,salt:t}),s=(new TextEncoder).encode(i);return function(e){const t=Array.from(new Uint8Array(e)).map((e=>e.toString(16).padStart(2,"0"))).join("");return BigInt(`0x${t}`).toString(32).padStart(52,"0")}(await crypto.subtle.digest("sha-256",s))};const i=new WeakMap;function s(e){if(!e.parent||e.parent===e)return null;try{const t=e.location,i=e.parent.location;if("null"!==t.origin&&"null"!==i.origin&&t.origin!==i.origin)return null}catch(e){return null}return e.parent}t.IframeUtils=class{static getSameOriginWindowChain(e){let t=i.get(e);if(!t){t=[],i.set(e,t);let r,n=e;do{r=s(n),r?t.push({window:new WeakRef(n),iframeElement:n.frameElement||null}):t.push({window:new WeakRef(n),iframeElement:null}),n=r}while(n)}return t.slice(0)}static getPositionOfChildWindowRelativeToAncestorWindow(e,t){if(!t||e===t)return{top:0,left:0};let i=0,s=0;const r=this.getSameOriginWindowChain(e);for(const e of r){const r=e.window.deref();if(i+=r?.scrollY??0,s+=r?.scrollX??0,r===t)break;if(!e.iframeElement)break;const n=e.iframeElement.getBoundingClientRect();i+=n.top,s+=n.left}return{top:i,left:s}}}},5394:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.StandardKeyboardEvent=void 0,t.printKeyboardEvent=function(e){const t=[];return e.ctrlKey&&t.push("ctrl"),e.shiftKey&&t.push("shift"),e.altKey&&t.push("alt"),e.metaKey&&t.push("meta"),`modifiers: [${t.join(",")}], code: ${e.code}, keyCode: ${e.keyCode}, key: ${e.key}`},t.printStandardKeyboardEvent=function(e){const t=[];return e.ctrlKey&&t.push("ctrl"),e.shiftKey&&t.push("shift"),e.altKey&&t.push("alt"),e.metaKey&&t.push("meta"),`modifiers: [${t.join(",")}], code: ${e.code}, keyCode: ${e.keyCode} ('${a.KeyCodeUtils.toString(e.keyCode)}')`};const o=n(i(4333)),a=i(7883),l=i(2811),h=n(i(8163)),c=h.isMacintosh?a.KeyMod.WinCtrl:a.KeyMod.CtrlCmd,d=a.KeyMod.Alt,u=a.KeyMod.Shift,_=h.isMacintosh?a.KeyMod.CtrlCmd:a.KeyMod.WinCtrl;t.StandardKeyboardEvent=class{constructor(e){this._standardKeyboardEventBrand=!0;const t=e;this.browserEvent=t,this.target=t.target,this.ctrlKey=t.ctrlKey,this.shiftKey=t.shiftKey,this.altKey=t.altKey,this.metaKey=t.metaKey,this.altGraphKey=t.getModifierState?.("AltGraph"),this.keyCode=function(e){if(e.charCode){const t=String.fromCharCode(e.charCode).toUpperCase();return a.KeyCodeUtils.fromString(t)}const t=e.keyCode;if(3===t)return a.KeyCode.PauseBreak;if(o.isFirefox)switch(t){case 59:return a.KeyCode.Semicolon;case 60:if(h.isLinux)return a.KeyCode.IntlBackslash;break;case 61:return a.KeyCode.Equal;case 107:return a.KeyCode.NumpadAdd;case 109:return a.KeyCode.NumpadSubtract;case 173:return a.KeyCode.Minus;case 224:if(h.isMacintosh)return a.KeyCode.Meta}else if(o.isWebKit){if(h.isMacintosh&&93===t)return a.KeyCode.Meta;if(!h.isMacintosh&&92===t)return a.KeyCode.Meta}return a.EVENT_KEY_CODE_MAP[t]||a.KeyCode.Unknown}(t),this.code=t.code,this.ctrlKey=this.ctrlKey||this.keyCode===a.KeyCode.Ctrl,this.altKey=this.altKey||this.keyCode===a.KeyCode.Alt,this.shiftKey=this.shiftKey||this.keyCode===a.KeyCode.Shift,this.metaKey=this.metaKey||this.keyCode===a.KeyCode.Meta,this._asKeybinding=this._computeKeybinding(),this._asKeyCodeChord=this._computeKeyCodeChord()}preventDefault(){this.browserEvent&&this.browserEvent.preventDefault&&this.browserEvent.preventDefault()}stopPropagation(){this.browserEvent&&this.browserEvent.stopPropagation&&this.browserEvent.stopPropagation()}toKeyCodeChord(){return this._asKeyCodeChord}equals(e){return this._asKeybinding===e}_computeKeybinding(){let e=a.KeyCode.Unknown;this.keyCode!==a.KeyCode.Ctrl&&this.keyCode!==a.KeyCode.Shift&&this.keyCode!==a.KeyCode.Alt&&this.keyCode!==a.KeyCode.Meta&&(e=this.keyCode);let t=0;return this.ctrlKey&&(t|=c),this.altKey&&(t|=d),this.shiftKey&&(t|=u),this.metaKey&&(t|=_),t|=e,t}_computeKeyCodeChord(){let e=a.KeyCode.Unknown;return this.keyCode!==a.KeyCode.Ctrl&&this.keyCode!==a.KeyCode.Shift&&this.keyCode!==a.KeyCode.Alt&&this.keyCode!==a.KeyCode.Meta&&(e=this.keyCode),new l.KeyCodeChord(this.ctrlKey,this.shiftKey,this.altKey,this.metaKey,e)}}},5964:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.StandardWheelEvent=t.DragMouseEvent=t.StandardMouseEvent=void 0;const o=n(i(4333)),a=i(6609),l=n(i(8163));class h{constructor(e,t){this.timestamp=Date.now(),this.browserEvent=t,this.leftButton=0===t.button,this.middleButton=1===t.button,this.rightButton=2===t.button,this.buttons=t.buttons,this.target=t.target,this.detail=t.detail||1,"dblclick"===t.type&&(this.detail=2),this.ctrlKey=t.ctrlKey,this.shiftKey=t.shiftKey,this.altKey=t.altKey,this.metaKey=t.metaKey,"number"==typeof t.pageX?(this.posx=t.pageX,this.posy=t.pageY):(this.posx=t.clientX+this.target.ownerDocument.body.scrollLeft+this.target.ownerDocument.documentElement.scrollLeft,this.posy=t.clientY+this.target.ownerDocument.body.scrollTop+this.target.ownerDocument.documentElement.scrollTop);const i=a.IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(e,t.view);this.posx-=i.left,this.posy-=i.top}preventDefault(){this.browserEvent.preventDefault()}stopPropagation(){this.browserEvent.stopPropagation()}}t.StandardMouseEvent=h,t.DragMouseEvent=class extends h{constructor(e,t){super(e,t),this.dataTransfer=t.dataTransfer}},t.StandardWheelEvent=class{constructor(e,t=0,i=0){this.browserEvent=e||null,this.target=e?e.target||e.targetNode||e.srcElement:null,this.deltaY=i,this.deltaX=t;let s=!1;if(o.isChrome){const e=navigator.userAgent.match(/Chrome\/(\d+)/);s=(e?parseInt(e[1]):123)<=122}if(e){const t=e,i=e,r=e.view?.devicePixelRatio||1;if(void 0!==t.wheelDeltaY)this.deltaY=s?t.wheelDeltaY/(120*r):t.wheelDeltaY/120;else if(void 0!==i.VERTICAL_AXIS&&i.axis===i.VERTICAL_AXIS)this.deltaY=-i.detail/3;else if("wheel"===e.type){const t=e;t.deltaMode===t.DOM_DELTA_LINE?o.isFirefox&&!l.isMacintosh?this.deltaY=-e.deltaY/3:this.deltaY=-e.deltaY:this.deltaY=-e.deltaY/40}if(void 0!==t.wheelDeltaX)o.isSafari&&l.isWindows?this.deltaX=-t.wheelDeltaX/120:this.deltaX=s?t.wheelDeltaX/(120*r):t.wheelDeltaX/120;else if(void 0!==i.HORIZONTAL_AXIS&&i.axis===i.HORIZONTAL_AXIS)this.deltaX=-e.detail/3;else if("wheel"===e.type){const t=e;t.deltaMode===t.DOM_DELTA_LINE?o.isFirefox&&!l.isMacintosh?this.deltaX=-e.deltaX/3:this.deltaX=-e.deltaX:this.deltaX=-e.deltaX/40}0===this.deltaY&&0===this.deltaX&&e.wheelDelta&&(this.deltaY=s?e.wheelDelta/(120*r):e.wheelDelta/120)}}preventDefault(){this.browserEvent?.preventDefault()}stopPropagation(){this.browserEvent?.stopPropagation()}}},8594:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},o=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.Gesture=t.EventType=void 0;const a=o(i(7093)),l=i(4693),h=o(i(3058)),c=i(4838),d=i(802),u=i(7150),_=i(6317);var f;!function(e){e.Tap="-xterm-gesturetap",e.Change="-xterm-gesturechange",e.Start="-xterm-gesturestart",e.End="-xterm-gesturesend",e.Contextmenu="-xterm-gesturecontextmenu"}(f||(t.EventType=f={}));class p extends u.Disposable{static{this.SCROLL_FRICTION=-.005}static{this.HOLD_DELAY=700}static{this.CLEAR_TAP_COUNT_TIME=400}constructor(){super(),this.dispatched=!1,this.targets=new _.LinkedList,this.ignoreTargets=new _.LinkedList,this.activeTouches={},this.handle=null,this._lastSetTapCountTime=0,this._register(d.Event.runAndSubscribe(a.onDidRegisterWindow,(({window:e,disposables:t})=>{t.add(a.addDisposableListener(e.document,"touchstart",(e=>this.onTouchStart(e)),{passive:!1})),t.add(a.addDisposableListener(e.document,"touchend",(t=>this.onTouchEnd(e,t)))),t.add(a.addDisposableListener(e.document,"touchmove",(e=>this.onTouchMove(e)),{passive:!1}))}),{window:l.mainWindow,disposables:this._store}))}static addTarget(e){if(!p.isTouchDevice())return u.Disposable.None;p.INSTANCE||(p.INSTANCE=(0,u.markAsSingleton)(new p));const t=p.INSTANCE.targets.push(e);return(0,u.toDisposable)(t)}static ignoreTarget(e){if(!p.isTouchDevice())return u.Disposable.None;p.INSTANCE||(p.INSTANCE=(0,u.markAsSingleton)(new p));const t=p.INSTANCE.ignoreTargets.push(e);return(0,u.toDisposable)(t)}static isTouchDevice(){return"ontouchstart"in l.mainWindow||navigator.maxTouchPoints>0}dispose(){this.handle&&(this.handle.dispose(),this.handle=null),super.dispose()}onTouchStart(e){const t=Date.now();this.handle&&(this.handle.dispose(),this.handle=null);for(let i=0,s=e.targetTouches.length;i=p.HOLD_DELAY&&Math.abs(o.initialPageX-h.tail(o.rollingPageX))<30&&Math.abs(o.initialPageY-h.tail(o.rollingPageY))<30){const e=this.newGestureEvent(f.Contextmenu,o.initialTarget);e.pageX=h.tail(o.rollingPageX),e.pageY=h.tail(o.rollingPageY),this.dispatchEvent(e)}else if(1===s){const t=h.tail(o.rollingPageX),s=h.tail(o.rollingPageY),r=h.tail(o.rollingTimestamps)-o.rollingTimestamps[0],n=t-o.rollingPageX[0],a=s-o.rollingPageY[0],l=[...this.targets].filter((e=>o.initialTarget instanceof Node&&e.contains(o.initialTarget)));this.inertia(e,l,i,Math.abs(n)/r,n>0?1:-1,t,Math.abs(a)/r,a>0?1:-1,s)}this.dispatchEvent(this.newGestureEvent(f.End,o.initialTarget)),delete this.activeTouches[n.identifier]}this.dispatched&&(t.preventDefault(),t.stopPropagation(),this.dispatched=!1)}newGestureEvent(e,t){const i=document.createEvent("CustomEvent");return i.initEvent(e,!1,!0),i.initialTarget=t,i.tapCount=0,i}dispatchEvent(e){if(e.type===f.Tap){const t=(new Date).getTime();let i=0;i=t-this._lastSetTapCountTime>p.CLEAR_TAP_COUNT_TIME?1:2,this._lastSetTapCountTime=t,e.tapCount=i}else e.type!==f.Change&&e.type!==f.Contextmenu||(this._lastSetTapCountTime=0);if(e.initialTarget instanceof Node){for(const t of this.ignoreTargets)if(t.contains(e.initialTarget))return;const t=[];for(const i of this.targets)if(i.contains(e.initialTarget)){let s=0,r=e.initialTarget;for(;r&&r!==i;)s++,r=r.parentElement;t.push([s,i])}t.sort(((e,t)=>e[0]-t[0]));for(const[i,s]of t)s.dispatchEvent(e),this.dispatched=!0}}inertia(e,t,i,s,r,n,o,l,h){this.handle=a.scheduleAtNextAnimationFrame(e,(()=>{const a=Date.now(),c=a-i;let d=0,u=0,_=!0;s+=p.SCROLL_FRICTION*c,o+=p.SCROLL_FRICTION*c,s>0&&(_=!1,d=r*s*c),o>0&&(_=!1,u=l*o*c);const g=this.newGestureEvent(f.Change);g.translationX=d,g.translationY=u,t.forEach((e=>e.dispatchEvent(g))),_||this.inertia(e,t,a,s,r,n+d,o,l,h+u)}))}onTouchMove(e){const t=Date.now();for(let i=0,s=e.changedTouches.length;i3&&(r.rollingPageX.shift(),r.rollingPageY.shift(),r.rollingTimestamps.shift()),r.rollingPageX.push(s.pageX),r.rollingPageY.push(s.pageY),r.rollingTimestamps.push(t)}this.dispatched&&(e.preventDefault(),e.stopPropagation(),this.dispatched=!1)}}t.Gesture=p,n([c.memoize],p,"isTouchDevice",null)},8801:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.AbstractScrollbar=void 0;const o=n(i(7093)),a=i(9675),l=i(8328),h=i(8974),c=i(79),d=i(8286),u=n(i(8163));class _ extends d.Widget{constructor(e){super(),this._lazyRender=e.lazyRender,this._host=e.host,this._scrollable=e.scrollable,this._scrollByPage=e.scrollByPage,this._scrollbarState=e.scrollbarState,this._visibilityController=this._register(new c.ScrollbarVisibilityController(e.visibility,"visible scrollbar "+e.extraScrollbarClassName,"invisible scrollbar "+e.extraScrollbarClassName)),this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._pointerMoveMonitor=this._register(new l.GlobalPointerMoveMonitor),this._shouldRender=!0,this.domNode=(0,a.createFastDomNode)(document.createElement("div")),this.domNode.setAttribute("role","presentation"),this.domNode.setAttribute("aria-hidden","true"),this._visibilityController.setDomNode(this.domNode),this.domNode.setPosition("absolute"),this._register(o.addDisposableListener(this.domNode.domNode,o.EventType.POINTER_DOWN,(e=>this._domNodePointerDown(e))))}_createArrow(e){const t=this._register(new h.ScrollbarArrow(e));this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode)}_createSlider(e,t,i,s){this.slider=(0,a.createFastDomNode)(document.createElement("div")),this.slider.setClassName("slider"),this.slider.setPosition("absolute"),this.slider.setTop(e),this.slider.setLeft(t),"number"==typeof i&&this.slider.setWidth(i),"number"==typeof s&&this.slider.setHeight(s),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(o.addDisposableListener(this.slider.domNode,o.EventType.POINTER_DOWN,(e=>{0===e.button&&(e.preventDefault(),this._sliderPointerDown(e))}))),this.onclick(this.slider.domNode,(e=>{e.leftButton&&e.stopPropagation()}))}_onElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_onElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._onPointerDown(e)}delegatePointerDown(e){const t=this.domNode.domNode.getClientRects()[0].top,i=t+this._scrollbarState.getSliderPosition(),s=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),r=this._sliderPointerPosition(e);i<=r&&r<=s?0===e.button&&(e.preventDefault(),this._sliderPointerDown(e)):this._onPointerDown(e)}_onPointerDown(e){let t,i;if(e.target===this.domNode.domNode&&"number"==typeof e.offsetX&&"number"==typeof e.offsetY)t=e.offsetX,i=e.offsetY;else{const s=o.getDomNodePagePosition(this.domNode.domNode);t=e.pageX-s.left,i=e.pageY-s.top}const s=this._pointerDownRelativePosition(t,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(s):this._scrollbarState.getDesiredScrollPositionFromOffset(s)),0===e.button&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!(e.target&&e.target instanceof Element))return;const t=this._sliderPointerPosition(e),i=this._sliderOrthogonalPointerPosition(e),s=this._scrollbarState.clone();this.slider.toggleClassName("active",!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,(e=>{const r=this._sliderOrthogonalPointerPosition(e),n=Math.abs(r-i);if(u.isWindows&&n>140)return void this._setDesiredScrollPositionNow(s.getScrollPosition());const o=this._sliderPointerPosition(e)-t;this._setDesiredScrollPositionNow(s.getDesiredScrollPositionFromDelta(o))}),(()=>{this.slider.toggleClassName("active",!1),this._host.onDragEnd()})),this._host.onDragStart()}_setDesiredScrollPositionNow(e){const t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}}t.AbstractScrollbar=_},151:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.HorizontalScrollbar=void 0;const s=i(8801),r=i(8245),n=i(9881);class o extends s.AbstractScrollbar{constructor(e,t,i){const s=e.getScrollDimensions(),o=e.getCurrentScrollPosition();if(super({lazyRender:t.lazyRender,host:i,scrollbarState:new r.ScrollbarState(t.horizontalHasArrows?t.arrowSize:0,t.horizontal===n.ScrollbarVisibility.Hidden?0:t.horizontalScrollbarSize,t.vertical===n.ScrollbarVisibility.Hidden?0:t.verticalScrollbarSize,s.width,s.scrollWidth,o.scrollLeft),visibility:t.horizontal,extraScrollbarClassName:"horizontal",scrollable:e,scrollByPage:t.scrollByPage}),t.horizontalHasArrows)throw new Error("horizontalHasArrows is not supported in xterm.js");this._createSlider(Math.floor((t.horizontalScrollbarSize-t.horizontalSliderSize)/2),0,void 0,t.horizontalSliderSize)}_updateSlider(e,t){this.slider.setWidth(e),this.slider.setLeft(t)}_renderDomNode(e,t){this.domNode.setWidth(e),this.domNode.setHeight(t),this.domNode.setLeft(0),this.domNode.setBottom(0)}onDidScroll(e){return this._shouldRender=this._onElementScrollSize(e.scrollWidth)||this._shouldRender,this._shouldRender=this._onElementScrollPosition(e.scrollLeft)||this._shouldRender,this._shouldRender=this._onElementSize(e.width)||this._shouldRender,this._shouldRender}_pointerDownRelativePosition(e,t){return e}_sliderPointerPosition(e){return e.pageX}_sliderOrthogonalPointerPosition(e){return e.pageY}_updateScrollbarSize(e){this.slider.setHeight(e)}writeScrollPosition(e,t){e.scrollLeft=t}updateOptions(e){this.updateScrollbarSize(e.horizontal===n.ScrollbarVisibility.Hidden?0:e.horizontalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(e.vertical===n.ScrollbarVisibility.Hidden?0:e.verticalScrollbarSize),this._visibilityController.setVisibility(e.horizontal),this._scrollByPage=e.scrollByPage}}t.HorizontalScrollbar=o},8234:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.DomScrollableElement=t.SmoothScrollableElement=t.ScrollableElement=t.AbstractScrollableElement=t.MouseWheelClassifier=void 0;const o=i(4333),a=n(i(7093)),l=i(9675),h=i(5964),c=i(151),d=i(5473),u=i(8286),_=i(1758),f=i(802),p=i(7150),g=n(i(8163)),m=i(9881);class v{constructor(e,t,i){this.timestamp=e,this.deltaX=t,this.deltaY=i,this.score=0}}class S{static{this.INSTANCE=new S}constructor(){this._capacity=5,this._memory=[],this._front=-1,this._rear=-1}isPhysicalMouseWheel(){if(-1===this._front&&-1===this._rear)return!1;let e=1,t=0,i=1,s=this._rear;for(;;){const r=s===this._front?e:Math.pow(2,-i);if(e-=r,t+=this._memory[s].score*r,s===this._front)break;s=(this._capacity+s-1)%this._capacity,i++}return t<=.5}acceptStandardWheelEvent(e){if(o.isChrome){const t=a.getWindow(e.browserEvent),i=(0,o.getZoomFactor)(t);this.accept(Date.now(),e.deltaX*i,e.deltaY*i)}else this.accept(Date.now(),e.deltaX,e.deltaY)}accept(e,t,i){let s=null;const r=new v(e,t,i);-1===this._front&&-1===this._rear?(this._memory[0]=r,this._front=0,this._rear=0):(s=this._memory[this._rear],this._rear=(this._rear+1)%this._capacity,this._rear===this._front&&(this._front=(this._front+1)%this._capacity),this._memory[this._rear]=r),r.score=this._computeScore(r,s)}_computeScore(e,t){if(Math.abs(e.deltaX)>0&&Math.abs(e.deltaY)>0)return 1;let i=.5;if(this._isAlmostInt(e.deltaX)&&this._isAlmostInt(e.deltaY)||(i+=.25),t){const s=Math.abs(e.deltaX),r=Math.abs(e.deltaY),n=Math.abs(t.deltaX),o=Math.abs(t.deltaY),a=Math.max(Math.min(s,n),1),l=Math.max(Math.min(r,o),1),h=Math.max(s,n),c=Math.max(r,o);h%a==0&&c%l==0&&(i-=.5)}return Math.min(Math.max(i,0),1)}_isAlmostInt(e){return Math.abs(Math.round(e)-e)<.01}}t.MouseWheelClassifier=S;class b extends u.Widget{get options(){return this._options}constructor(e,t,i){super(),this._onScroll=this._register(new f.Emitter),this.onScroll=this._onScroll.event,this._onWillScroll=this._register(new f.Emitter),this.onWillScroll=this._onWillScroll.event,this._options=function(e){const t={lazyRender:void 0!==e.lazyRender&&e.lazyRender,className:void 0!==e.className?e.className:"",useShadows:void 0===e.useShadows||e.useShadows,handleMouseWheel:void 0===e.handleMouseWheel||e.handleMouseWheel,flipAxes:void 0!==e.flipAxes&&e.flipAxes,consumeMouseWheelIfScrollbarIsNeeded:void 0!==e.consumeMouseWheelIfScrollbarIsNeeded&&e.consumeMouseWheelIfScrollbarIsNeeded,alwaysConsumeMouseWheel:void 0!==e.alwaysConsumeMouseWheel&&e.alwaysConsumeMouseWheel,scrollYToX:void 0!==e.scrollYToX&&e.scrollYToX,mouseWheelScrollSensitivity:void 0!==e.mouseWheelScrollSensitivity?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:void 0!==e.fastScrollSensitivity?e.fastScrollSensitivity:5,scrollPredominantAxis:void 0===e.scrollPredominantAxis||e.scrollPredominantAxis,mouseWheelSmoothScroll:void 0===e.mouseWheelSmoothScroll||e.mouseWheelSmoothScroll,arrowSize:void 0!==e.arrowSize?e.arrowSize:11,listenOnDomNode:void 0!==e.listenOnDomNode?e.listenOnDomNode:null,horizontal:void 0!==e.horizontal?e.horizontal:m.ScrollbarVisibility.Auto,horizontalScrollbarSize:void 0!==e.horizontalScrollbarSize?e.horizontalScrollbarSize:10,horizontalSliderSize:void 0!==e.horizontalSliderSize?e.horizontalSliderSize:0,horizontalHasArrows:void 0!==e.horizontalHasArrows&&e.horizontalHasArrows,vertical:void 0!==e.vertical?e.vertical:m.ScrollbarVisibility.Auto,verticalScrollbarSize:void 0!==e.verticalScrollbarSize?e.verticalScrollbarSize:10,verticalHasArrows:void 0!==e.verticalHasArrows&&e.verticalHasArrows,verticalSliderSize:void 0!==e.verticalSliderSize?e.verticalSliderSize:0,scrollByPage:void 0!==e.scrollByPage&&e.scrollByPage};return t.horizontalSliderSize=void 0!==e.horizontalSliderSize?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=void 0!==e.verticalSliderSize?e.verticalSliderSize:t.verticalScrollbarSize,g.isMacintosh&&(t.className+=" mac"),t}(t),this._scrollable=i,this._register(this._scrollable.onScroll((e=>{this._onWillScroll.fire(e),this._onDidScroll(e),this._onScroll.fire(e)})));const s={onMouseWheel:e=>this._onMouseWheel(e),onDragStart:()=>this._onDragStart(),onDragEnd:()=>this._onDragEnd()};this._verticalScrollbar=this._register(new d.VerticalScrollbar(this._scrollable,this._options,s)),this._horizontalScrollbar=this._register(new c.HorizontalScrollbar(this._scrollable,this._options,s)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=(0,l.createFastDomNode)(document.createElement("div")),this._leftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=(0,l.createFastDomNode)(document.createElement("div")),this._topShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=(0,l.createFastDomNode)(document.createElement("div")),this._topLeftShadowDomNode.setClassName("shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode||this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this.onmouseover(this._listenOnDomNode,(e=>this._onMouseOver(e))),this.onmouseleave(this._listenOnDomNode,(e=>this._onMouseLeave(e))),this._hideTimeout=this._register(new _.TimeoutTimer),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}dispose(){this._mouseWheelToDispose=(0,p.dispose)(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getOverviewRulerLayoutInfo(){return{parent:this._domNode,insertBefore:this._verticalScrollbar.domNode.domNode}}delegateVerticalScrollbarPointerDown(e){this._verticalScrollbar.delegatePointerDown(e)}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}updateClassName(e){this._options.className=e,g.isMacintosh&&(this._options.className+=" mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){void 0!==e.handleMouseWheel&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),void 0!==e.mouseWheelScrollSensitivity&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),void 0!==e.fastScrollSensitivity&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),void 0!==e.scrollPredominantAxis&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),void 0!==e.horizontal&&(this._options.horizontal=e.horizontal),void 0!==e.vertical&&(this._options.vertical=e.vertical),void 0!==e.horizontalScrollbarSize&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),void 0!==e.verticalScrollbarSize&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),void 0!==e.scrollByPage&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}setRevealOnScroll(e){this._revealOnScroll=e}delegateScrollFromMouseWheelEvent(e){this._onMouseWheel(new h.StandardWheelEvent(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=(0,p.dispose)(this._mouseWheelToDispose),e)){const e=e=>{this._onMouseWheel(new h.StandardWheelEvent(e))};this._mouseWheelToDispose.push(a.addDisposableListener(this._listenOnDomNode,a.EventType.MOUSE_WHEEL,e,{passive:!1}))}}_onMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;const t=S.INSTANCE;t.acceptStandardWheelEvent(e);let i=!1;if(e.deltaY||e.deltaX){let s=e.deltaY*this._options.mouseWheelScrollSensitivity,r=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&r+s===0?r=s=0:Math.abs(s)>=Math.abs(r)?r=0:s=0),this._options.flipAxes&&([s,r]=[r,s]);const n=!g.isMacintosh&&e.browserEvent&&e.browserEvent.shiftKey;!this._options.scrollYToX&&!n||r||(r=s,s=0),e.browserEvent&&e.browserEvent.altKey&&(r*=this._options.fastScrollSensitivity,s*=this._options.fastScrollSensitivity);const o=this._scrollable.getFutureScrollPosition();let a={};if(s){const e=50*s,t=o.scrollTop-(e<0?Math.floor(e):Math.ceil(e));this._verticalScrollbar.writeScrollPosition(a,t)}if(r){const e=50*r,t=o.scrollLeft-(e<0?Math.floor(e):Math.ceil(e));this._horizontalScrollbar.writeScrollPosition(a,t)}a=this._scrollable.validateScrollPosition(a),(o.scrollLeft!==a.scrollLeft||o.scrollTop!==a.scrollTop)&&(this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(a):this._scrollable.setScrollPositionNow(a),i=!0)}let s=i;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_onDidScroll(e){this._shouldRender=this._horizontalScrollbar.onDidScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.onDidScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){const e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,i=e.scrollLeft>0,s=i?" left":"",r=t?" top":"",n=i||t?" top-left-corner":"";this._leftShadowDomNode.setClassName(`shadow${s}`),this._topShadowDomNode.setClassName(`shadow${r}`),this._topLeftShadowDomNode.setClassName(`shadow${n}${r}${s}`)}}_onDragStart(){this._isDragging=!0,this._reveal()}_onDragEnd(){this._isDragging=!1,this._hide()}_onMouseLeave(e){this._mouseIsOver=!1,this._hide()}_onMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){this._mouseIsOver||this._isDragging||(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){this._mouseIsOver||this._isDragging||this._hideTimeout.cancelAndSet((()=>this._hide()),500)}}t.AbstractScrollableElement=b,t.ScrollableElement=class extends b{constructor(e,t){(t=t||{}).mouseWheelSmoothScroll=!1;const i=new m.Scrollable({forceIntegerValues:!0,smoothScrollDuration:0,scheduleAtNextAnimationFrame:t=>a.scheduleAtNextAnimationFrame(a.getWindow(e),t)});super(e,t,i),this._register(i)}setScrollPosition(e){this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}},t.SmoothScrollableElement=class extends b{constructor(e,t,i){super(e,t,i)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}},t.DomScrollableElement=class extends b{constructor(e,t){(t=t||{}).mouseWheelSmoothScroll=!1;const i=new m.Scrollable({forceIntegerValues:!1,smoothScrollDuration:0,scheduleAtNextAnimationFrame:t=>a.scheduleAtNextAnimationFrame(a.getWindow(e),t)});super(e,t,i),this._register(i),this._element=e,this._register(this.onScroll((e=>{e.scrollTopChanged&&(this._element.scrollTop=e.scrollTop),e.scrollLeftChanged&&(this._element.scrollLeft=e.scrollLeft)}))),this.scanDomNode()}setScrollPosition(e){this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}scanDomNode(){this.setScrollDimensions({width:this._element.clientWidth,scrollWidth:this._element.scrollWidth,height:this._element.clientHeight,scrollHeight:this._element.scrollHeight}),this.setScrollPosition({scrollLeft:this._element.scrollLeft,scrollTop:this._element.scrollTop})}}},8974:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.ScrollbarArrow=t.ARROW_IMG_SIZE=void 0;const o=i(8328),a=i(8286),l=i(1758),h=n(i(7093));t.ARROW_IMG_SIZE=11;class c extends a.Widget{constructor(e){super(),this._onActivate=e.onActivate,this.bgDomNode=document.createElement("div"),this.bgDomNode.className="arrow-background",this.bgDomNode.style.position="absolute",this.bgDomNode.style.width=e.bgWidth+"px",this.bgDomNode.style.height=e.bgHeight+"px",void 0!==e.top&&(this.bgDomNode.style.top="0px"),void 0!==e.left&&(this.bgDomNode.style.left="0px"),void 0!==e.bottom&&(this.bgDomNode.style.bottom="0px"),void 0!==e.right&&(this.bgDomNode.style.right="0px"),this.domNode=document.createElement("div"),this.domNode.className=e.className,this.domNode.style.position="absolute",this.domNode.style.width=t.ARROW_IMG_SIZE+"px",this.domNode.style.height=t.ARROW_IMG_SIZE+"px",void 0!==e.top&&(this.domNode.style.top=e.top+"px"),void 0!==e.left&&(this.domNode.style.left=e.left+"px"),void 0!==e.bottom&&(this.domNode.style.bottom=e.bottom+"px"),void 0!==e.right&&(this.domNode.style.right=e.right+"px"),this._pointerMoveMonitor=this._register(new o.GlobalPointerMoveMonitor),this._register(h.addStandardDisposableListener(this.bgDomNode,h.EventType.POINTER_DOWN,(e=>this._arrowPointerDown(e)))),this._register(h.addStandardDisposableListener(this.domNode,h.EventType.POINTER_DOWN,(e=>this._arrowPointerDown(e)))),this._pointerdownRepeatTimer=this._register(new h.WindowIntervalTimer),this._pointerdownScheduleRepeatTimer=this._register(new l.TimeoutTimer)}_arrowPointerDown(e){e.target&&e.target instanceof Element&&(this._onActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet((()=>{this._pointerdownRepeatTimer.cancelAndSet((()=>this._onActivate()),1e3/24,h.getWindow(e))}),200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,(e=>{}),(()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()})),e.preventDefault())}}t.ScrollbarArrow=c},8245:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ScrollbarState=void 0;class i{constructor(e,t,i,s,r,n){this._scrollbarSize=Math.round(t),this._oppositeScrollbarSize=Math.round(i),this._arrowSize=Math.round(e),this._visibleSize=s,this._scrollSize=r,this._scrollPosition=n,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new i(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(e){const t=Math.round(e);return this._visibleSize!==t&&(this._visibleSize=t,this._refreshComputedValues(),!0)}setScrollSize(e){const t=Math.round(e);return this._scrollSize!==t&&(this._scrollSize=t,this._refreshComputedValues(),!0)}setScrollPosition(e){const t=Math.round(e);return this._scrollPosition!==t&&(this._scrollPosition=t,this._refreshComputedValues(),!0)}setScrollbarSize(e){this._scrollbarSize=Math.round(e)}setOppositeScrollbarSize(e){this._oppositeScrollbarSize=Math.round(e)}static _computeValues(e,t,i,s,r){const n=Math.max(0,i-e),o=Math.max(0,n-2*t),a=s>0&&s>i;if(!a)return{computedAvailableSize:Math.round(n),computedIsNeeded:a,computedSliderSize:Math.round(o),computedSliderRatio:0,computedSliderPosition:0};const l=Math.round(Math.max(20,Math.floor(i*o/s))),h=(o-l)/(s-i),c=r*h;return{computedAvailableSize:Math.round(n),computedIsNeeded:a,computedSliderSize:Math.round(l),computedSliderRatio:h,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){const e=i._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=e.computedAvailableSize,this._computedIsNeeded=e.computedIsNeeded,this._computedSliderSize=e.computedSliderSize,this._computedSliderRatio=e.computedSliderRatio,this._computedSliderPosition=e.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(e){if(!this._computedIsNeeded)return 0;const t=e-this._arrowSize-this._computedSliderSize/2;return Math.round(t/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(e){if(!this._computedIsNeeded)return 0;const t=e-this._arrowSize;let i=this._scrollPosition;return t{Object.defineProperty(t,"__esModule",{value:!0}),t.ScrollbarVisibilityController=void 0;const s=i(1758),r=i(7150),n=i(9881);class o extends r.Disposable{constructor(e,t,i){super(),this._visibility=e,this._visibleClassName=t,this._invisibleClassName=i,this._domNode=null,this._isVisible=!1,this._isNeeded=!1,this._rawShouldBeVisible=!1,this._shouldBeVisible=!1,this._revealTimer=this._register(new s.TimeoutTimer)}setVisibility(e){this._visibility!==e&&(this._visibility=e,this._updateShouldBeVisible())}setShouldBeVisible(e){this._rawShouldBeVisible=e,this._updateShouldBeVisible()}_applyVisibilitySetting(){return this._visibility!==n.ScrollbarVisibility.Hidden&&(this._visibility===n.ScrollbarVisibility.Visible||this._rawShouldBeVisible)}_updateShouldBeVisible(){const e=this._applyVisibilitySetting();this._shouldBeVisible!==e&&(this._shouldBeVisible=e,this.ensureVisibility())}setIsNeeded(e){this._isNeeded!==e&&(this._isNeeded=e,this.ensureVisibility())}setDomNode(e){this._domNode=e,this._domNode.setClassName(this._invisibleClassName),this.setShouldBeVisible(!1)}ensureVisibility(){this._isNeeded?this._shouldBeVisible?this._reveal():this._hide(!0):this._hide(!1)}_reveal(){this._isVisible||(this._isVisible=!0,this._revealTimer.setIfNotSet((()=>{this._domNode?.setClassName(this._visibleClassName)}),0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?" fade":"")))}}t.ScrollbarVisibilityController=o},5473:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.VerticalScrollbar=void 0;const s=i(8801),r=i(8245),n=i(9881);class o extends s.AbstractScrollbar{constructor(e,t,i){const s=e.getScrollDimensions(),o=e.getCurrentScrollPosition();if(super({lazyRender:t.lazyRender,host:i,scrollbarState:new r.ScrollbarState(t.verticalHasArrows?t.arrowSize:0,t.vertical===n.ScrollbarVisibility.Hidden?0:t.verticalScrollbarSize,0,s.height,s.scrollHeight,o.scrollTop),visibility:t.vertical,extraScrollbarClassName:"vertical",scrollable:e,scrollByPage:t.scrollByPage}),t.verticalHasArrows)throw new Error("horizontalHasArrows is not supported in xterm.js");this._createSlider(0,Math.floor((t.verticalScrollbarSize-t.verticalSliderSize)/2),t.verticalSliderSize,void 0)}_updateSlider(e,t){this.slider.setHeight(e),this.slider.setTop(t)}_renderDomNode(e,t){this.domNode.setWidth(t),this.domNode.setHeight(e),this.domNode.setRight(0),this.domNode.setTop(0)}onDidScroll(e){return this._shouldRender=this._onElementScrollSize(e.scrollHeight)||this._shouldRender,this._shouldRender=this._onElementScrollPosition(e.scrollTop)||this._shouldRender,this._shouldRender=this._onElementSize(e.height)||this._shouldRender,this._shouldRender}_pointerDownRelativePosition(e,t){return t}_sliderPointerPosition(e){return e.pageY}_sliderOrthogonalPointerPosition(e){return e.pageX}_updateScrollbarSize(e){this.slider.setWidth(e)}writeScrollPosition(e,t){e.scrollTop=t}updateOptions(e){this.updateScrollbarSize(e.vertical===n.ScrollbarVisibility.Hidden?0:e.verticalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(0),this._visibilityController.setVisibility(e.vertical),this._scrollByPage=e.scrollByPage}}t.VerticalScrollbar=o},8286:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.Widget=void 0;const o=n(i(7093)),a=i(5394),l=i(5964),h=i(8594),c=i(7150);class d extends c.Disposable{onclick(e,t){this._register(o.addDisposableListener(e,o.EventType.CLICK,(i=>t(new l.StandardMouseEvent(o.getWindow(e),i)))))}onmousedown(e,t){this._register(o.addDisposableListener(e,o.EventType.MOUSE_DOWN,(i=>t(new l.StandardMouseEvent(o.getWindow(e),i)))))}onmouseover(e,t){this._register(o.addDisposableListener(e,o.EventType.MOUSE_OVER,(i=>t(new l.StandardMouseEvent(o.getWindow(e),i)))))}onmouseleave(e,t){this._register(o.addDisposableListener(e,o.EventType.MOUSE_LEAVE,(i=>t(new l.StandardMouseEvent(o.getWindow(e),i)))))}onkeydown(e,t){this._register(o.addDisposableListener(e,o.EventType.KEY_DOWN,(e=>t(new a.StandardKeyboardEvent(e)))))}onkeyup(e,t){this._register(o.addDisposableListener(e,o.EventType.KEY_UP,(e=>t(new a.StandardKeyboardEvent(e)))))}oninput(e,t){this._register(o.addDisposableListener(e,o.EventType.INPUT,t))}onblur(e,t){this._register(o.addDisposableListener(e,o.EventType.BLUR,t))}onfocus(e,t){this._register(o.addDisposableListener(e,o.EventType.FOCUS,t))}onchange(e,t){this._register(o.addDisposableListener(e,o.EventType.CHANGE,t))}ignoreGesture(e){return h.Gesture.ignoreTarget(e)}}t.Widget=d},4693:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.mainWindow=void 0,t.ensureCodeWindow=function(e,t){},t.mainWindow="object"==typeof window?window:globalThis},3058:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Permutation=t.CallbackIterable=t.ArrayQueue=t.booleanComparator=t.numberComparator=t.CompareResult=void 0,t.tail=function(e,t=0){return e[e.length-(1+t)]},t.tail2=function(e){if(0===e.length)throw new Error("Invalid tail call");return[e.slice(0,e.length-1),e[e.length-1]]},t.equals=function(e,t,i=(e,t)=>e===t){if(e===t)return!0;if(!e||!t)return!1;if(e.length!==t.length)return!1;for(let s=0,r=e.length;si(e[s],t)))},t.binarySearch2=n,t.quickSelect=function e(t,i,s){if((t|=0)>=i.length)throw new TypeError("invalid index");const r=i[Math.floor(i.length*Math.random())],n=[],o=[],a=[];for(const e of i){const t=s(e,r);t<0?n.push(e):t>0?o.push(e):a.push(e)}return t{(async()=>{const o=e.length,l=e.slice(0,i).sort(t);for(let h=i,c=Math.min(i+r,o);hi&&await new Promise((e=>setTimeout(e))),n&&n.isCancellationRequested)throw new s.CancellationError;a(e,t,l,h,c)}return l})().then(o,l)}))},t.coalesce=function(e){return e.filter((e=>!!e))},t.coalesceInPlace=function(e){let t=0;for(let i=0;i0},t.distinct=function(e,t=e=>e){const i=new Set;return e.filter((e=>{const s=t(e);return!i.has(s)&&(i.add(s),!0)}))},t.uniqueFilter=function(e){const t=new Set;return i=>{const s=e(i);return!t.has(s)&&(t.add(s),!0)}},t.firstOrDefault=function(e,t){return e.length>0?e[0]:t},t.lastOrDefault=function(e,t){return e.length>0?e[e.length-1]:t},t.commonPrefixLength=function(e,t,i=(e,t)=>e===t){let s=0;for(let r=0,n=Math.min(e.length,t.length);rt;e--)s.push(e);return s},t.index=function(e,t,i){return e.reduce(((e,s)=>(e[t(s)]=i?i(s):s,e)),Object.create(null))},t.insert=function(e,t){return e.push(t),()=>l(e,t)},t.remove=l,t.arrayInsert=function(e,t,i){const s=e.slice(0,t),r=e.slice(t);return s.concat(i,r)},t.shuffle=function(e,t){let i;if("number"==typeof t){let e=t;i=()=>{const t=179426549*Math.sin(e++);return t-Math.floor(t)}}else i=Math.random;for(let t=e.length-1;t>0;t-=1){const s=Math.floor(i()*(t+1)),r=e[t];e[t]=e[s],e[s]=r}},t.pushToStart=function(e,t){const i=e.indexOf(t);i>-1&&(e.splice(i,1),e.unshift(t))},t.pushToEnd=function(e,t){const i=e.indexOf(t);i>-1&&(e.splice(i,1),e.push(t))},t.pushMany=function(e,t){for(const i of t)e.push(i)},t.mapArrayOrNot=function(e,t){return Array.isArray(e)?e.map(t):t(e)},t.asArray=function(e){return Array.isArray(e)?e:[e]},t.getRandomElement=function(e){return e[Math.floor(Math.random()*e.length)]},t.insertInto=h,t.splice=function(e,t,i,s){const r=c(e,t);let n=e.splice(r,i);return void 0===n&&(n=[]),h(e,r,s),n},t.compareBy=function(e,t){return(i,s)=>t(e(i),e(s))},t.tieBreakComparators=function(...e){return(t,i)=>{for(const s of e){const e=s(t,i);if(!d.isNeitherLessOrGreaterThan(e))return e}return d.neitherLessOrGreaterThan}},t.reverseOrder=function(e){return(t,i)=>-e(t,i)};const s=i(9807),r=i(8297);function n(e,t){let i=0,s=e-1;for(;i<=s;){const e=(i+s)/2|0,r=t(e);if(r<0)i=e+1;else{if(!(r>0))return e;s=e-1}}return-(i+1)}function o(e,t,i){const s=[];function r(e,t,i){if(0===t&&0===i.length)return;const r=s[s.length-1];r&&r.start+r.deleteCount===e?(r.deleteCount+=t,r.toInsert.push(...i)):s.push({start:e,deleteCount:t,toInsert:i})}let n=0,o=0;for(;;){if(n===e.length){r(n,0,t.slice(o));break}if(o===t.length){r(n,e.length-n,[]);break}const s=e[n],a=t[o],l=i(s,a);0===l?(n+=1,o+=1):l<0?(r(n,1,[]),n+=1):l>0&&(r(n,0,[a]),o+=1)}return s}function a(e,t,i,s,n){for(const o=i.length;st(n,e)<0));i.splice(e,0,n)}}}function l(e,t){const i=e.indexOf(t);if(i>-1)return e.splice(i,1),t}function h(e,t,i){const s=c(e,t),r=e.length,n=i.length;e.length=r+n;for(let t=r-1;t>=s;t--)e[t+n]=e[t];for(let t=0;t0},e.isNeitherLessOrGreaterThan=function(e){return 0===e},e.greaterThan=1,e.lessThan=-1,e.neitherLessOrGreaterThan=0}(d||(t.CompareResult=d={})),t.numberComparator=(e,t)=>e-t,t.booleanComparator=(e,i)=>(0,t.numberComparator)(e?1:0,i?1:0),t.ArrayQueue=class{constructor(e){this.items=e,this.firstIdx=0,this.lastIdx=this.items.length-1}get length(){return this.lastIdx-this.firstIdx+1}takeWhile(e){let t=this.firstIdx;for(;t=0&&e(this.items[t]);)t--;const i=t===this.lastIdx?null:this.items.slice(t+1,this.lastIdx+1);return this.lastIdx=t,i}peek(){if(0!==this.length)return this.items[this.firstIdx]}peekLast(){if(0!==this.length)return this.items[this.lastIdx]}dequeue(){const e=this.items[this.firstIdx];return this.firstIdx++,e}removeLast(){const e=this.items[this.lastIdx];return this.lastIdx--,e}takeCount(e){const t=this.items.slice(this.firstIdx,this.firstIdx+e);return this.firstIdx+=e,t}};class u{static{this.empty=new u((e=>{}))}constructor(e){this.iterate=e}forEach(e){this.iterate((t=>(e(t),!0)))}toArray(){const e=[];return this.iterate((t=>(e.push(t),!0))),e}filter(e){return new u((t=>this.iterate((i=>!e(i)||t(i)))))}map(e){return new u((t=>this.iterate((i=>t(e(i))))))}some(e){let t=!1;return this.iterate((i=>(t=e(i),!t))),t}findFirst(e){let t;return this.iterate((i=>!e(i)||(t=i,!1))),t}findLast(e){let t;return this.iterate((i=>(e(i)&&(t=i),!0))),t}findLastMaxBy(e){let t,i=!0;return this.iterate((s=>((i||d.isGreaterThan(e(s,t)))&&(i=!1,t=s),!0))),t}}t.CallbackIterable=u;class _{constructor(e){this._indexMap=e}static createSortPermutation(e,t){const i=Array.from(e.keys()).sort(((i,s)=>t(e[i],e[s])));return new _(i)}apply(e){return e.map(((t,i)=>e[this._indexMap[i]]))}inverse(){const e=this._indexMap.slice();for(let t=0;t{function i(e,t,i=e.length-1){for(let s=i;s>=0;s--)if(t(e[s]))return s;return-1}function s(e,t,i=0,s=e.length){let r=i,n=s;for(;r=0&&(i=r)}return i},t.findFirstMin=function(e,t){return o(e,((e,i)=>-t(e,i)))},t.findMaxIdx=function(e,t){if(0===e.length)return-1;let i=0;for(let s=1;s0&&(i=s);return i},t.mapFindFirst=function(e,t){for(const i of e){const e=t(i);if(void 0!==e)return e}};class n{static{this.assertInvariants=!1}constructor(e){this._array=e,this._findLastMonotonousLastIdx=0}findLastMonotonous(e){if(n.assertInvariants){if(this._prevFindLastPredicate)for(const t of this._array)if(this._prevFindLastPredicate(t)&&!e(t))throw new Error("MonotonousArray: current predicate must be weaker than (or equal to) the previous predicate.");this._prevFindLastPredicate=e}const t=s(this._array,e,this._findLastMonotonousLastIdx);return this._findLastMonotonousLastIdx=t+1,-1===t?void 0:this._array[t]}}function o(e,t){if(0===e.length)return;let i=e[0];for(let s=1;s0&&(i=r)}return i}t.MonotonousArray=n},1758:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.AsyncIterableSource=t.CancelableAsyncIterableObject=t.AsyncIterableObject=t.LazyStatefulPromise=t.StatefulPromise=t.Promises=t.DeferredPromise=t.IntervalCounter=t.TaskSequentializer=t.GlobalIdleValue=t.AbstractIdleValue=t._runWhenIdle=t.runWhenGlobalIdle=t.ThrottledWorker=t.RunOnceWorker=t.ProcessTimeRunOnceScheduler=t.RunOnceScheduler=t.IntervalTimer=t.TimeoutTimer=t.LimitedQueue=t.Queue=t.Limiter=t.AutoOpenBarrier=t.Barrier=t.ThrottledDelayer=t.Delayer=t.SequencerByKey=t.Sequencer=t.Throttler=void 0,t.isThenable=c,t.createCancelablePromise=d,t.raceCancellation=function(e,t,i){return new Promise(((s,r)=>{const n=t.onCancellationRequested((()=>{n.dispose(),s(i)}));e.then(s,r).finally((()=>n.dispose()))}))},t.raceCancellationError=function(e,t){return new Promise(((i,s)=>{const n=t.onCancellationRequested((()=>{n.dispose(),s(new r.CancellationError)}));e.then(i,s).finally((()=>n.dispose()))}))},t.raceCancellablePromises=async function(e){let t=-1;const i=e.map(((e,i)=>e.then((e=>(t=i,e)))));try{return await Promise.race(i)}finally{e.forEach(((e,i)=>{i!==t&&e.cancel()}))}},t.raceTimeout=function(e,t,i){let s;const r=setTimeout((()=>{s?.(void 0),i?.()}),t);return Promise.race([e.finally((()=>clearTimeout(r))),new Promise((e=>s=e))])},t.asPromise=function(e){return new Promise(((t,i)=>{const s=e();c(s)?s.then(t,i):t(s)}))},t.promiseWithResolvers=u,t.timeout=g,t.disposableTimeout=function(e,t=0,i){const s=setTimeout((()=>{e(),i&&r.dispose()}),t),r=(0,o.toDisposable)((()=>{clearTimeout(s),i?.deleteAndLeak(r)}));return i?.add(r),r},t.sequence=function(e){const t=[];let i=0;const s=e.length;return Promise.resolve(null).then((function r(n){null!=n&&t.push(n);const o=i!!e,i=null){let s=0;const r=e.length,n=()=>{if(s>=r)return Promise.resolve(i);const o=e[s++];return Promise.resolve(o()).then((e=>t(e)?Promise.resolve(e):n()))};return n()},t.firstParallel=function(e,t=e=>!!e,i=null){if(0===e.length)return Promise.resolve(i);let s=e.length;const r=()=>{s=-1;for(const t of e)t.cancel?.()};return new Promise(((n,o)=>{for(const a of e)a.then((e=>{--s>=0&&t(e)?(r(),n(e)):0===s&&n(i)})).catch((e=>{--s>=0&&(r(),o(e))}))}))},t.retry=async function(e,t,i){let s;for(let r=0;r{const s=t.token.onCancellationRequested((()=>{s.dispose(),t.dispose(),e.reject(new r.CancellationError)}));try{for await(const s of i){if(t.token.isCancellationRequested)return;e.emitOne(s)}s.dispose(),t.dispose()}catch(i){s.dispose(),t.dispose(),e.reject(i)}}))};const s=i(8447),r=i(9807),n=i(802),o=i(7150),a=i(8163),l=i(5015),h=i(626);function c(e){return!!e&&"function"==typeof e.then}function d(e){const t=new s.CancellationTokenSource,i=e(t.token),n=new Promise(((e,s)=>{const n=t.token.onCancellationRequested((()=>{n.dispose(),s(new r.CancellationError)}));Promise.resolve(i).then((i=>{n.dispose(),t.dispose(),e(i)}),(e=>{n.dispose(),t.dispose(),s(e)}))}));return new class{cancel(){t.cancel(),t.dispose()}then(e,t){return n.then(e,t)}catch(e){return this.then(void 0,e)}finally(e){return n.finally(e)}}}function u(){let e,t;return{promise:new Promise(((i,s)=>{e=i,t=s})),resolve:e,reject:t}}class _{constructor(){this.isDisposed=!1,this.activePromise=null,this.queuedPromise=null,this.queuedPromiseFactory=null}queue(e){if(this.isDisposed)return Promise.reject(new Error("Throttler is disposed"));if(this.activePromise){if(this.queuedPromiseFactory=e,!this.queuedPromise){const e=()=>{if(this.queuedPromise=null,this.isDisposed)return;const e=this.queue(this.queuedPromiseFactory);return this.queuedPromiseFactory=null,e};this.queuedPromise=new Promise((t=>{this.activePromise.then(e,e).then(t)}))}return new Promise(((e,t)=>{this.queuedPromise.then(e,t)}))}return this.activePromise=e(),new Promise(((e,t)=>{this.activePromise.then((t=>{this.activePromise=null,e(t)}),(e=>{this.activePromise=null,t(e)}))}))}dispose(){this.isDisposed=!0}}t.Throttler=_,t.Sequencer=class{constructor(){this.current=Promise.resolve(null)}queue(e){return this.current=this.current.then((()=>e()),(()=>e()))}},t.SequencerByKey=class{constructor(){this.promiseMap=new Map}queue(e,t){const i=(this.promiseMap.get(e)??Promise.resolve()).catch((()=>{})).then(t).finally((()=>{this.promiseMap.get(e)===i&&this.promiseMap.delete(e)}));return this.promiseMap.set(e,i),i}};class f{constructor(e){this.defaultDelay=e,this.deferred=null,this.completionPromise=null,this.doResolve=null,this.doReject=null,this.task=null}trigger(e,t=this.defaultDelay){this.task=e,this.cancelTimeout(),this.completionPromise||(this.completionPromise=new Promise(((e,t)=>{this.doResolve=e,this.doReject=t})).then((()=>{if(this.completionPromise=null,this.doResolve=null,this.task){const e=this.task;return this.task=null,e()}})));const i=()=>{this.deferred=null,this.doResolve?.(null)};return this.deferred=t===l.MicrotaskDelay?(e=>{let t=!0;return queueMicrotask((()=>{t&&(t=!1,e())})),{isTriggered:()=>t,dispose:()=>{t=!1}}})(i):((e,t)=>{let i=!0;const s=setTimeout((()=>{i=!1,t()}),e);return{isTriggered:()=>i,dispose:()=>{clearTimeout(s),i=!1}}})(t,i),this.completionPromise}isTriggered(){return!!this.deferred?.isTriggered()}cancel(){this.cancelTimeout(),this.completionPromise&&(this.doReject?.(new r.CancellationError),this.completionPromise=null)}cancelTimeout(){this.deferred?.dispose(),this.deferred=null}dispose(){this.cancel()}}t.Delayer=f,t.ThrottledDelayer=class{constructor(e){this.delayer=new f(e),this.throttler=new _}trigger(e,t){return this.delayer.trigger((()=>this.throttler.queue(e)),t)}isTriggered(){return this.delayer.isTriggered()}cancel(){this.delayer.cancel()}dispose(){this.delayer.dispose(),this.throttler.dispose()}};class p{constructor(){this._isOpen=!1,this._promise=new Promise(((e,t)=>{this._completePromise=e}))}isOpen(){return this._isOpen}open(){this._isOpen=!0,this._completePromise(!0)}wait(){return this._promise}}function g(e,t){return t?new Promise(((i,s)=>{const n=setTimeout((()=>{o.dispose(),i()}),e),o=t.onCancellationRequested((()=>{clearTimeout(n),o.dispose(),s(new r.CancellationError)}))})):d((t=>g(e,t)))}t.Barrier=p,t.AutoOpenBarrier=class extends p{constructor(e){super(),this._timeout=setTimeout((()=>this.open()),e)}open(){clearTimeout(this._timeout),super.open()}};class m{constructor(e){this._size=0,this._isDisposed=!1,this.maxDegreeOfParalellism=e,this.outstandingPromises=[],this.runningPromises=0,this._onDrained=new n.Emitter}whenIdle(){return this.size>0?n.Event.toPromise(this.onDrained):Promise.resolve()}get onDrained(){return this._onDrained.event}get size(){return this._size}queue(e){if(this._isDisposed)throw new Error("Object has been disposed");return this._size++,new Promise(((t,i)=>{this.outstandingPromises.push({factory:e,c:t,e:i}),this.consume()}))}consume(){for(;this.outstandingPromises.length&&this.runningPromisesthis.consumed()),(()=>this.consumed()))}}consumed(){this._isDisposed||(this.runningPromises--,0==--this._size&&this._onDrained.fire(),this.outstandingPromises.length>0&&this.consume())}clear(){if(this._isDisposed)throw new Error("Object has been disposed");this.outstandingPromises.length=0,this._size=this.runningPromises}dispose(){this._isDisposed=!0,this.outstandingPromises.length=0,this._size=0,this._onDrained.dispose()}}t.Limiter=m,t.Queue=class extends m{constructor(){super(1)}},t.LimitedQueue=class{constructor(){this.sequentializer=new C,this.tasks=0}queue(e){return this.sequentializer.isRunning()?this.sequentializer.queue((()=>this.sequentializer.run(this.tasks++,e()))):this.sequentializer.run(this.tasks++,e())}},t.TimeoutTimer=class{constructor(e,t){this._isDisposed=!1,this._token=-1,"function"==typeof e&&"number"==typeof t&&this.setIfNotSet(e,t)}dispose(){this.cancel(),this._isDisposed=!0}cancel(){-1!==this._token&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new r.BugIndicatingError("Calling 'cancelAndSet' on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout((()=>{this._token=-1,e()}),t)}setIfNotSet(e,t){if(this._isDisposed)throw new r.BugIndicatingError("Calling 'setIfNotSet' on a disposed TimeoutTimer");-1===this._token&&(this._token=setTimeout((()=>{this._token=-1,e()}),t))}},t.IntervalTimer=class{constructor(){this.disposable=void 0,this.isDisposed=!1}cancel(){this.disposable?.dispose(),this.disposable=void 0}cancelAndSet(e,t,i=globalThis){if(this.isDisposed)throw new r.BugIndicatingError("Calling 'cancelAndSet' on a disposed IntervalTimer");this.cancel();const s=i.setInterval((()=>{e()}),t);this.disposable=(0,o.toDisposable)((()=>{i.clearInterval(s),this.disposable=void 0}))}dispose(){this.cancel(),this.isDisposed=!0}};class v{constructor(e,t){this.timeoutToken=-1,this.runner=e,this.timeout=t,this.timeoutHandler=this.onTimeout.bind(this)}dispose(){this.cancel(),this.runner=null}cancel(){this.isScheduled()&&(clearTimeout(this.timeoutToken),this.timeoutToken=-1)}schedule(e=this.timeout){this.cancel(),this.timeoutToken=setTimeout(this.timeoutHandler,e)}get delay(){return this.timeout}set delay(e){this.timeout=e}isScheduled(){return-1!==this.timeoutToken}flush(){this.isScheduled()&&(this.cancel(),this.doRun())}onTimeout(){this.timeoutToken=-1,this.runner&&this.doRun()}doRun(){this.runner?.()}}t.RunOnceScheduler=v,t.ProcessTimeRunOnceScheduler=class{constructor(e,t){t%1e3!=0&&console.warn(`ProcessTimeRunOnceScheduler resolution is 1s, ${t}ms is not a multiple of 1000ms.`),this.runner=e,this.timeout=t,this.counter=0,this.intervalToken=-1,this.intervalHandler=this.onInterval.bind(this)}dispose(){this.cancel(),this.runner=null}cancel(){this.isScheduled()&&(clearInterval(this.intervalToken),this.intervalToken=-1)}schedule(e=this.timeout){e%1e3!=0&&console.warn(`ProcessTimeRunOnceScheduler resolution is 1s, ${e}ms is not a multiple of 1000ms.`),this.cancel(),this.counter=Math.ceil(e/1e3),this.intervalToken=setInterval(this.intervalHandler,1e3)}isScheduled(){return-1!==this.intervalToken}onInterval(){this.counter--,this.counter>0||(clearInterval(this.intervalToken),this.intervalToken=-1,this.runner?.())}},t.RunOnceWorker=class extends v{constructor(e,t){super(e,t),this.units=[]}work(e){this.units.push(e),this.isScheduled()||this.schedule()}doRun(){const e=this.units;this.units=[],this.runner?.(e)}dispose(){this.units=[],super.dispose()}};class S extends o.Disposable{constructor(e,t){super(),this.options=e,this.handler=t,this.pendingWork=[],this.throttler=this._register(new o.MutableDisposable),this.disposed=!1}get pending(){return this.pendingWork.length}work(e){if(this.disposed)return!1;if("number"==typeof this.options.maxBufferedWork)if(this.throttler.value){if(this.pending+e.length>this.options.maxBufferedWork)return!1}else if(this.pending+e.length-this.options.maxWorkChunkSize>this.options.maxBufferedWork)return!1;for(const t of e)this.pendingWork.push(t);return this.throttler.value||this.doWork(),!0}doWork(){this.handler(this.pendingWork.splice(0,this.options.maxWorkChunkSize)),this.pendingWork.length>0&&(this.throttler.value=new v((()=>{this.throttler.clear(),this.doWork()}),this.options.throttleDelay),this.throttler.value.schedule())}dispose(){super.dispose(),this.disposed=!0}}t.ThrottledWorker=S,"function"!=typeof globalThis.requestIdleCallback||"function"!=typeof globalThis.cancelIdleCallback?t._runWhenIdle=(e,t)=>{(0,a.setTimeout0)((()=>{if(i)return;const e=Date.now()+15,s={didTimeout:!0,timeRemaining:()=>Math.max(0,e-Date.now())};t(Object.freeze(s))}));let i=!1;return{dispose(){i||(i=!0)}}}:t._runWhenIdle=(e,t,i)=>{const s=e.requestIdleCallback(t,"number"==typeof i?{timeout:i}:void 0);let r=!1;return{dispose(){r||(r=!0,e.cancelIdleCallback(s))}}},t.runWhenGlobalIdle=e=>(0,t._runWhenIdle)(globalThis,e);class b{constructor(e,i){this._didRun=!1,this._executor=()=>{try{this._value=i()}catch(e){this._error=e}finally{this._didRun=!0}},this._handle=(0,t._runWhenIdle)(e,(()=>this._executor()))}dispose(){this._handle.dispose()}get value(){if(this._didRun||(this._handle.dispose(),this._executor()),this._error)throw this._error;return this._value}get isInitialized(){return this._didRun}}t.AbstractIdleValue=b,t.GlobalIdleValue=class extends b{constructor(e){super(globalThis,e)}};class C{isRunning(e){return"number"==typeof e?this._running?.taskId===e:!!this._running}get running(){return this._running?.promise}cancelRunning(){this._running?.cancel()}run(e,t,i){return this._running={taskId:e,cancel:()=>i?.(),promise:t},t.then((()=>this.doneRunning(e)),(()=>this.doneRunning(e))),t}doneRunning(e){this._running&&e===this._running.taskId&&(this._running=void 0,this.runQueued())}runQueued(){if(this._queued){const e=this._queued;this._queued=void 0,e.run().then(e.promiseResolve,e.promiseReject)}}queue(e){if(this._queued)this._queued.run=e;else{const{promise:t,resolve:i,reject:s}=u();this._queued={run:e,promise:t,promiseResolve:i,promiseReject:s}}return this._queued.promise}hasQueued(){return!!this._queued}async join(){return this._queued?.promise??this._running?.promise}}var y,w,E;t.TaskSequentializer=C,t.IntervalCounter=class{constructor(e,t=()=>Date.now()){this.interval=e,this.nowFn=t,this.lastIncrementTime=0,this.value=0}increment(){const e=this.nowFn();return e-this.lastIncrementTime>this.interval&&(this.lastIncrementTime=e,this.value=0),this.value++,this.value}},function(e){e[e.Resolved=0]="Resolved",e[e.Rejected=1]="Rejected"}(y||(y={}));class D{get isRejected(){return this.outcome?.outcome===y.Rejected}get isResolved(){return this.outcome?.outcome===y.Resolved}get isSettled(){return!!this.outcome}get value(){return this.outcome?.outcome===y.Resolved?this.outcome?.value:void 0}constructor(){this.p=new Promise(((e,t)=>{this.completeCallback=e,this.errorCallback=t}))}complete(e){return new Promise((t=>{this.completeCallback(e),this.outcome={outcome:y.Resolved,value:e},t()}))}error(e){return new Promise((t=>{this.errorCallback(e),this.outcome={outcome:y.Rejected,value:e},t()}))}cancel(){return this.error(new r.CancellationError)}}t.DeferredPromise=D,function(e){e.settled=async function(e){let t;const i=await Promise.all(e.map((e=>e.then((e=>e),(e=>{t||(t=e)})))));if(void 0!==t)throw t;return i},e.withAsyncBody=function(e){return new Promise((async(t,i)=>{try{await e(t,i)}catch(e){i(e)}}))}}(w||(t.Promises=w={}));class L{get value(){return this._value}get error(){return this._error}get isResolved(){return this._isResolved}constructor(e){this._value=void 0,this._error=void 0,this._isResolved=!1,this.promise=e.then((e=>(this._value=e,this._isResolved=!0,e)),(e=>{throw this._error=e,this._isResolved=!0,e}))}requireValue(){if(!this._isResolved)throw new r.BugIndicatingError("Promise is not resolved yet");if(this._error)throw this._error;return this._value}}t.StatefulPromise=L,t.LazyStatefulPromise=class{constructor(e){this._compute=e,this._promise=new h.Lazy((()=>new L(this._compute())))}requireValue(){return this._promise.value.requireValue()}getPromise(){return this._promise.value.promise}get currentValue(){return this._promise.rawValue?.value}},function(e){e[e.Initial=0]="Initial",e[e.DoneOK=1]="DoneOK",e[e.DoneError=2]="DoneError"}(E||(E={}));class R{static fromArray(e){return new R((t=>{t.emitMany(e)}))}static fromPromise(e){return new R((async t=>{t.emitMany(await e)}))}static fromPromises(e){return new R((async t=>{await Promise.all(e.map((async e=>t.emitOne(await e))))}))}static merge(e){return new R((async t=>{await Promise.all(e.map((async e=>{for await(const i of e)t.emitOne(i)})))}))}static{this.EMPTY=R.fromArray([])}constructor(e,t){this._state=E.Initial,this._results=[],this._error=null,this._onReturn=t,this._onStateChanged=new n.Emitter,queueMicrotask((async()=>{const t={emitOne:e=>this.emitOne(e),emitMany:e=>this.emitMany(e),reject:e=>this.reject(e)};try{await Promise.resolve(e(t)),this.resolve()}catch(e){this.reject(e)}finally{t.emitOne=void 0,t.emitMany=void 0,t.reject=void 0}}))}[Symbol.asyncIterator](){let e=0;return{next:async()=>{for(;;){if(this._state===E.DoneError)throw this._error;if(e(this._onReturn?.(),{done:!0,value:void 0})}}static map(e,t){return new R((async i=>{for await(const s of e)i.emitOne(t(s))}))}map(e){return R.map(this,e)}static filter(e,t){return new R((async i=>{for await(const s of e)t(s)&&i.emitOne(s)}))}filter(e){return R.filter(this,e)}static coalesce(e){return R.filter(e,(e=>!!e))}coalesce(){return R.coalesce(this)}static async toPromise(e){const t=[];for await(const i of e)t.push(i);return t}toPromise(){return R.toPromise(this)}emitOne(e){this._state===E.Initial&&(this._results.push(e),this._onStateChanged.fire())}emitMany(e){this._state===E.Initial&&(this._results=this._results.concat(e),this._onStateChanged.fire())}resolve(){this._state===E.Initial&&(this._state=E.DoneOK,this._onStateChanged.fire())}reject(e){this._state===E.Initial&&(this._state=E.DoneError,this._error=e,this._onStateChanged.fire())}}t.AsyncIterableObject=R;class A extends R{constructor(e,t){super(t),this._source=e}cancel(){this._source.cancel()}}t.CancelableAsyncIterableObject=A,t.AsyncIterableSource=class{constructor(e){let t,i;this._deferred=new D,this._asyncIterable=new R((e=>{if(!t)return i&&e.emitMany(i),this._errorFn=t=>e.reject(t),this._emitFn=t=>e.emitOne(t),this._deferred.p;e.reject(t)}),e),this._emitFn=e=>{i||(i=[]),i.push(e)},this._errorFn=e=>{t||(t=e)}}get asyncIterable(){return this._asyncIterable}resolve(){this._deferred.complete()}reject(e){this._errorFn(e),this._deferred.complete()}emitOne(e){this._emitFn(e)}}},8447:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CancellationTokenSource=t.CancellationToken=void 0,t.cancelOnDispose=function(e){const t=new a;return e.add({dispose(){t.cancel()}}),t.token};const s=i(802),r=Object.freeze((function(e,t){const i=setTimeout(e.bind(t),0);return{dispose(){clearTimeout(i)}}}));var n;!function(e){e.isCancellationToken=function(t){return t===e.None||t===e.Cancelled||t instanceof o||!(!t||"object"!=typeof t)&&"boolean"==typeof t.isCancellationRequested&&"function"==typeof t.onCancellationRequested},e.None=Object.freeze({isCancellationRequested:!1,onCancellationRequested:s.Event.None}),e.Cancelled=Object.freeze({isCancellationRequested:!0,onCancellationRequested:r})}(n||(t.CancellationToken=n={}));class o{constructor(){this._isCancelled=!1,this._emitter=null}cancel(){this._isCancelled||(this._isCancelled=!0,this._emitter&&(this._emitter.fire(void 0),this.dispose()))}get isCancellationRequested(){return this._isCancelled}get onCancellationRequested(){return this._isCancelled?r:(this._emitter||(this._emitter=new s.Emitter),this._emitter.event)}dispose(){this._emitter&&(this._emitter.dispose(),this._emitter=null)}}class a{constructor(e){this._token=void 0,this._parentListener=void 0,this._parentListener=e&&e.onCancellationRequested(this.cancel,this)}get token(){return this._token||(this._token=new o),this._token}cancel(){this._token?this._token instanceof o&&this._token.cancel():this._token=n.Cancelled}dispose(e=!1){e&&this.cancel(),this._parentListener?.dispose(),this._token?this._token instanceof o&&this._token.dispose():this._token=n.None}}t.CancellationTokenSource=a},4869:(e,t)=>{var i;Object.defineProperty(t,"__esModule",{value:!0}),t.CharCode=void 0,function(e){e[e.Null=0]="Null",e[e.Backspace=8]="Backspace",e[e.Tab=9]="Tab",e[e.LineFeed=10]="LineFeed",e[e.CarriageReturn=13]="CarriageReturn",e[e.Space=32]="Space",e[e.ExclamationMark=33]="ExclamationMark",e[e.DoubleQuote=34]="DoubleQuote",e[e.Hash=35]="Hash",e[e.DollarSign=36]="DollarSign",e[e.PercentSign=37]="PercentSign",e[e.Ampersand=38]="Ampersand",e[e.SingleQuote=39]="SingleQuote",e[e.OpenParen=40]="OpenParen",e[e.CloseParen=41]="CloseParen",e[e.Asterisk=42]="Asterisk",e[e.Plus=43]="Plus",e[e.Comma=44]="Comma",e[e.Dash=45]="Dash",e[e.Period=46]="Period",e[e.Slash=47]="Slash",e[e.Digit0=48]="Digit0",e[e.Digit1=49]="Digit1",e[e.Digit2=50]="Digit2",e[e.Digit3=51]="Digit3",e[e.Digit4=52]="Digit4",e[e.Digit5=53]="Digit5",e[e.Digit6=54]="Digit6",e[e.Digit7=55]="Digit7",e[e.Digit8=56]="Digit8",e[e.Digit9=57]="Digit9",e[e.Colon=58]="Colon",e[e.Semicolon=59]="Semicolon",e[e.LessThan=60]="LessThan",e[e.Equals=61]="Equals",e[e.GreaterThan=62]="GreaterThan",e[e.QuestionMark=63]="QuestionMark",e[e.AtSign=64]="AtSign",e[e.A=65]="A",e[e.B=66]="B",e[e.C=67]="C",e[e.D=68]="D",e[e.E=69]="E",e[e.F=70]="F",e[e.G=71]="G",e[e.H=72]="H",e[e.I=73]="I",e[e.J=74]="J",e[e.K=75]="K",e[e.L=76]="L",e[e.M=77]="M",e[e.N=78]="N",e[e.O=79]="O",e[e.P=80]="P",e[e.Q=81]="Q",e[e.R=82]="R",e[e.S=83]="S",e[e.T=84]="T",e[e.U=85]="U",e[e.V=86]="V",e[e.W=87]="W",e[e.X=88]="X",e[e.Y=89]="Y",e[e.Z=90]="Z",e[e.OpenSquareBracket=91]="OpenSquareBracket",e[e.Backslash=92]="Backslash",e[e.CloseSquareBracket=93]="CloseSquareBracket",e[e.Caret=94]="Caret",e[e.Underline=95]="Underline",e[e.BackTick=96]="BackTick",e[e.a=97]="a",e[e.b=98]="b",e[e.c=99]="c",e[e.d=100]="d",e[e.e=101]="e",e[e.f=102]="f",e[e.g=103]="g",e[e.h=104]="h",e[e.i=105]="i",e[e.j=106]="j",e[e.k=107]="k",e[e.l=108]="l",e[e.m=109]="m",e[e.n=110]="n",e[e.o=111]="o",e[e.p=112]="p",e[e.q=113]="q",e[e.r=114]="r",e[e.s=115]="s",e[e.t=116]="t",e[e.u=117]="u",e[e.v=118]="v",e[e.w=119]="w",e[e.x=120]="x",e[e.y=121]="y",e[e.z=122]="z",e[e.OpenCurlyBrace=123]="OpenCurlyBrace",e[e.Pipe=124]="Pipe",e[e.CloseCurlyBrace=125]="CloseCurlyBrace",e[e.Tilde=126]="Tilde",e[e.NoBreakSpace=160]="NoBreakSpace",e[e.U_Combining_Grave_Accent=768]="U_Combining_Grave_Accent",e[e.U_Combining_Acute_Accent=769]="U_Combining_Acute_Accent",e[e.U_Combining_Circumflex_Accent=770]="U_Combining_Circumflex_Accent",e[e.U_Combining_Tilde=771]="U_Combining_Tilde",e[e.U_Combining_Macron=772]="U_Combining_Macron",e[e.U_Combining_Overline=773]="U_Combining_Overline",e[e.U_Combining_Breve=774]="U_Combining_Breve",e[e.U_Combining_Dot_Above=775]="U_Combining_Dot_Above",e[e.U_Combining_Diaeresis=776]="U_Combining_Diaeresis",e[e.U_Combining_Hook_Above=777]="U_Combining_Hook_Above",e[e.U_Combining_Ring_Above=778]="U_Combining_Ring_Above",e[e.U_Combining_Double_Acute_Accent=779]="U_Combining_Double_Acute_Accent",e[e.U_Combining_Caron=780]="U_Combining_Caron",e[e.U_Combining_Vertical_Line_Above=781]="U_Combining_Vertical_Line_Above",e[e.U_Combining_Double_Vertical_Line_Above=782]="U_Combining_Double_Vertical_Line_Above",e[e.U_Combining_Double_Grave_Accent=783]="U_Combining_Double_Grave_Accent",e[e.U_Combining_Candrabindu=784]="U_Combining_Candrabindu",e[e.U_Combining_Inverted_Breve=785]="U_Combining_Inverted_Breve",e[e.U_Combining_Turned_Comma_Above=786]="U_Combining_Turned_Comma_Above",e[e.U_Combining_Comma_Above=787]="U_Combining_Comma_Above",e[e.U_Combining_Reversed_Comma_Above=788]="U_Combining_Reversed_Comma_Above",e[e.U_Combining_Comma_Above_Right=789]="U_Combining_Comma_Above_Right",e[e.U_Combining_Grave_Accent_Below=790]="U_Combining_Grave_Accent_Below",e[e.U_Combining_Acute_Accent_Below=791]="U_Combining_Acute_Accent_Below",e[e.U_Combining_Left_Tack_Below=792]="U_Combining_Left_Tack_Below",e[e.U_Combining_Right_Tack_Below=793]="U_Combining_Right_Tack_Below",e[e.U_Combining_Left_Angle_Above=794]="U_Combining_Left_Angle_Above",e[e.U_Combining_Horn=795]="U_Combining_Horn",e[e.U_Combining_Left_Half_Ring_Below=796]="U_Combining_Left_Half_Ring_Below",e[e.U_Combining_Up_Tack_Below=797]="U_Combining_Up_Tack_Below",e[e.U_Combining_Down_Tack_Below=798]="U_Combining_Down_Tack_Below",e[e.U_Combining_Plus_Sign_Below=799]="U_Combining_Plus_Sign_Below",e[e.U_Combining_Minus_Sign_Below=800]="U_Combining_Minus_Sign_Below",e[e.U_Combining_Palatalized_Hook_Below=801]="U_Combining_Palatalized_Hook_Below",e[e.U_Combining_Retroflex_Hook_Below=802]="U_Combining_Retroflex_Hook_Below",e[e.U_Combining_Dot_Below=803]="U_Combining_Dot_Below",e[e.U_Combining_Diaeresis_Below=804]="U_Combining_Diaeresis_Below",e[e.U_Combining_Ring_Below=805]="U_Combining_Ring_Below",e[e.U_Combining_Comma_Below=806]="U_Combining_Comma_Below",e[e.U_Combining_Cedilla=807]="U_Combining_Cedilla",e[e.U_Combining_Ogonek=808]="U_Combining_Ogonek",e[e.U_Combining_Vertical_Line_Below=809]="U_Combining_Vertical_Line_Below",e[e.U_Combining_Bridge_Below=810]="U_Combining_Bridge_Below",e[e.U_Combining_Inverted_Double_Arch_Below=811]="U_Combining_Inverted_Double_Arch_Below",e[e.U_Combining_Caron_Below=812]="U_Combining_Caron_Below",e[e.U_Combining_Circumflex_Accent_Below=813]="U_Combining_Circumflex_Accent_Below",e[e.U_Combining_Breve_Below=814]="U_Combining_Breve_Below",e[e.U_Combining_Inverted_Breve_Below=815]="U_Combining_Inverted_Breve_Below",e[e.U_Combining_Tilde_Below=816]="U_Combining_Tilde_Below",e[e.U_Combining_Macron_Below=817]="U_Combining_Macron_Below",e[e.U_Combining_Low_Line=818]="U_Combining_Low_Line",e[e.U_Combining_Double_Low_Line=819]="U_Combining_Double_Low_Line",e[e.U_Combining_Tilde_Overlay=820]="U_Combining_Tilde_Overlay",e[e.U_Combining_Short_Stroke_Overlay=821]="U_Combining_Short_Stroke_Overlay",e[e.U_Combining_Long_Stroke_Overlay=822]="U_Combining_Long_Stroke_Overlay",e[e.U_Combining_Short_Solidus_Overlay=823]="U_Combining_Short_Solidus_Overlay",e[e.U_Combining_Long_Solidus_Overlay=824]="U_Combining_Long_Solidus_Overlay",e[e.U_Combining_Right_Half_Ring_Below=825]="U_Combining_Right_Half_Ring_Below",e[e.U_Combining_Inverted_Bridge_Below=826]="U_Combining_Inverted_Bridge_Below",e[e.U_Combining_Square_Below=827]="U_Combining_Square_Below",e[e.U_Combining_Seagull_Below=828]="U_Combining_Seagull_Below",e[e.U_Combining_X_Above=829]="U_Combining_X_Above",e[e.U_Combining_Vertical_Tilde=830]="U_Combining_Vertical_Tilde",e[e.U_Combining_Double_Overline=831]="U_Combining_Double_Overline",e[e.U_Combining_Grave_Tone_Mark=832]="U_Combining_Grave_Tone_Mark",e[e.U_Combining_Acute_Tone_Mark=833]="U_Combining_Acute_Tone_Mark",e[e.U_Combining_Greek_Perispomeni=834]="U_Combining_Greek_Perispomeni",e[e.U_Combining_Greek_Koronis=835]="U_Combining_Greek_Koronis",e[e.U_Combining_Greek_Dialytika_Tonos=836]="U_Combining_Greek_Dialytika_Tonos",e[e.U_Combining_Greek_Ypogegrammeni=837]="U_Combining_Greek_Ypogegrammeni",e[e.U_Combining_Bridge_Above=838]="U_Combining_Bridge_Above",e[e.U_Combining_Equals_Sign_Below=839]="U_Combining_Equals_Sign_Below",e[e.U_Combining_Double_Vertical_Line_Below=840]="U_Combining_Double_Vertical_Line_Below",e[e.U_Combining_Left_Angle_Below=841]="U_Combining_Left_Angle_Below",e[e.U_Combining_Not_Tilde_Above=842]="U_Combining_Not_Tilde_Above",e[e.U_Combining_Homothetic_Above=843]="U_Combining_Homothetic_Above",e[e.U_Combining_Almost_Equal_To_Above=844]="U_Combining_Almost_Equal_To_Above",e[e.U_Combining_Left_Right_Arrow_Below=845]="U_Combining_Left_Right_Arrow_Below",e[e.U_Combining_Upwards_Arrow_Below=846]="U_Combining_Upwards_Arrow_Below",e[e.U_Combining_Grapheme_Joiner=847]="U_Combining_Grapheme_Joiner",e[e.U_Combining_Right_Arrowhead_Above=848]="U_Combining_Right_Arrowhead_Above",e[e.U_Combining_Left_Half_Ring_Above=849]="U_Combining_Left_Half_Ring_Above",e[e.U_Combining_Fermata=850]="U_Combining_Fermata",e[e.U_Combining_X_Below=851]="U_Combining_X_Below",e[e.U_Combining_Left_Arrowhead_Below=852]="U_Combining_Left_Arrowhead_Below",e[e.U_Combining_Right_Arrowhead_Below=853]="U_Combining_Right_Arrowhead_Below",e[e.U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below=854]="U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below",e[e.U_Combining_Right_Half_Ring_Above=855]="U_Combining_Right_Half_Ring_Above",e[e.U_Combining_Dot_Above_Right=856]="U_Combining_Dot_Above_Right",e[e.U_Combining_Asterisk_Below=857]="U_Combining_Asterisk_Below",e[e.U_Combining_Double_Ring_Below=858]="U_Combining_Double_Ring_Below",e[e.U_Combining_Zigzag_Above=859]="U_Combining_Zigzag_Above",e[e.U_Combining_Double_Breve_Below=860]="U_Combining_Double_Breve_Below",e[e.U_Combining_Double_Breve=861]="U_Combining_Double_Breve",e[e.U_Combining_Double_Macron=862]="U_Combining_Double_Macron",e[e.U_Combining_Double_Macron_Below=863]="U_Combining_Double_Macron_Below",e[e.U_Combining_Double_Tilde=864]="U_Combining_Double_Tilde",e[e.U_Combining_Double_Inverted_Breve=865]="U_Combining_Double_Inverted_Breve",e[e.U_Combining_Double_Rightwards_Arrow_Below=866]="U_Combining_Double_Rightwards_Arrow_Below",e[e.U_Combining_Latin_Small_Letter_A=867]="U_Combining_Latin_Small_Letter_A",e[e.U_Combining_Latin_Small_Letter_E=868]="U_Combining_Latin_Small_Letter_E",e[e.U_Combining_Latin_Small_Letter_I=869]="U_Combining_Latin_Small_Letter_I",e[e.U_Combining_Latin_Small_Letter_O=870]="U_Combining_Latin_Small_Letter_O",e[e.U_Combining_Latin_Small_Letter_U=871]="U_Combining_Latin_Small_Letter_U",e[e.U_Combining_Latin_Small_Letter_C=872]="U_Combining_Latin_Small_Letter_C",e[e.U_Combining_Latin_Small_Letter_D=873]="U_Combining_Latin_Small_Letter_D",e[e.U_Combining_Latin_Small_Letter_H=874]="U_Combining_Latin_Small_Letter_H",e[e.U_Combining_Latin_Small_Letter_M=875]="U_Combining_Latin_Small_Letter_M",e[e.U_Combining_Latin_Small_Letter_R=876]="U_Combining_Latin_Small_Letter_R",e[e.U_Combining_Latin_Small_Letter_T=877]="U_Combining_Latin_Small_Letter_T",e[e.U_Combining_Latin_Small_Letter_V=878]="U_Combining_Latin_Small_Letter_V",e[e.U_Combining_Latin_Small_Letter_X=879]="U_Combining_Latin_Small_Letter_X",e[e.LINE_SEPARATOR=8232]="LINE_SEPARATOR",e[e.PARAGRAPH_SEPARATOR=8233]="PARAGRAPH_SEPARATOR",e[e.NEXT_LINE=133]="NEXT_LINE",e[e.U_CIRCUMFLEX=94]="U_CIRCUMFLEX",e[e.U_GRAVE_ACCENT=96]="U_GRAVE_ACCENT",e[e.U_DIAERESIS=168]="U_DIAERESIS",e[e.U_MACRON=175]="U_MACRON",e[e.U_ACUTE_ACCENT=180]="U_ACUTE_ACCENT",e[e.U_CEDILLA=184]="U_CEDILLA",e[e.U_MODIFIER_LETTER_LEFT_ARROWHEAD=706]="U_MODIFIER_LETTER_LEFT_ARROWHEAD",e[e.U_MODIFIER_LETTER_RIGHT_ARROWHEAD=707]="U_MODIFIER_LETTER_RIGHT_ARROWHEAD",e[e.U_MODIFIER_LETTER_UP_ARROWHEAD=708]="U_MODIFIER_LETTER_UP_ARROWHEAD",e[e.U_MODIFIER_LETTER_DOWN_ARROWHEAD=709]="U_MODIFIER_LETTER_DOWN_ARROWHEAD",e[e.U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING=722]="U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING",e[e.U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING=723]="U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING",e[e.U_MODIFIER_LETTER_UP_TACK=724]="U_MODIFIER_LETTER_UP_TACK",e[e.U_MODIFIER_LETTER_DOWN_TACK=725]="U_MODIFIER_LETTER_DOWN_TACK",e[e.U_MODIFIER_LETTER_PLUS_SIGN=726]="U_MODIFIER_LETTER_PLUS_SIGN",e[e.U_MODIFIER_LETTER_MINUS_SIGN=727]="U_MODIFIER_LETTER_MINUS_SIGN",e[e.U_BREVE=728]="U_BREVE",e[e.U_DOT_ABOVE=729]="U_DOT_ABOVE",e[e.U_RING_ABOVE=730]="U_RING_ABOVE",e[e.U_OGONEK=731]="U_OGONEK",e[e.U_SMALL_TILDE=732]="U_SMALL_TILDE",e[e.U_DOUBLE_ACUTE_ACCENT=733]="U_DOUBLE_ACUTE_ACCENT",e[e.U_MODIFIER_LETTER_RHOTIC_HOOK=734]="U_MODIFIER_LETTER_RHOTIC_HOOK",e[e.U_MODIFIER_LETTER_CROSS_ACCENT=735]="U_MODIFIER_LETTER_CROSS_ACCENT",e[e.U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR=741]="U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR",e[e.U_MODIFIER_LETTER_HIGH_TONE_BAR=742]="U_MODIFIER_LETTER_HIGH_TONE_BAR",e[e.U_MODIFIER_LETTER_MID_TONE_BAR=743]="U_MODIFIER_LETTER_MID_TONE_BAR",e[e.U_MODIFIER_LETTER_LOW_TONE_BAR=744]="U_MODIFIER_LETTER_LOW_TONE_BAR",e[e.U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR=745]="U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR",e[e.U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK=746]="U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK",e[e.U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK=747]="U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK",e[e.U_MODIFIER_LETTER_UNASPIRATED=749]="U_MODIFIER_LETTER_UNASPIRATED",e[e.U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD=751]="U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD",e[e.U_MODIFIER_LETTER_LOW_UP_ARROWHEAD=752]="U_MODIFIER_LETTER_LOW_UP_ARROWHEAD",e[e.U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD=753]="U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD",e[e.U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD=754]="U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD",e[e.U_MODIFIER_LETTER_LOW_RING=755]="U_MODIFIER_LETTER_LOW_RING",e[e.U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT=756]="U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT",e[e.U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT=757]="U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT",e[e.U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT=758]="U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT",e[e.U_MODIFIER_LETTER_LOW_TILDE=759]="U_MODIFIER_LETTER_LOW_TILDE",e[e.U_MODIFIER_LETTER_RAISED_COLON=760]="U_MODIFIER_LETTER_RAISED_COLON",e[e.U_MODIFIER_LETTER_BEGIN_HIGH_TONE=761]="U_MODIFIER_LETTER_BEGIN_HIGH_TONE",e[e.U_MODIFIER_LETTER_END_HIGH_TONE=762]="U_MODIFIER_LETTER_END_HIGH_TONE",e[e.U_MODIFIER_LETTER_BEGIN_LOW_TONE=763]="U_MODIFIER_LETTER_BEGIN_LOW_TONE",e[e.U_MODIFIER_LETTER_END_LOW_TONE=764]="U_MODIFIER_LETTER_END_LOW_TONE",e[e.U_MODIFIER_LETTER_SHELF=765]="U_MODIFIER_LETTER_SHELF",e[e.U_MODIFIER_LETTER_OPEN_SHELF=766]="U_MODIFIER_LETTER_OPEN_SHELF",e[e.U_MODIFIER_LETTER_LOW_LEFT_ARROW=767]="U_MODIFIER_LETTER_LOW_LEFT_ARROW",e[e.U_GREEK_LOWER_NUMERAL_SIGN=885]="U_GREEK_LOWER_NUMERAL_SIGN",e[e.U_GREEK_TONOS=900]="U_GREEK_TONOS",e[e.U_GREEK_DIALYTIKA_TONOS=901]="U_GREEK_DIALYTIKA_TONOS",e[e.U_GREEK_KORONIS=8125]="U_GREEK_KORONIS",e[e.U_GREEK_PSILI=8127]="U_GREEK_PSILI",e[e.U_GREEK_PERISPOMENI=8128]="U_GREEK_PERISPOMENI",e[e.U_GREEK_DIALYTIKA_AND_PERISPOMENI=8129]="U_GREEK_DIALYTIKA_AND_PERISPOMENI",e[e.U_GREEK_PSILI_AND_VARIA=8141]="U_GREEK_PSILI_AND_VARIA",e[e.U_GREEK_PSILI_AND_OXIA=8142]="U_GREEK_PSILI_AND_OXIA",e[e.U_GREEK_PSILI_AND_PERISPOMENI=8143]="U_GREEK_PSILI_AND_PERISPOMENI",e[e.U_GREEK_DASIA_AND_VARIA=8157]="U_GREEK_DASIA_AND_VARIA",e[e.U_GREEK_DASIA_AND_OXIA=8158]="U_GREEK_DASIA_AND_OXIA",e[e.U_GREEK_DASIA_AND_PERISPOMENI=8159]="U_GREEK_DASIA_AND_PERISPOMENI",e[e.U_GREEK_DIALYTIKA_AND_VARIA=8173]="U_GREEK_DIALYTIKA_AND_VARIA",e[e.U_GREEK_DIALYTIKA_AND_OXIA=8174]="U_GREEK_DIALYTIKA_AND_OXIA",e[e.U_GREEK_VARIA=8175]="U_GREEK_VARIA",e[e.U_GREEK_OXIA=8189]="U_GREEK_OXIA",e[e.U_GREEK_DASIA=8190]="U_GREEK_DASIA",e[e.U_IDEOGRAPHIC_FULL_STOP=12290]="U_IDEOGRAPHIC_FULL_STOP",e[e.U_LEFT_CORNER_BRACKET=12300]="U_LEFT_CORNER_BRACKET",e[e.U_RIGHT_CORNER_BRACKET=12301]="U_RIGHT_CORNER_BRACKET",e[e.U_LEFT_BLACK_LENTICULAR_BRACKET=12304]="U_LEFT_BLACK_LENTICULAR_BRACKET",e[e.U_RIGHT_BLACK_LENTICULAR_BRACKET=12305]="U_RIGHT_BLACK_LENTICULAR_BRACKET",e[e.U_OVERLINE=8254]="U_OVERLINE",e[e.UTF8_BOM=65279]="UTF8_BOM",e[e.U_FULLWIDTH_SEMICOLON=65307]="U_FULLWIDTH_SEMICOLON",e[e.U_FULLWIDTH_COMMA=65292]="U_FULLWIDTH_COMMA"}(i||(t.CharCode=i={}))},9087:(e,t)=>{var i;Object.defineProperty(t,"__esModule",{value:!0}),t.SetWithKey=void 0,t.groupBy=function(e,t){const i=Object.create(null);for(const s of e){const e=t(s);let r=i[e];r||(r=i[e]=[]),r.push(s)}return i},t.diffSets=function(e,t){const i=[],s=[];for(const s of e)t.has(s)||i.push(s);for(const i of t)e.has(i)||s.push(i);return{removed:i,added:s}},t.diffMaps=function(e,t){const i=[],s=[];for(const[s,r]of e)t.has(s)||i.push(r);for(const[i,r]of t)e.has(i)||s.push(r);return{removed:i,added:s}},t.intersection=function(e,t){const i=new Set;for(const s of t)e.has(s)&&i.add(s);return i};class s{static{i=Symbol.toStringTag}constructor(e,t){this.toKey=t,this._map=new Map,this[i]="SetWithKey";for(const t of e)this.add(t)}get size(){return this._map.size}add(e){const t=this.toKey(e);return this._map.set(t,e),this}delete(e){return this._map.delete(this.toKey(e))}has(e){return this._map.has(this.toKey(e))}*entries(){for(const e of this._map.values())yield[e,e]}keys(){return this.values()}*values(){for(const e of this._map.values())yield e}clear(){this._map.clear()}forEach(e,t){this._map.forEach((i=>e.call(t,i,i,this)))}[Symbol.iterator](){return this.values()}}t.SetWithKey=s},4838:(e,t)=>{function i(e){return(t,i,s)=>{let r=null,n=null;if("function"==typeof s.value?(r="value",n=s.value):"function"==typeof s.get&&(r="get",n=s.get),!n)throw new Error("not supported");s[r]=e(n,i)}}Object.defineProperty(t,"__esModule",{value:!0}),t.memoize=function(e,t,i){let s=null,r=null;if("function"==typeof i.value?(s="value",r=i.value,0!==r.length&&console.warn("Memoize should only be used in functions with zero parameters")):"function"==typeof i.get&&(s="get",r=i.get),!r)throw new Error("not supported");const n=`$memoize$${t}`;i[s]=function(...e){return this.hasOwnProperty(n)||Object.defineProperty(this,n,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,e)}),this[n]}},t.debounce=function(e,t,s){return i(((i,r)=>{const n=`$debounce$${r}`,o=`$debounce$result$${r}`;return function(...r){this[o]||(this[o]=s?s():void 0),clearTimeout(this[n]),t&&(this[o]=t(this[o],...r),r=[this[o]]),this[n]=setTimeout((()=>{i.apply(this,r),this[o]=s?s():void 0}),e)}}))},t.throttle=function(e,t,s){return i(((i,r)=>{const n=`$throttle$timer$${r}`,o=`$throttle$result$${r}`,a=`$throttle$lastRun$${r}`,l=`$throttle$pending$${r}`;return function(...r){if(this[o]||(this[o]=s?s():void 0),null!==this[a]&&void 0!==this[a]||(this[a]=-Number.MAX_VALUE),t&&(this[o]=t(this[o],...r)),this[l])return;const h=this[a]+e;h<=Date.now()?(this[a]=Date.now(),i.apply(this,[this[o]]),this[o]=s?s():void 0):(this[l]=!0,this[n]=setTimeout((()=>{this[l]=!1,this[a]=Date.now(),i.apply(this,[this[o]]),this[o]=s?s():void 0}),h-Date.now()))}}))}},9807:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BugIndicatingError=t.ErrorNoTelemetry=t.ExpectedError=t.NotSupportedError=t.NotImplementedError=t.ReadonlyError=t.CancellationError=t.errorHandler=t.ErrorHandler=void 0,t.setUnexpectedErrorHandler=function(e){t.errorHandler.setUnexpectedErrorHandler(e)},t.isSigPipeError=function(e){if(!e||"object"!=typeof e)return!1;const t=e;return"EPIPE"===t.code&&"WRITE"===t.syscall?.toUpperCase()},t.onUnexpectedError=function(e){r(e)||t.errorHandler.onUnexpectedError(e)},t.onUnexpectedExternalError=function(e){r(e)||t.errorHandler.onUnexpectedExternalError(e)},t.transformErrorForSerialization=function(e){if(e instanceof Error){const{name:t,message:i}=e;return{$isError:!0,name:t,message:i,stack:e.stacktrace||e.stack,noTelemetry:c.isErrorNoTelemetry(e)}}return e},t.transformErrorFromSerialization=function(e){let t;return e.noTelemetry?t=new c:(t=new Error,t.name=e.name),t.message=e.message,t.stack=e.stack,t},t.isCancellationError=r,t.canceled=function(){const e=new Error(s);return e.name=e.message,e},t.illegalArgument=function(e){return e?new Error(`Illegal argument: ${e}`):new Error("Illegal argument")},t.illegalState=function(e){return e?new Error(`Illegal state: ${e}`):new Error("Illegal state")},t.getErrorMessage=function(e){return e?e.message?e.message:e.stack?e.stack.split("\n")[0]:String(e):"Error"};class i{constructor(){this.listeners=[],this.unexpectedErrorHandler=function(e){setTimeout((()=>{if(e.stack){if(c.isErrorNoTelemetry(e))throw new c(e.message+"\n\n"+e.stack);throw new Error(e.message+"\n\n"+e.stack)}throw e}),0)}}addListener(e){return this.listeners.push(e),()=>{this._removeListener(e)}}emit(e){this.listeners.forEach((t=>{t(e)}))}_removeListener(e){this.listeners.splice(this.listeners.indexOf(e),1)}setUnexpectedErrorHandler(e){this.unexpectedErrorHandler=e}getUnexpectedErrorHandler(){return this.unexpectedErrorHandler}onUnexpectedError(e){this.unexpectedErrorHandler(e),this.emit(e)}onUnexpectedExternalError(e){this.unexpectedErrorHandler(e)}}t.ErrorHandler=i,t.errorHandler=new i;const s="Canceled";function r(e){return e instanceof n||e instanceof Error&&e.name===s&&e.message===s}class n extends Error{constructor(){super(s),this.name=this.message}}t.CancellationError=n;class o extends TypeError{constructor(e){super(e?`${e} is read-only and cannot be changed`:"Cannot change read-only property")}}t.ReadonlyError=o;class a extends Error{constructor(e){super("NotImplemented"),e&&(this.message=e)}}t.NotImplementedError=a;class l extends Error{constructor(e){super("NotSupported"),e&&(this.message=e)}}t.NotSupportedError=l;class h extends Error{constructor(){super(...arguments),this.isExpected=!0}}t.ExpectedError=h;class c extends Error{constructor(e){super(e),this.name="CodeExpectedError"}static fromError(e){if(e instanceof c)return e;const t=new c;return t.message=e.message,t.stack=e.stack,t}static isErrorNoTelemetry(e){return"CodeExpectedError"===e.name}}t.ErrorNoTelemetry=c;class d extends Error{constructor(e){super(e||"An unexpected bug occurred."),Object.setPrototypeOf(this,d.prototype)}}t.BugIndicatingError=d},802:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ValueWithChangeEvent=t.Relay=t.EventBufferer=t.DynamicListEventMultiplexer=t.EventMultiplexer=t.MicrotaskEmitter=t.DebounceEmitter=t.PauseableEmitter=t.AsyncEmitter=t.createEventDeliveryQueue=t.Emitter=t.ListenerRefusalError=t.ListenerLeakError=t.EventProfiling=t.Event=void 0,t.setGlobalLeakWarningThreshold=function(e){const t=c;return c=e,{dispose(){c=t}}};const s=i(9807),r=i(8841),n=i(7150),o=i(6317),a=i(9725);var l;!function(e){function t(e){return(t,i=null,s)=>{let r,n=!1;return r=e((e=>{if(!n)return r?r.dispose():n=!0,t.call(i,e)}),null,s),n&&r.dispose(),r}}function i(e,t,i){return r(((i,s=null,r)=>e((e=>i.call(s,t(e))),null,r)),i)}function s(e,t,i){return r(((i,s=null,r)=>e((e=>t(e)&&i.call(s,e)),null,r)),i)}function r(e,t){let i;const s=new m({onWillAddFirstListener(){i=e(s.fire,s)},onDidRemoveLastListener(){i?.dispose()}});return t?.add(s),s.event}function o(e,t,i=100,s=!1,r=!1,n,o){let a,l,h,c,d=0;const u=new m({leakWarningThreshold:n,onWillAddFirstListener(){a=e((e=>{d++,l=t(l,e),s&&!h&&(u.fire(l),l=void 0),c=()=>{const e=l;l=void 0,h=void 0,(!s||d>1)&&u.fire(e),d=0},"number"==typeof i?(clearTimeout(h),h=setTimeout(c,i)):void 0===h&&(h=0,queueMicrotask(c))}))},onWillRemoveListener(){r&&d>0&&c?.()},onDidRemoveLastListener(){c=void 0,a.dispose()}});return o?.add(u),u.event}e.None=()=>n.Disposable.None,e.defer=function(e,t){return o(e,(()=>{}),0,void 0,!0,void 0,t)},e.once=t,e.map=i,e.forEach=function(e,t,i){return r(((i,s=null,r)=>e((e=>{t(e),i.call(s,e)}),null,r)),i)},e.filter=s,e.signal=function(e){return e},e.any=function(...e){return(t,i=null,s)=>{return r=(0,n.combinedDisposable)(...e.map((e=>e((e=>t.call(i,e)))))),(o=s)instanceof Array?o.push(r):o&&o.add(r),r;var r,o}},e.reduce=function(e,t,s,r){let n=s;return i(e,(e=>(n=t(n,e),n)),r)},e.debounce=o,e.accumulate=function(t,i=0,s){return e.debounce(t,((e,t)=>e?(e.push(t),e):[t]),i,void 0,!0,void 0,s)},e.latch=function(e,t=(e,t)=>e===t,i){let r,n=!0;return s(e,(e=>{const i=n||!t(e,r);return n=!1,r=e,i}),i)},e.split=function(t,i,s){return[e.filter(t,i,s),e.filter(t,(e=>!i(e)),s)]},e.buffer=function(e,t=!1,i=[],s){let r=i.slice(),n=e((e=>{r?r.push(e):a.fire(e)}));s&&s.add(n);const o=()=>{r?.forEach((e=>a.fire(e))),r=null},a=new m({onWillAddFirstListener(){n||(n=e((e=>a.fire(e))),s&&s.add(n))},onDidAddFirstListener(){r&&(t?setTimeout(o):o())},onDidRemoveLastListener(){n&&n.dispose(),n=null}});return s&&s.add(a),a.event},e.chain=function(e,t){return(i,s,r)=>{const n=t(new l);return e((function(e){const t=n.evaluate(e);t!==a&&i.call(s,t)}),void 0,r)}};const a=Symbol("HaltChainable");class l{constructor(){this.steps=[]}map(e){return this.steps.push(e),this}forEach(e){return this.steps.push((t=>(e(t),t))),this}filter(e){return this.steps.push((t=>e(t)?t:a)),this}reduce(e,t){let i=t;return this.steps.push((t=>(i=e(i,t),i))),this}latch(e=(e,t)=>e===t){let t,i=!0;return this.steps.push((s=>{const r=i||!e(s,t);return i=!1,t=s,r?s:a})),this}evaluate(e){for(const t of this.steps)if((e=t(e))===a)break;return e}}e.fromNodeEventEmitter=function(e,t,i=e=>e){const s=(...e)=>r.fire(i(...e)),r=new m({onWillAddFirstListener:()=>e.on(t,s),onDidRemoveLastListener:()=>e.removeListener(t,s)});return r.event},e.fromDOMEventEmitter=function(e,t,i=e=>e){const s=(...e)=>r.fire(i(...e)),r=new m({onWillAddFirstListener:()=>e.addEventListener(t,s),onDidRemoveLastListener:()=>e.removeEventListener(t,s)});return r.event},e.toPromise=function(e){return new Promise((i=>t(e)(i)))},e.fromPromise=function(e){const t=new m;return e.then((e=>{t.fire(e)}),(()=>{t.fire(void 0)})).finally((()=>{t.dispose()})),t.event},e.forward=function(e,t){return e((e=>t.fire(e)))},e.runAndSubscribe=function(e,t,i){return t(i),e((e=>t(e)))};class h{constructor(e,t){this._observable=e,this._counter=0,this._hasChanged=!1;const i={onWillAddFirstListener:()=>{e.addObserver(this)},onDidRemoveLastListener:()=>{e.removeObserver(this)}};this.emitter=new m(i),t&&t.add(this.emitter)}beginUpdate(e){this._counter++}handlePossibleChange(e){}handleChange(e,t){this._hasChanged=!0}endUpdate(e){this._counter--,0===this._counter&&(this._observable.reportChanges(),this._hasChanged&&(this._hasChanged=!1,this.emitter.fire(this._observable.get())))}}e.fromObservable=function(e,t){return new h(e,t).emitter.event},e.fromObservableLight=function(e){return(t,i,s)=>{let r=0,o=!1;const a={beginUpdate(){r++},endUpdate(){r--,0===r&&(e.reportChanges(),o&&(o=!1,t.call(i)))},handlePossibleChange(){},handleChange(){o=!0}};e.addObserver(a),e.reportChanges();const l={dispose(){e.removeObserver(a)}};return s instanceof n.DisposableStore?s.add(l):Array.isArray(s)&&s.push(l),l}}}(l||(t.Event=l={}));class h{static{this.all=new Set}static{this._idPool=0}constructor(e){this.listenerCount=0,this.invocationCount=0,this.elapsedOverall=0,this.durations=[],this.name=`${e}_${h._idPool++}`,h.all.add(this)}start(e){this._stopWatch=new a.StopWatch,this.listenerCount=e}stop(){if(this._stopWatch){const e=this._stopWatch.elapsed();this.durations.push(e),this.elapsedOverall+=e,this.invocationCount+=1,this._stopWatch=void 0}}}t.EventProfiling=h;let c=-1;class d{static{this._idPool=1}constructor(e,t,i=(d._idPool++).toString(16).padStart(3,"0")){this._errorHandler=e,this.threshold=t,this.name=i,this._warnCountdown=0}dispose(){this._stacks?.clear()}check(e,t){const i=this.threshold;if(i<=0||t{const t=this._stacks.get(e.value)||0;this._stacks.set(e.value,t-1)}}getMostFrequentStack(){if(!this._stacks)return;let e,t=0;for(const[i,s]of this._stacks)(!e||t0||this._options?.leakWarningThreshold?new d(e?.onListenerError??s.onUnexpectedError,this._options?.leakWarningThreshold??c):void 0,this._perfMon=this._options?._profName?new h(this._options._profName):void 0,this._deliveryQueue=this._options?.deliveryQueue}dispose(){this._disposed||(this._disposed=!0,this._deliveryQueue?.current===this&&this._deliveryQueue.reset(),this._listeners&&(this._listeners=void 0,this._size=0),this._options?.onDidRemoveLastListener?.(),this._leakageMon?.dispose())}get event(){return this._event??=(e,t,i)=>{if(this._leakageMon&&this._size>this._leakageMon.threshold**2){const e=`[${this._leakageMon.name}] REFUSES to accept new listeners because it exceeded its threshold by far (${this._size} vs ${this._leakageMon.threshold})`;console.warn(e);const t=this._leakageMon.getMostFrequentStack()??["UNKNOWN stack",-1],i=new f(`${e}. HINT: Stack shows most frequent listener (${t[1]}-times)`,t[0]);return(this._options?.onListenerError||s.onUnexpectedError)(i),n.Disposable.None}if(this._disposed)return n.Disposable.None;t&&(e=e.bind(t));const r=new g(e);let o;this._leakageMon&&this._size>=Math.ceil(.2*this._leakageMon.threshold)&&(r.stack=u.create(),o=this._leakageMon.check(r.stack,this._size+1)),this._listeners?this._listeners instanceof g?(this._deliveryQueue??=new v,this._listeners=[this._listeners,r]):this._listeners.push(r):(this._options?.onWillAddFirstListener?.(this),this._listeners=r,this._options?.onDidAddFirstListener?.(this)),this._size++;const a=(0,n.toDisposable)((()=>{o?.(),this._removeListener(r)}));return i instanceof n.DisposableStore?i.add(a):Array.isArray(i)&&i.push(a),a},this._event}_removeListener(e){if(this._options?.onWillRemoveListener?.(this),!this._listeners)return;if(1===this._size)return this._listeners=void 0,this._options?.onDidRemoveLastListener?.(this),void(this._size=0);const t=this._listeners,i=t.indexOf(e);if(-1===i)throw console.log("disposed?",this._disposed),console.log("size?",this._size),console.log("arr?",JSON.stringify(this._listeners)),new Error("Attempted to dispose unknown listener");this._size--,t[i]=void 0;const s=this._deliveryQueue.current===this;if(2*this._size<=t.length){let e=0;for(let i=0;i0}}t.Emitter=m,t.createEventDeliveryQueue=()=>new v;class v{constructor(){this.i=-1,this.end=0}enqueue(e,t,i){this.i=0,this.end=i,this.current=e,this.value=t}reset(){this.i=this.end,this.current=void 0,this.value=void 0}}t.AsyncEmitter=class extends m{async fireAsync(e,t,i){if(this._listeners)for(this._asyncDeliveryQueue||(this._asyncDeliveryQueue=new o.LinkedList),((e,t)=>{if(e instanceof g)t(e);else for(let i=0;ithis._asyncDeliveryQueue.push([t.value,e])));this._asyncDeliveryQueue.size>0&&!t.isCancellationRequested;){const[e,r]=this._asyncDeliveryQueue.shift(),n=[],o={...r,token:t,waitUntil:t=>{if(Object.isFrozen(n))throw new Error("waitUntil can NOT be called asynchronous");i&&(t=i(t,e)),n.push(t)}};try{e(o)}catch(e){(0,s.onUnexpectedError)(e);continue}Object.freeze(n),await Promise.allSettled(n).then((e=>{for(const t of e)"rejected"===t.status&&(0,s.onUnexpectedError)(t.reason)}))}}};class S extends m{get isPaused(){return 0!==this._isPaused}constructor(e){super(e),this._isPaused=0,this._eventQueue=new o.LinkedList,this._mergeFn=e?.merge}pause(){this._isPaused++}resume(){if(0!==this._isPaused&&0==--this._isPaused)if(this._mergeFn){if(this._eventQueue.size>0){const e=Array.from(this._eventQueue);this._eventQueue.clear(),super.fire(this._mergeFn(e))}}else for(;!this._isPaused&&0!==this._eventQueue.size;)super.fire(this._eventQueue.shift())}fire(e){this._size&&(0!==this._isPaused?this._eventQueue.push(e):super.fire(e))}}t.PauseableEmitter=S,t.DebounceEmitter=class extends S{constructor(e){super(e),this._delay=e.delay??100}fire(e){this._handle||(this.pause(),this._handle=setTimeout((()=>{this._handle=void 0,this.resume()}),this._delay)),super.fire(e)}},t.MicrotaskEmitter=class extends m{constructor(e){super(e),this._queuedEvents=[],this._mergeFn=e?.merge}fire(e){this.hasListeners()&&(this._queuedEvents.push(e),1===this._queuedEvents.length&&queueMicrotask((()=>{this._mergeFn?super.fire(this._mergeFn(this._queuedEvents)):this._queuedEvents.forEach((e=>super.fire(e))),this._queuedEvents=[]})))}};class b{constructor(){this.hasListeners=!1,this.events=[],this.emitter=new m({onWillAddFirstListener:()=>this.onFirstListenerAdd(),onDidRemoveLastListener:()=>this.onLastListenerRemove()})}get event(){return this.emitter.event}add(e){const t={event:e,listener:null};return this.events.push(t),this.hasListeners&&this.hook(t),(0,n.toDisposable)((0,r.createSingleCallFunction)((()=>{this.hasListeners&&this.unhook(t);const e=this.events.indexOf(t);this.events.splice(e,1)})))}onFirstListenerAdd(){this.hasListeners=!0,this.events.forEach((e=>this.hook(e)))}onLastListenerRemove(){this.hasListeners=!1,this.events.forEach((e=>this.unhook(e)))}hook(e){e.listener=e.event((e=>this.emitter.fire(e)))}unhook(e){e.listener?.dispose(),e.listener=null}dispose(){this.emitter.dispose();for(const e of this.events)e.listener?.dispose();this.events=[]}}t.EventMultiplexer=b,t.DynamicListEventMultiplexer=class{constructor(e,t,i,s){this._store=new n.DisposableStore;const r=this._store.add(new b),o=this._store.add(new n.DisposableMap);function a(e){o.set(e,r.add(s(e)))}for(const t of e)a(t);this._store.add(t((e=>{a(e)}))),this._store.add(i((e=>{o.deleteAndDispose(e)}))),this.event=r.event}dispose(){this._store.dispose()}},t.EventBufferer=class{constructor(){this.data=[]}wrapEvent(e,t,i){return(s,r,n)=>e((e=>{const n=this.data[this.data.length-1];if(!t)return void(n?n.buffers.push((()=>s.call(r,e))):s.call(r,e));const o=n;o?(o.items??=[],o.items.push(e),0===o.buffers.length&&n.buffers.push((()=>{o.reducedResult??=i?o.items.reduce(t,i):o.items.reduce(t),s.call(r,o.reducedResult)}))):s.call(r,t(i,e))}),void 0,n)}bufferEvents(e){const t={buffers:new Array};this.data.push(t);const i=e();return this.data.pop(),t.buffers.forEach((e=>e())),i}},t.Relay=class{constructor(){this.listening=!1,this.inputEvent=l.None,this.inputEventListener=n.Disposable.None,this.emitter=new m({onDidAddFirstListener:()=>{this.listening=!0,this.inputEventListener=this.inputEvent(this.emitter.fire,this.emitter)},onDidRemoveLastListener:()=>{this.listening=!1,this.inputEventListener.dispose()}}),this.event=this.emitter.event}set input(e){this.inputEvent=e,this.listening&&(this.inputEventListener.dispose(),this.inputEventListener=e(this.emitter.fire,this.emitter))}dispose(){this.inputEventListener.dispose(),this.emitter.dispose()}},t.ValueWithChangeEvent=class{static const(e){return new C(e)}constructor(e){this._value=e,this._onDidChange=new m,this.onDidChange=this._onDidChange.event}get value(){return this._value}set value(e){e!==this._value&&(this._value=e,this._onDidChange.fire(void 0))}};class C{constructor(e){this.value=e,this.onDidChange=l.None}}},8841:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.createSingleCallFunction=function(e,t){const i=this;let s,r=!1;return function(){if(r)return s;if(r=!0,t)try{s=e.apply(i,arguments)}finally{t()}else s=e.apply(i,arguments);return s}}},6304:function(e,t,i){var s=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),r=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&s(t,e,i);return r(t,e),t};Object.defineProperty(t,"__esModule",{value:!0}),t.StringSHA1=t.Hasher=void 0,t.hash=function(e){return a(e,0)},t.doHash=a,t.numberHash=l,t.stringHash=h,t.toHexString=_;const o=n(i(1316));function a(e,t){switch(typeof e){case"object":return null===e?l(349,t):Array.isArray(e)?(i=e,s=l(104579,s=t),i.reduce(((e,t)=>a(t,e)),s)):function(e,t){return t=l(181387,t),Object.keys(e).sort().reduce(((t,i)=>(t=h(i,t),a(e[i],t))),t)}(e,t);case"string":return h(e,t);case"boolean":return function(e,t){return l(e?433:863,t)}(e,t);case"number":return l(e,t);case"undefined":return l(937,t);default:return l(617,t)}var i,s}function l(e,t){return(t<<5)-t+e|0}function h(e,t){t=l(149417,t);for(let i=0,s=e.length;i>>s)>>>0}function u(e,t=0,i=e.byteLength,s=0){for(let r=0;re.toString(16).padStart(2,"0"))).join(""):function(e,t,i="0"){for(;e.length>>0).toString(16),t/4)}t.Hasher=class{constructor(){this._value=0}get value(){return this._value}hash(e){return this._value=a(e,this._value),this._value}},function(e){e[e.BLOCK_SIZE=64]="BLOCK_SIZE",e[e.UNICODE_REPLACEMENT=65533]="UNICODE_REPLACEMENT"}(c||(c={}));class f{static{this._bigBlock32=new DataView(new ArrayBuffer(320))}constructor(){this._h0=1732584193,this._h1=4023233417,this._h2=2562383102,this._h3=271733878,this._h4=3285377520,this._buff=new Uint8Array(c.BLOCK_SIZE+3),this._buffDV=new DataView(this._buff.buffer),this._buffLen=0,this._totalLen=0,this._leftoverHighSurrogate=0,this._finished=!1}update(e){const t=e.length;if(0===t)return;const i=this._buff;let s,r,n=this._buffLen,a=this._leftoverHighSurrogate;for(0!==a?(s=a,r=-1,a=0):(s=e.charCodeAt(0),r=0);;){let l=s;if(o.isHighSurrogate(s)){if(!(r+1>>6,e[t++]=128|(63&i)>>>0):i<65536?(e[t++]=224|(61440&i)>>>12,e[t++]=128|(4032&i)>>>6,e[t++]=128|(63&i)>>>0):(e[t++]=240|(1835008&i)>>>18,e[t++]=128|(258048&i)>>>12,e[t++]=128|(4032&i)>>>6,e[t++]=128|(63&i)>>>0),t>=c.BLOCK_SIZE&&(this._step(),t-=c.BLOCK_SIZE,this._totalLen+=c.BLOCK_SIZE,e[0]=e[c.BLOCK_SIZE+0],e[1]=e[c.BLOCK_SIZE+1],e[2]=e[c.BLOCK_SIZE+2]),t}digest(){return this._finished||(this._finished=!0,this._leftoverHighSurrogate&&(this._leftoverHighSurrogate=0,this._buffLen=this._push(this._buff,this._buffLen,c.UNICODE_REPLACEMENT)),this._totalLen+=this._buffLen,this._wrapUp()),_(this._h0)+_(this._h1)+_(this._h2)+_(this._h3)+_(this._h4)}_wrapUp(){this._buff[this._buffLen++]=128,u(this._buff,this._buffLen),this._buffLen>56&&(this._step(),u(this._buff));const e=8*this._totalLen;this._buffDV.setUint32(56,Math.floor(e/4294967296),!1),this._buffDV.setUint32(60,e%4294967296,!1),this._step()}_step(){const e=f._bigBlock32,t=this._buffDV;for(let i=0;i<64;i+=4)e.setUint32(i,t.getUint32(i,!1),!1);for(let t=64;t<320;t+=4)e.setUint32(t,d(e.getUint32(t-12,!1)^e.getUint32(t-32,!1)^e.getUint32(t-56,!1)^e.getUint32(t-64,!1),1),!1);let i,s,r,n=this._h0,o=this._h1,a=this._h2,l=this._h3,h=this._h4;for(let t=0;t<80;t++)t<20?(i=o&a|~o&l,s=1518500249):t<40?(i=o^a^l,s=1859775393):t<60?(i=o&a|o&l|a&l,s=2400959708):(i=o^a^l,s=3395469782),r=d(n,5)+i+h+s+e.getUint32(4*t,!1)&4294967295,h=l,l=a,a=d(o,30),o=n,n=r;this._h0=this._h0+n&4294967295,this._h1=this._h1+o&4294967295,this._h2=this._h2+a&4294967295,this._h3=this._h3+l&4294967295,this._h4=this._h4+h&4294967295}}t.StringSHA1=f},4218:(e,t)=>{var i;Object.defineProperty(t,"__esModule",{value:!0}),t.Iterable=void 0,function(e){function t(e){return e&&"object"==typeof e&&"function"==typeof e[Symbol.iterator]}e.is=t;const i=Object.freeze([]);function*s(e){yield e}e.empty=function(){return i},e.single=s,e.wrap=function(e){return t(e)?e:s(e)},e.from=function(e){return e||i},e.reverse=function*(e){for(let t=e.length-1;t>=0;t--)yield e[t]},e.isEmpty=function(e){return!e||!0===e[Symbol.iterator]().next().done},e.first=function(e){return e[Symbol.iterator]().next().value},e.some=function(e,t){let i=0;for(const s of e)if(t(s,i++))return!0;return!1},e.find=function(e,t){for(const i of e)if(t(i))return i},e.filter=function*(e,t){for(const i of e)t(i)&&(yield i)},e.map=function*(e,t){let i=0;for(const s of e)yield t(s,i++)},e.flatMap=function*(e,t){let i=0;for(const s of e)yield*t(s,i++)},e.concat=function*(...e){for(const t of e)yield*t},e.reduce=function(e,t,i){let s=i;for(const i of e)s=t(s,i);return s},e.slice=function*(e,t,i=e.length){for(t<0&&(t+=e.length),i<0?i+=e.length:i>e.length&&(i=e.length);tr}]},e.asyncToArray=async function(e){const t=[];for await(const i of e)t.push(i);return Promise.resolve(t)}}(i||(t.Iterable=i={}))},7883:(e,t)=>{var i,s;Object.defineProperty(t,"__esModule",{value:!0}),t.KeyMod=t.KeyCodeUtils=t.ScanCodeUtils=t.NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE=t.EVENT_KEY_CODE_MAP=t.ScanCode=t.KeyCode=void 0,t.KeyChord=function(e,t){return(e|(65535&t)<<16>>>0)>>>0},function(e){e[e.DependsOnKbLayout=-1]="DependsOnKbLayout",e[e.Unknown=0]="Unknown",e[e.Backspace=1]="Backspace",e[e.Tab=2]="Tab",e[e.Enter=3]="Enter",e[e.Shift=4]="Shift",e[e.Ctrl=5]="Ctrl",e[e.Alt=6]="Alt",e[e.PauseBreak=7]="PauseBreak",e[e.CapsLock=8]="CapsLock",e[e.Escape=9]="Escape",e[e.Space=10]="Space",e[e.PageUp=11]="PageUp",e[e.PageDown=12]="PageDown",e[e.End=13]="End",e[e.Home=14]="Home",e[e.LeftArrow=15]="LeftArrow",e[e.UpArrow=16]="UpArrow",e[e.RightArrow=17]="RightArrow",e[e.DownArrow=18]="DownArrow",e[e.Insert=19]="Insert",e[e.Delete=20]="Delete",e[e.Digit0=21]="Digit0",e[e.Digit1=22]="Digit1",e[e.Digit2=23]="Digit2",e[e.Digit3=24]="Digit3",e[e.Digit4=25]="Digit4",e[e.Digit5=26]="Digit5",e[e.Digit6=27]="Digit6",e[e.Digit7=28]="Digit7",e[e.Digit8=29]="Digit8",e[e.Digit9=30]="Digit9",e[e.KeyA=31]="KeyA",e[e.KeyB=32]="KeyB",e[e.KeyC=33]="KeyC",e[e.KeyD=34]="KeyD",e[e.KeyE=35]="KeyE",e[e.KeyF=36]="KeyF",e[e.KeyG=37]="KeyG",e[e.KeyH=38]="KeyH",e[e.KeyI=39]="KeyI",e[e.KeyJ=40]="KeyJ",e[e.KeyK=41]="KeyK",e[e.KeyL=42]="KeyL",e[e.KeyM=43]="KeyM",e[e.KeyN=44]="KeyN",e[e.KeyO=45]="KeyO",e[e.KeyP=46]="KeyP",e[e.KeyQ=47]="KeyQ",e[e.KeyR=48]="KeyR",e[e.KeyS=49]="KeyS",e[e.KeyT=50]="KeyT",e[e.KeyU=51]="KeyU",e[e.KeyV=52]="KeyV",e[e.KeyW=53]="KeyW",e[e.KeyX=54]="KeyX",e[e.KeyY=55]="KeyY",e[e.KeyZ=56]="KeyZ",e[e.Meta=57]="Meta",e[e.ContextMenu=58]="ContextMenu",e[e.F1=59]="F1",e[e.F2=60]="F2",e[e.F3=61]="F3",e[e.F4=62]="F4",e[e.F5=63]="F5",e[e.F6=64]="F6",e[e.F7=65]="F7",e[e.F8=66]="F8",e[e.F9=67]="F9",e[e.F10=68]="F10",e[e.F11=69]="F11",e[e.F12=70]="F12",e[e.F13=71]="F13",e[e.F14=72]="F14",e[e.F15=73]="F15",e[e.F16=74]="F16",e[e.F17=75]="F17",e[e.F18=76]="F18",e[e.F19=77]="F19",e[e.F20=78]="F20",e[e.F21=79]="F21",e[e.F22=80]="F22",e[e.F23=81]="F23",e[e.F24=82]="F24",e[e.NumLock=83]="NumLock",e[e.ScrollLock=84]="ScrollLock",e[e.Semicolon=85]="Semicolon",e[e.Equal=86]="Equal",e[e.Comma=87]="Comma",e[e.Minus=88]="Minus",e[e.Period=89]="Period",e[e.Slash=90]="Slash",e[e.Backquote=91]="Backquote",e[e.BracketLeft=92]="BracketLeft",e[e.Backslash=93]="Backslash",e[e.BracketRight=94]="BracketRight",e[e.Quote=95]="Quote",e[e.OEM_8=96]="OEM_8",e[e.IntlBackslash=97]="IntlBackslash",e[e.Numpad0=98]="Numpad0",e[e.Numpad1=99]="Numpad1",e[e.Numpad2=100]="Numpad2",e[e.Numpad3=101]="Numpad3",e[e.Numpad4=102]="Numpad4",e[e.Numpad5=103]="Numpad5",e[e.Numpad6=104]="Numpad6",e[e.Numpad7=105]="Numpad7",e[e.Numpad8=106]="Numpad8",e[e.Numpad9=107]="Numpad9",e[e.NumpadMultiply=108]="NumpadMultiply",e[e.NumpadAdd=109]="NumpadAdd",e[e.NUMPAD_SEPARATOR=110]="NUMPAD_SEPARATOR",e[e.NumpadSubtract=111]="NumpadSubtract",e[e.NumpadDecimal=112]="NumpadDecimal",e[e.NumpadDivide=113]="NumpadDivide",e[e.KEY_IN_COMPOSITION=114]="KEY_IN_COMPOSITION",e[e.ABNT_C1=115]="ABNT_C1",e[e.ABNT_C2=116]="ABNT_C2",e[e.AudioVolumeMute=117]="AudioVolumeMute",e[e.AudioVolumeUp=118]="AudioVolumeUp",e[e.AudioVolumeDown=119]="AudioVolumeDown",e[e.BrowserSearch=120]="BrowserSearch",e[e.BrowserHome=121]="BrowserHome",e[e.BrowserBack=122]="BrowserBack",e[e.BrowserForward=123]="BrowserForward",e[e.MediaTrackNext=124]="MediaTrackNext",e[e.MediaTrackPrevious=125]="MediaTrackPrevious",e[e.MediaStop=126]="MediaStop",e[e.MediaPlayPause=127]="MediaPlayPause",e[e.LaunchMediaPlayer=128]="LaunchMediaPlayer",e[e.LaunchMail=129]="LaunchMail",e[e.LaunchApp2=130]="LaunchApp2",e[e.Clear=131]="Clear",e[e.MAX_VALUE=132]="MAX_VALUE"}(i||(t.KeyCode=i={})),function(e){e[e.DependsOnKbLayout=-1]="DependsOnKbLayout",e[e.None=0]="None",e[e.Hyper=1]="Hyper",e[e.Super=2]="Super",e[e.Fn=3]="Fn",e[e.FnLock=4]="FnLock",e[e.Suspend=5]="Suspend",e[e.Resume=6]="Resume",e[e.Turbo=7]="Turbo",e[e.Sleep=8]="Sleep",e[e.WakeUp=9]="WakeUp",e[e.KeyA=10]="KeyA",e[e.KeyB=11]="KeyB",e[e.KeyC=12]="KeyC",e[e.KeyD=13]="KeyD",e[e.KeyE=14]="KeyE",e[e.KeyF=15]="KeyF",e[e.KeyG=16]="KeyG",e[e.KeyH=17]="KeyH",e[e.KeyI=18]="KeyI",e[e.KeyJ=19]="KeyJ",e[e.KeyK=20]="KeyK",e[e.KeyL=21]="KeyL",e[e.KeyM=22]="KeyM",e[e.KeyN=23]="KeyN",e[e.KeyO=24]="KeyO",e[e.KeyP=25]="KeyP",e[e.KeyQ=26]="KeyQ",e[e.KeyR=27]="KeyR",e[e.KeyS=28]="KeyS",e[e.KeyT=29]="KeyT",e[e.KeyU=30]="KeyU",e[e.KeyV=31]="KeyV",e[e.KeyW=32]="KeyW",e[e.KeyX=33]="KeyX",e[e.KeyY=34]="KeyY",e[e.KeyZ=35]="KeyZ",e[e.Digit1=36]="Digit1",e[e.Digit2=37]="Digit2",e[e.Digit3=38]="Digit3",e[e.Digit4=39]="Digit4",e[e.Digit5=40]="Digit5",e[e.Digit6=41]="Digit6",e[e.Digit7=42]="Digit7",e[e.Digit8=43]="Digit8",e[e.Digit9=44]="Digit9",e[e.Digit0=45]="Digit0",e[e.Enter=46]="Enter",e[e.Escape=47]="Escape",e[e.Backspace=48]="Backspace",e[e.Tab=49]="Tab",e[e.Space=50]="Space",e[e.Minus=51]="Minus",e[e.Equal=52]="Equal",e[e.BracketLeft=53]="BracketLeft",e[e.BracketRight=54]="BracketRight",e[e.Backslash=55]="Backslash",e[e.IntlHash=56]="IntlHash",e[e.Semicolon=57]="Semicolon",e[e.Quote=58]="Quote",e[e.Backquote=59]="Backquote",e[e.Comma=60]="Comma",e[e.Period=61]="Period",e[e.Slash=62]="Slash",e[e.CapsLock=63]="CapsLock",e[e.F1=64]="F1",e[e.F2=65]="F2",e[e.F3=66]="F3",e[e.F4=67]="F4",e[e.F5=68]="F5",e[e.F6=69]="F6",e[e.F7=70]="F7",e[e.F8=71]="F8",e[e.F9=72]="F9",e[e.F10=73]="F10",e[e.F11=74]="F11",e[e.F12=75]="F12",e[e.PrintScreen=76]="PrintScreen",e[e.ScrollLock=77]="ScrollLock",e[e.Pause=78]="Pause",e[e.Insert=79]="Insert",e[e.Home=80]="Home",e[e.PageUp=81]="PageUp",e[e.Delete=82]="Delete",e[e.End=83]="End",e[e.PageDown=84]="PageDown",e[e.ArrowRight=85]="ArrowRight",e[e.ArrowLeft=86]="ArrowLeft",e[e.ArrowDown=87]="ArrowDown",e[e.ArrowUp=88]="ArrowUp",e[e.NumLock=89]="NumLock",e[e.NumpadDivide=90]="NumpadDivide",e[e.NumpadMultiply=91]="NumpadMultiply",e[e.NumpadSubtract=92]="NumpadSubtract",e[e.NumpadAdd=93]="NumpadAdd",e[e.NumpadEnter=94]="NumpadEnter",e[e.Numpad1=95]="Numpad1",e[e.Numpad2=96]="Numpad2",e[e.Numpad3=97]="Numpad3",e[e.Numpad4=98]="Numpad4",e[e.Numpad5=99]="Numpad5",e[e.Numpad6=100]="Numpad6",e[e.Numpad7=101]="Numpad7",e[e.Numpad8=102]="Numpad8",e[e.Numpad9=103]="Numpad9",e[e.Numpad0=104]="Numpad0",e[e.NumpadDecimal=105]="NumpadDecimal",e[e.IntlBackslash=106]="IntlBackslash",e[e.ContextMenu=107]="ContextMenu",e[e.Power=108]="Power",e[e.NumpadEqual=109]="NumpadEqual",e[e.F13=110]="F13",e[e.F14=111]="F14",e[e.F15=112]="F15",e[e.F16=113]="F16",e[e.F17=114]="F17",e[e.F18=115]="F18",e[e.F19=116]="F19",e[e.F20=117]="F20",e[e.F21=118]="F21",e[e.F22=119]="F22",e[e.F23=120]="F23",e[e.F24=121]="F24",e[e.Open=122]="Open",e[e.Help=123]="Help",e[e.Select=124]="Select",e[e.Again=125]="Again",e[e.Undo=126]="Undo",e[e.Cut=127]="Cut",e[e.Copy=128]="Copy",e[e.Paste=129]="Paste",e[e.Find=130]="Find",e[e.AudioVolumeMute=131]="AudioVolumeMute",e[e.AudioVolumeUp=132]="AudioVolumeUp",e[e.AudioVolumeDown=133]="AudioVolumeDown",e[e.NumpadComma=134]="NumpadComma",e[e.IntlRo=135]="IntlRo",e[e.KanaMode=136]="KanaMode",e[e.IntlYen=137]="IntlYen",e[e.Convert=138]="Convert",e[e.NonConvert=139]="NonConvert",e[e.Lang1=140]="Lang1",e[e.Lang2=141]="Lang2",e[e.Lang3=142]="Lang3",e[e.Lang4=143]="Lang4",e[e.Lang5=144]="Lang5",e[e.Abort=145]="Abort",e[e.Props=146]="Props",e[e.NumpadParenLeft=147]="NumpadParenLeft",e[e.NumpadParenRight=148]="NumpadParenRight",e[e.NumpadBackspace=149]="NumpadBackspace",e[e.NumpadMemoryStore=150]="NumpadMemoryStore",e[e.NumpadMemoryRecall=151]="NumpadMemoryRecall",e[e.NumpadMemoryClear=152]="NumpadMemoryClear",e[e.NumpadMemoryAdd=153]="NumpadMemoryAdd",e[e.NumpadMemorySubtract=154]="NumpadMemorySubtract",e[e.NumpadClear=155]="NumpadClear",e[e.NumpadClearEntry=156]="NumpadClearEntry",e[e.ControlLeft=157]="ControlLeft",e[e.ShiftLeft=158]="ShiftLeft",e[e.AltLeft=159]="AltLeft",e[e.MetaLeft=160]="MetaLeft",e[e.ControlRight=161]="ControlRight",e[e.ShiftRight=162]="ShiftRight",e[e.AltRight=163]="AltRight",e[e.MetaRight=164]="MetaRight",e[e.BrightnessUp=165]="BrightnessUp",e[e.BrightnessDown=166]="BrightnessDown",e[e.MediaPlay=167]="MediaPlay",e[e.MediaRecord=168]="MediaRecord",e[e.MediaFastForward=169]="MediaFastForward",e[e.MediaRewind=170]="MediaRewind",e[e.MediaTrackNext=171]="MediaTrackNext",e[e.MediaTrackPrevious=172]="MediaTrackPrevious",e[e.MediaStop=173]="MediaStop",e[e.Eject=174]="Eject",e[e.MediaPlayPause=175]="MediaPlayPause",e[e.MediaSelect=176]="MediaSelect",e[e.LaunchMail=177]="LaunchMail",e[e.LaunchApp2=178]="LaunchApp2",e[e.LaunchApp1=179]="LaunchApp1",e[e.SelectTask=180]="SelectTask",e[e.LaunchScreenSaver=181]="LaunchScreenSaver",e[e.BrowserSearch=182]="BrowserSearch",e[e.BrowserHome=183]="BrowserHome",e[e.BrowserBack=184]="BrowserBack",e[e.BrowserForward=185]="BrowserForward",e[e.BrowserStop=186]="BrowserStop",e[e.BrowserRefresh=187]="BrowserRefresh",e[e.BrowserFavorites=188]="BrowserFavorites",e[e.ZoomToggle=189]="ZoomToggle",e[e.MailReply=190]="MailReply",e[e.MailForward=191]="MailForward",e[e.MailSend=192]="MailSend",e[e.MAX_VALUE=193]="MAX_VALUE"}(s||(t.ScanCode=s={}));class r{constructor(){this._keyCodeToStr=[],this._strToKeyCode=Object.create(null)}define(e,t){this._keyCodeToStr[e]=t,this._strToKeyCode[t.toLowerCase()]=e}keyCodeToStr(e){return this._keyCodeToStr[e]}strToKeyCode(e){return this._strToKeyCode[e.toLowerCase()]||i.Unknown}}const n=new r,o=new r,a=new r;t.EVENT_KEY_CODE_MAP=new Array(230),t.NATIVE_WINDOWS_KEY_CODE_TO_KEY_CODE={};const l=[],h=Object.create(null),c=Object.create(null);var d,u;t.ScanCodeUtils={lowerCaseToEnum:e=>c[e]||s.None,toEnum:e=>h[e]||s.None,toString:e=>l[e]||"None"},function(e){e.toString=function(e){return n.keyCodeToStr(e)},e.fromString=function(e){return n.strToKeyCode(e)},e.toUserSettingsUS=function(e){return o.keyCodeToStr(e)},e.toUserSettingsGeneral=function(e){return a.keyCodeToStr(e)},e.fromUserSettings=function(e){return o.strToKeyCode(e)||a.strToKeyCode(e)},e.toElectronAccelerator=function(e){if(e>=i.Numpad0&&e<=i.NumpadDivide)return null;switch(e){case i.UpArrow:return"Up";case i.DownArrow:return"Down";case i.LeftArrow:return"Left";case i.RightArrow:return"Right"}return n.keyCodeToStr(e)}}(d||(t.KeyCodeUtils=d={})),function(e){e[e.CtrlCmd=2048]="CtrlCmd",e[e.Shift=1024]="Shift",e[e.Alt=512]="Alt",e[e.WinCtrl=256]="WinCtrl"}(u||(t.KeyMod=u={}))},2811:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ResolvedKeybinding=t.ResolvedChord=t.Keybinding=t.ScanCodeChord=t.KeyCodeChord=void 0,t.decodeKeybinding=function(e,t){if("number"==typeof e){if(0===e)return null;const i=(65535&e)>>>0,s=(4294901760&e)>>>16;return new c(0!==s?[a(i,t),a(s,t)]:[a(i,t)])}{const i=[];for(let s=0;s{Object.defineProperty(t,"__esModule",{value:!0}),t.Lazy=void 0,t.Lazy=class{constructor(e){this.executor=e,this._didRun=!1}get hasValue(){return this._didRun}get value(){if(!this._didRun)try{this._value=this.executor()}catch(e){this._error=e}finally{this._didRun=!0}if(this._error)throw this._error;return this._value}get rawValue(){return this._value}}},7150:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DisposableMap=t.ImmortalReference=t.AsyncReferenceCollection=t.ReferenceCollection=t.SafeDisposable=t.RefCountedDisposable=t.MandatoryMutableDisposable=t.MutableDisposable=t.Disposable=t.DisposableStore=t.DisposableTracker=void 0,t.setDisposableTracker=function(e){l=e},t.trackDisposable=c,t.markAsDisposed=d,t.markAsSingleton=function(e){return l?.markAsSingleton(e),e},t.isDisposable=_,t.dispose=f,t.disposeIfDisposable=function(e){for(const t of e)_(t)&&t.dispose();return[]},t.combinedDisposable=function(...e){const t=p((()=>f(e)));return function(e,t){if(l)for(const i of e)l.setParent(i,t)}(e,t),t},t.toDisposable=p,t.disposeOnReturn=function(e){const t=new g;try{e(t)}finally{t.dispose()}};const s=i(3058),r=i(9087),n=i(2608),o=i(8841),a=i(4218);let l=null;class h{constructor(){this.livingDisposables=new Map}static{this.idx=0}getDisposableData(e){let t=this.livingDisposables.get(e);return t||(t={parent:null,source:null,isSingleton:!1,value:e,idx:h.idx++},this.livingDisposables.set(e,t)),t}trackDisposable(e){const t=this.getDisposableData(e);t.source||(t.source=(new Error).stack)}setParent(e,t){this.getDisposableData(e).parent=t}markAsDisposed(e){this.livingDisposables.delete(e)}markAsSingleton(e){this.getDisposableData(e).isSingleton=!0}getRootParent(e,t){const i=t.get(e);if(i)return i;const s=e.parent?this.getRootParent(this.getDisposableData(e.parent),t):e;return t.set(e,s),s}getTrackedDisposables(){const e=new Map;return[...this.livingDisposables.entries()].filter((([,t])=>null!==t.source&&!this.getRootParent(t,e).isSingleton)).flatMap((([e])=>e))}computeLeakingDisposables(e=10,t){let i;if(t)i=t;else{const e=new Map,t=[...this.livingDisposables.values()].filter((t=>null!==t.source&&!this.getRootParent(t,e).isSingleton));if(0===t.length)return;const s=new Set(t.map((e=>e.value)));if(i=t.filter((e=>!(e.parent&&s.has(e.parent)))),0===i.length)throw new Error("There are cyclic diposable chains!")}if(!i)return;function o(e){const t=e.source.split("\n").map((e=>e.trim().replace("at ",""))).filter((e=>""!==e));return function(e,t){for(;e.length>0&&t.some((t=>"string"==typeof t?t===e[0]:e[0].match(t)));)e.shift()}(t,["Error",/^trackDisposable \(.*\)$/,/^DisposableTracker.trackDisposable \(.*\)$/]),t.reverse()}const a=new n.SetMap;for(const e of i){const t=o(e);for(let i=0;i<=t.length;i++)a.add(t.slice(0,i).join("\n"),e)}i.sort((0,s.compareBy)((e=>e.idx),s.numberComparator));let l="",h=0;for(const t of i.slice(0,e)){h++;const e=o(t),s=[];for(let t=0;to(e)[t])),(e=>e));delete h[e[t]];for(const[e,t]of Object.entries(h))s.unshift(` - stacktraces of ${t.length} other leaks continue with ${e}`);s.unshift(n)}l+=`\n\n\n==================== Leaking disposable ${h}/${i.length}: ${t.value.constructor.name} ====================\n${s.join("\n")}\n============================================================\n\n`}return i.length>e&&(l+=`\n\n\n... and ${i.length-e} more leaking disposables\n\n`),{leaks:i,details:l}}}function c(e){return l?.trackDisposable(e),e}function d(e){l?.markAsDisposed(e)}function u(e,t){l?.setParent(e,t)}function _(e){return"object"==typeof e&&null!==e&&"function"==typeof e.dispose&&0===e.dispose.length}function f(e){if(a.Iterable.is(e)){const t=[];for(const i of e)if(i)try{i.dispose()}catch(e){t.push(e)}if(1===t.length)throw t[0];if(t.length>1)throw new AggregateError(t,"Encountered errors while disposing of store");return Array.isArray(e)?[]:e}if(e)return e.dispose(),e}function p(e){const t=c({dispose:(0,o.createSingleCallFunction)((()=>{d(t),e()}))});return t}t.DisposableTracker=h;class g{static{this.DISABLE_DISPOSED_WARNING=!1}constructor(){this._toDispose=new Set,this._isDisposed=!1,c(this)}dispose(){this._isDisposed||(d(this),this._isDisposed=!0,this.clear())}get isDisposed(){return this._isDisposed}clear(){if(0!==this._toDispose.size)try{f(this._toDispose)}finally{this._toDispose.clear()}}add(e){if(!e)return e;if(e===this)throw new Error("Cannot register a disposable on itself!");return u(e,this),this._isDisposed?g.DISABLE_DISPOSED_WARNING||console.warn(new Error("Trying to add a disposable to a DisposableStore that has already been disposed of. The added object will be leaked!").stack):this._toDispose.add(e),e}delete(e){if(e){if(e===this)throw new Error("Cannot dispose a disposable on itself!");this._toDispose.delete(e),e.dispose()}}deleteAndLeak(e){e&&this._toDispose.has(e)&&(this._toDispose.delete(e),u(e,null))}}t.DisposableStore=g;class m{static{this.None=Object.freeze({dispose(){}})}constructor(){this._store=new g,c(this),u(this._store,this)}dispose(){d(this),this._store.dispose()}_register(e){if(e===this)throw new Error("Cannot register a disposable on itself!");return this._store.add(e)}}t.Disposable=m;class v{constructor(){this._isDisposed=!1,c(this)}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),e&&u(e,this),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,d(this),this._value?.dispose(),this._value=void 0}clearAndLeak(){const e=this._value;return this._value=void 0,e&&u(e,null),e}}t.MutableDisposable=v,t.MandatoryMutableDisposable=class{constructor(e){this._disposable=new v,this._isDisposed=!1,this._disposable.value=e}get value(){return this._disposable.value}set value(e){this._isDisposed||e===this._disposable.value||(this._disposable.value=e)}dispose(){this._isDisposed=!0,this._disposable.dispose()}},t.RefCountedDisposable=class{constructor(e){this._disposable=e,this._counter=1}acquire(){return this._counter++,this}release(){return 0==--this._counter&&this._disposable.dispose(),this}},t.SafeDisposable=class{constructor(){this.dispose=()=>{},this.unset=()=>{},this.isset=()=>!1,c(this)}set(e){let t=e;return this.unset=()=>t=void 0,this.isset=()=>void 0!==t,this.dispose=()=>{t&&(t(),t=void 0,d(this))},this}},t.ReferenceCollection=class{constructor(){this.references=new Map}acquire(e,...t){let i=this.references.get(e);i||(i={counter:0,object:this.createReferencedObject(e,...t)},this.references.set(e,i));const{object:s}=i,r=(0,o.createSingleCallFunction)((()=>{0==--i.counter&&(this.destroyReferencedObject(e,i.object),this.references.delete(e))}));return i.counter++,{object:s,dispose:r}}},t.AsyncReferenceCollection=class{constructor(e){this.referenceCollection=e}async acquire(e,...t){const i=this.referenceCollection.acquire(e,...t);try{return{object:await i.object,dispose:()=>i.dispose()}}catch(e){throw i.dispose(),e}}},t.ImmortalReference=class{constructor(e){this.object=e}dispose(){}};class S{constructor(){this._store=new Map,this._isDisposed=!1,c(this)}dispose(){d(this),this._isDisposed=!0,this.clearAndDisposeAll()}clearAndDisposeAll(){if(this._store.size)try{f(this._store.values())}finally{this._store.clear()}}has(e){return this._store.has(e)}get size(){return this._store.size}get(e){return this._store.get(e)}set(e,t,i=!1){this._isDisposed&&console.warn(new Error("Trying to add a disposable to a DisposableMap that has already been disposed of. The added object will be leaked!").stack),i||this._store.get(e)?.dispose(),this._store.set(e,t)}deleteAndDispose(e){this._store.get(e)?.dispose(),this._store.delete(e)}deleteAndLeak(e){const t=this._store.get(e);return this._store.delete(e),t}keys(){return this._store.keys()}values(){return this._store.values()}[Symbol.iterator](){return this._store[Symbol.iterator]()}}t.DisposableMap=S},6317:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.LinkedList=void 0;class i{static{this.Undefined=new i(void 0)}constructor(e){this.element=e,this.next=i.Undefined,this.prev=i.Undefined}}class s{constructor(){this._first=i.Undefined,this._last=i.Undefined,this._size=0}get size(){return this._size}isEmpty(){return this._first===i.Undefined}clear(){let e=this._first;for(;e!==i.Undefined;){const t=e.next;e.prev=i.Undefined,e.next=i.Undefined,e=t}this._first=i.Undefined,this._last=i.Undefined,this._size=0}unshift(e){return this._insert(e,!1)}push(e){return this._insert(e,!0)}_insert(e,t){const s=new i(e);if(this._first===i.Undefined)this._first=s,this._last=s;else if(t){const e=this._last;this._last=s,s.prev=e,e.next=s}else{const e=this._first;this._first=s,s.next=e,e.prev=s}this._size+=1;let r=!1;return()=>{r||(r=!0,this._remove(s))}}shift(){if(this._first!==i.Undefined){const e=this._first.element;return this._remove(this._first),e}}pop(){if(this._last!==i.Undefined){const e=this._last.element;return this._remove(this._last),e}}_remove(e){if(e.prev!==i.Undefined&&e.next!==i.Undefined){const t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===i.Undefined&&e.next===i.Undefined?(this._first=i.Undefined,this._last=i.Undefined):e.next===i.Undefined?(this._last=this._last.prev,this._last.next=i.Undefined):e.prev===i.Undefined&&(this._first=this._first.next,this._first.prev=i.Undefined);this._size-=1}*[Symbol.iterator](){let e=this._first;for(;e!==i.Undefined;)yield e.element,e=e.next}}t.LinkedList=s},2608:(e,t)=>{var i;Object.defineProperty(t,"__esModule",{value:!0}),t.SetMap=t.BidirectionalMap=t.CounterSet=t.Touch=void 0,t.getOrSet=function(e,t,i){let s=e.get(t);return void 0===s&&(s=i,e.set(t,s)),s},t.mapToString=function(e){const t=[];return e.forEach(((e,i)=>{t.push(`${i} => ${e}`)})),`Map(${e.size}) {${t.join(", ")}}`},t.setToString=function(e){const t=[];return e.forEach((e=>{t.push(e)})),`Set(${e.size}) {${t.join(", ")}}`},t.mapsStrictEqualIgnoreOrder=function(e,t){if(e===t)return!0;if(e.size!==t.size)return!1;for(const[i,s]of e)if(!t.has(i)||t.get(i)!==s)return!1;for(const[i]of t)if(!e.has(i))return!1;return!0},function(e){e[e.None=0]="None",e[e.AsOld=1]="AsOld",e[e.AsNew=2]="AsNew"}(i||(t.Touch=i={})),t.CounterSet=class{constructor(){this.map=new Map}add(e){return this.map.set(e,(this.map.get(e)||0)+1),this}delete(e){let t=this.map.get(e)||0;return 0!==t&&(t--,0===t?this.map.delete(e):this.map.set(e,t),!0)}has(e){return this.map.has(e)}},t.BidirectionalMap=class{constructor(e){if(this._m1=new Map,this._m2=new Map,e)for(const[t,i]of e)this.set(t,i)}clear(){this._m1.clear(),this._m2.clear()}set(e,t){this._m1.set(e,t),this._m2.set(t,e)}get(e){return this._m1.get(e)}getKey(e){return this._m2.get(e)}delete(e){const t=this._m1.get(e);return void 0!==t&&(this._m1.delete(e),this._m2.delete(t),!0)}forEach(e,t){this._m1.forEach(((i,s)=>{e.call(t,i,s,this)}))}keys(){return this._m1.keys()}values(){return this._m1.values()}},t.SetMap=class{constructor(){this.map=new Map}add(e,t){let i=this.map.get(e);i||(i=new Set,this.map.set(e,i)),i.add(t)}delete(e,t){const i=this.map.get(e);i&&(i.delete(t),0===i.size&&this.map.delete(e))}forEach(e,t){const i=this.map.get(e);i&&i.forEach(t)}get(e){return this.map.get(e)||new Set}}},7704:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SlidingWindowAverage=t.MovingAverage=t.Counter=void 0,t.clamp=function(e,t,i){return Math.min(Math.max(e,t),i)},t.rot=function(e,t){return(t+e%t)%t},t.isPointWithinTriangle=function(e,t,i,s,r,n,o,a){const l=o-i,h=a-s,c=r-i,d=n-s,u=e-i,_=t-s,f=l*l+h*h,p=l*c+h*d,g=l*u+h*_,m=c*c+d*d,v=c*u+d*_,S=1/(f*m-p*p),b=(m*g-p*v)*S,C=(f*v-p*g)*S;return b>=0&&C>=0&&b+C<1},t.Counter=class{constructor(){this._next=0}getNext(){return this._next++}},t.MovingAverage=class{constructor(){this._n=1,this._val=0}update(e){return this._val=this._val+(e-this._val)/this._n,this._n+=1,this._val}get value(){return this._val}},t.SlidingWindowAverage=class{constructor(e){this._n=0,this._val=0,this._values=[],this._index=0,this._sum=0,this._values=new Array(e),this._values.fill(0,0,e)}update(e){const t=this._values[this._index];return this._values[this._index]=e,this._index=(this._index+1)%this._values.length,this._sum-=t,this._sum+=e,this._n{Object.defineProperty(t,"__esModule",{value:!0}),t.isAndroid=t.isEdge=t.isSafari=t.isFirefox=t.isChrome=t.OS=t.OperatingSystem=t.setTimeout0=t.setTimeout0IsFaster=t.translationsConfigFile=t.platformLocale=t.locale=t.Language=t.language=t.userAgent=t.platform=t.isCI=t.isMobile=t.isIOS=t.webWorkerOrigin=t.isWebWorker=t.isWeb=t.isElectron=t.isNative=t.isLinuxSnap=t.isLinux=t.isMacintosh=t.isWindows=t.Platform=t.LANGUAGE_DEFAULT=void 0,t.PlatformToString=function(e){switch(e){case C.Web:return"Web";case C.Mac:return"Mac";case C.Linux:return"Linux";case C.Windows:return"Windows"}},t.isLittleEndian=function(){if(!L){L=!0;const e=new Uint8Array(2);e[0]=1,e[1]=2;const t=new Uint16Array(e.buffer);D=513===t[0]}return D},t.isBigSurOrNewer=function(e){return parseFloat(e)>=20},t.LANGUAGE_DEFAULT="en";let i,s,r,n=!1,o=!1,a=!1,l=!1,h=!1,c=!1,d=!1,u=!1,_=!1,f=!1,p=t.LANGUAGE_DEFAULT,g=t.LANGUAGE_DEFAULT;const m=globalThis;let v;void 0!==m.vscode&&void 0!==m.vscode.process?v=m.vscode.process:"undefined"!=typeof process&&"string"==typeof process?.versions?.node&&(v=process);const S="string"==typeof v?.versions?.electron,b=S&&"renderer"===v?.type;if("object"==typeof v){n="win32"===v.platform,o="darwin"===v.platform,a="linux"===v.platform,l=a&&!!v.env.SNAP&&!!v.env.SNAP_REVISION,d=S,_=!!v.env.CI||!!v.env.BUILD_ARTIFACTSTAGINGDIRECTORY,i=t.LANGUAGE_DEFAULT,p=t.LANGUAGE_DEFAULT;const e=v.env.VSCODE_NLS_CONFIG;if(e)try{const r=JSON.parse(e);i=r.userLocale,g=r.osLocale,p=r.resolvedLanguage||t.LANGUAGE_DEFAULT,s=r.languagePack?.translationsConfigFile}catch(e){}h=!0}else"object"!=typeof navigator||b?console.error("Unable to resolve platform."):(r=navigator.userAgent,n=r.indexOf("Windows")>=0,o=r.indexOf("Macintosh")>=0,u=(r.indexOf("Macintosh")>=0||r.indexOf("iPad")>=0||r.indexOf("iPhone")>=0)&&!!navigator.maxTouchPoints&&navigator.maxTouchPoints>0,a=r.indexOf("Linux")>=0,f=r?.indexOf("Mobi")>=0,c=!0,p=globalThis._VSCODE_NLS_LANGUAGE||t.LANGUAGE_DEFAULT,i=navigator.language.toLowerCase(),g=i);var C;!function(e){e[e.Web=0]="Web",e[e.Mac=1]="Mac",e[e.Linux=2]="Linux",e[e.Windows=3]="Windows"}(C||(t.Platform=C={}));let y=C.Web;var w,E;o?y=C.Mac:n?y=C.Windows:a&&(y=C.Linux),t.isWindows=n,t.isMacintosh=o,t.isLinux=a,t.isLinuxSnap=l,t.isNative=h,t.isElectron=d,t.isWeb=c,t.isWebWorker=c&&"function"==typeof m.importScripts,t.webWorkerOrigin=t.isWebWorker?m.origin:void 0,t.isIOS=u,t.isMobile=f,t.isCI=_,t.platform=y,t.userAgent=r,t.language=p,function(e){e.value=function(){return t.language},e.isDefaultVariant=function(){return 2===t.language.length?"en"===t.language:t.language.length>=3&&"e"===t.language[0]&&"n"===t.language[1]&&"-"===t.language[2]},e.isDefault=function(){return"en"===t.language}}(w||(t.Language=w={})),t.locale=i,t.platformLocale=g,t.translationsConfigFile=s,t.setTimeout0IsFaster="function"==typeof m.postMessage&&!m.importScripts,t.setTimeout0=(()=>{if(t.setTimeout0IsFaster){const e=[];m.addEventListener("message",(t=>{if(t.data&&t.data.vscodeScheduleAsyncWork)for(let i=0,s=e.length;i{const s=++t;e.push({id:s,callback:i}),m.postMessage({vscodeScheduleAsyncWork:s},"*")}}return e=>setTimeout(e)})(),function(e){e[e.Windows=1]="Windows",e[e.Macintosh=2]="Macintosh",e[e.Linux=3]="Linux"}(E||(t.OperatingSystem=E={})),t.OS=o||u?E.Macintosh:n?E.Windows:E.Linux;let D=!0,L=!1;t.isChrome=!!(t.userAgent&&t.userAgent.indexOf("Chrome")>=0),t.isFirefox=!!(t.userAgent&&t.userAgent.indexOf("Firefox")>=0),t.isSafari=!!(!t.isChrome&&t.userAgent&&t.userAgent.indexOf("Safari")>=0),t.isEdge=!!(t.userAgent&&t.userAgent.indexOf("Edg/")>=0),t.isAndroid=!!(t.userAgent&&t.userAgent.indexOf("Android")>=0)},9881:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SmoothScrollingOperation=t.SmoothScrollingUpdate=t.Scrollable=t.ScrollState=t.ScrollbarVisibility=void 0;const s=i(802),r=i(7150);var n;!function(e){e[e.Auto=1]="Auto",e[e.Hidden=2]="Hidden",e[e.Visible=3]="Visible"}(n||(t.ScrollbarVisibility=n={}));class o{constructor(e,t,i,s,r,n,o){this._forceIntegerValues=e,this._scrollStateBrand=void 0,this._forceIntegerValues&&(t|=0,i|=0,s|=0,r|=0,n|=0,o|=0),this.rawScrollLeft=s,this.rawScrollTop=o,t<0&&(t=0),s+t>i&&(s=i-t),s<0&&(s=0),r<0&&(r=0),o+r>n&&(o=n-r),o<0&&(o=0),this.width=t,this.scrollWidth=i,this.scrollLeft=s,this.height=r,this.scrollHeight=n,this.scrollTop=o}equals(e){return this.rawScrollLeft===e.rawScrollLeft&&this.rawScrollTop===e.rawScrollTop&&this.width===e.width&&this.scrollWidth===e.scrollWidth&&this.scrollLeft===e.scrollLeft&&this.height===e.height&&this.scrollHeight===e.scrollHeight&&this.scrollTop===e.scrollTop}withScrollDimensions(e,t){return new o(this._forceIntegerValues,void 0!==e.width?e.width:this.width,void 0!==e.scrollWidth?e.scrollWidth:this.scrollWidth,t?this.rawScrollLeft:this.scrollLeft,void 0!==e.height?e.height:this.height,void 0!==e.scrollHeight?e.scrollHeight:this.scrollHeight,t?this.rawScrollTop:this.scrollTop)}withScrollPosition(e){return new o(this._forceIntegerValues,this.width,this.scrollWidth,void 0!==e.scrollLeft?e.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,void 0!==e.scrollTop?e.scrollTop:this.rawScrollTop)}createScrollEvent(e,t){const i=this.width!==e.width,s=this.scrollWidth!==e.scrollWidth,r=this.scrollLeft!==e.scrollLeft,n=this.height!==e.height,o=this.scrollHeight!==e.scrollHeight,a=this.scrollTop!==e.scrollTop;return{inSmoothScrolling:t,oldWidth:e.width,oldScrollWidth:e.scrollWidth,oldScrollLeft:e.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:e.height,oldScrollHeight:e.scrollHeight,oldScrollTop:e.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:i,scrollWidthChanged:s,scrollLeftChanged:r,heightChanged:n,scrollHeightChanged:o,scrollTopChanged:a}}}t.ScrollState=o;class a extends r.Disposable{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new s.Emitter),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new o(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){const i=this._state.withScrollDimensions(e,t);this._setState(i,Boolean(this._smoothScrolling)),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){const t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(0===this._smoothScrollDuration)return this.setScrollPositionNow(e);if(this._smoothScrolling){e={scrollLeft:void 0===e.scrollLeft?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:void 0===e.scrollTop?this._smoothScrolling.to.scrollTop:e.scrollTop};const i=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===i.scrollLeft&&this._smoothScrolling.to.scrollTop===i.scrollTop)return;let s;s=t?new c(this._smoothScrolling.from,i,this._smoothScrolling.startTime,this._smoothScrolling.duration):this._smoothScrolling.combine(this._state,i,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{const t=this._state.withScrollPosition(e);this._smoothScrolling=c.start(this._state,t,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame((()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())}))}hasPendingScrollAnimation(){return Boolean(this._smoothScrolling)}_performSmoothScrolling(){if(!this._smoothScrolling)return;const e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);return this._setState(t,!0),this._smoothScrolling?e.isDone?(this._smoothScrolling.dispose(),void(this._smoothScrolling=null)):void(this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame((()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())}))):void 0}_setState(e,t){const i=this._state;i.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(i,t)))}}t.Scrollable=a;class l{constructor(e,t,i){this.scrollLeft=e,this.scrollTop=t,this.isDone=i}}function h(e,t){const i=t-e;return function(t){return e+i*(1-(s=1-t,Math.pow(s,3)));var s}}t.SmoothScrollingUpdate=l;class c{constructor(e,t,i,s){this.from=e,this.to=t,this.duration=s,this.startTime=i,this.animationFrameDisposable=null,this._initAnimations()}_initAnimations(){this.scrollLeft=this._initAnimation(this.from.scrollLeft,this.to.scrollLeft,this.to.width),this.scrollTop=this._initAnimation(this.from.scrollTop,this.to.scrollTop,this.to.height)}_initAnimation(e,t,i){if(Math.abs(e-t)>2.5*i){let o,a;return e{Object.defineProperty(t,"__esModule",{value:!0}),t.StopWatch=void 0;const i=globalThis.performance&&"function"==typeof globalThis.performance.now;class s{static create(e){return new s(e)}constructor(e){this._now=i&&!1===e?Date.now:globalThis.performance.now.bind(globalThis.performance),this._startTime=this._now(),this._stopTime=-1}stop(){this._stopTime=this._now()}reset(){this._startTime=this._now(),this._stopTime=-1}elapsed(){return-1!==this._stopTime?this._stopTime-this._startTime:this._now()-this._startTime}}t.StopWatch=s},1316:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.noBreakWhitespace=t.CodePointIterator=void 0,t.isFalsyOrWhitespace=function(e){return!e||"string"!=typeof e||0===e.trim().length},t.format=function(e,...t){return 0===t.length?e:e.replace(n,(function(e,i){const s=parseInt(i,10);return isNaN(s)||s<0||s>=t.length?e:t[s]}))},t.format2=function(e,t){return 0===Object.keys(t).length?e:e.replace(o,((e,i)=>t[i]??e))},t.htmlAttributeEncodeValue=function(e){return e.replace(/[<>"'&]/g,(e=>{switch(e){case"<":return"<";case">":return">";case'"':return""";case"'":return"'";case"&":return"&"}return e}))},t.escape=function(e){return e.replace(/[<>&]/g,(function(e){switch(e){case"<":return"<";case">":return">";case"&":return"&";default:return e}}))},t.escapeRegExpCharacters=a,t.count=function(e,t){let i=0,s=e.indexOf(t);for(;-1!==s;)i++,s=e.indexOf(t,s+t.length);return i},t.truncate=function(e,t,i="…"){return e.length<=t?e:`${e.substr(0,t)}${i}`},t.truncateMiddle=function(e,t,i="…"){if(e.length<=t)return e;const s=Math.ceil(t/2)-i.length/2,r=Math.floor(t/2)-i.length/2;return`${e.substr(0,s)}${i}${e.substr(e.length-r)}`},t.trim=function(e,t=" "){return h(l(e,t),t)},t.ltrim=l,t.rtrim=h,t.convertSimple2RegExpPattern=function(e){return e.replace(/[\-\\\{\}\+\?\|\^\$\.\,\[\]\(\)\#\s]/g,"\\$&").replace(/[\*]/g,".*")},t.stripWildcards=function(e){return e.replace(/\*/g,"")},t.createRegExp=function(e,t,i={}){if(!e)throw new Error("Cannot create regex from empty string");t||(e=a(e)),i.wholeWord&&(/\B/.test(e.charAt(0))||(e="\\b"+e),/\B/.test(e.charAt(e.length-1))||(e+="\\b"));let s="";return i.global&&(s+="g"),i.matchCase||(s+="i"),i.multiline&&(s+="m"),i.unicode&&(s+="u"),new RegExp(e,s)},t.regExpLeadsToEndlessLoop=function(e){return"^"!==e.source&&"^$"!==e.source&&"$"!==e.source&&"^\\s*$"!==e.source&&!(!e.exec("")||0!==e.lastIndex)},t.splitLines=function(e){return e.split(/\r\n|\r|\n/)},t.splitLinesIncludeSeparators=function(e){const t=[],i=e.split(/(\r\n|\r|\n)/);for(let e=0;e=0;i--){const t=e.charCodeAt(i);if(t!==s.CharCode.Space&&t!==s.CharCode.Tab)return i}return-1},t.replaceAsync=function(e,t,i){const s=[];let r=0;for(const n of e.matchAll(t)){if(s.push(e.slice(r,n.index)),void 0===n.index)throw new Error("match.index should be defined");r=n.index+n[0].length,s.push(i(n[0],...n.slice(1),n.index,e,n.groups))}return s.push(e.slice(r)),Promise.all(s).then((e=>e.join("")))},t.compare=function(e,t){return et?1:0},t.compareSubstring=c,t.compareIgnoreCase=function(e,t){return d(e,t,0,e.length,0,t.length)},t.compareSubstringIgnoreCase=d,t.isAsciiDigit=function(e){return e>=s.CharCode.Digit0&&e<=s.CharCode.Digit9},t.isLowerAsciiLetter=u,t.isUpperAsciiLetter=function(e){return e>=s.CharCode.A&&e<=s.CharCode.Z},t.equalsIgnoreCase=function(e,t){return e.length===t.length&&0===d(e,t)},t.startsWithIgnoreCase=function(e,t){const i=t.length;return!(t.length>e.length)&&0===d(e,t,0,i)},t.commonPrefixLength=function(e,t){const i=Math.min(e.length,t.length);let s;for(s=0;sn)return 1}const o=s-i,a=n-r;return oa?1:0}function d(e,t,i=0,s=e.length,r=0,n=t.length){for(;i=128||a>=128)return c(e.toLowerCase(),t.toLowerCase(),i,s,r,n);u(o)&&(o-=32),u(a)&&(a-=32);const l=o-a;if(0!==l)return l}const o=s-i,a=n-r;return oa?1:0}function u(e){return e>=s.CharCode.a&&e<=s.CharCode.z}function _(e){return 55296<=e&&e<=56319}function f(e){return 56320<=e&&e<=57343}function p(e,t){return t-56320+(e-55296<<10)+65536}function g(e,t,i){const s=e.charCodeAt(i);if(_(s)&&i+11){const s=e.charCodeAt(t-2);if(_(s))return p(s,i)}return i}(this._str,this._offset);return this._offset-=e>=r.Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN?2:1,e}nextCodePoint(){const e=g(this._str,this._len,this._offset);return this._offset+=e>=r.Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN?2:1,e}eol(){return this._offset>=this._len}},t.noBreakWhitespace=" "},5015:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.MicrotaskDelay=void 0,t.MicrotaskDelay=Symbol("MicrotaskDelay")},8960:(e,t)=>{var i;Object.defineProperty(t,"__esModule",{value:!0}),t.Constants=void 0,t.toUint8=function(e){return e<0?0:e>i.MAX_UINT_8?i.MAX_UINT_8:0|e},t.toUint32=function(e){return e<0?0:e>i.MAX_UINT_32?i.MAX_UINT_32:0|e},function(e){e[e.MAX_SAFE_SMALL_INTEGER=1073741824]="MAX_SAFE_SMALL_INTEGER",e[e.MIN_SAFE_SMALL_INTEGER=-1073741824]="MIN_SAFE_SMALL_INTEGER",e[e.MAX_UINT_8=255]="MAX_UINT_8",e[e.MAX_UINT_16=65535]="MAX_UINT_16",e[e.MAX_UINT_32=4294967295]="MAX_UINT_32",e[e.UNICODE_SUPPLEMENTARY_PLANE_BEGIN=65536]="UNICODE_SUPPLEMENTARY_PLANE_BEGIN"}(i||(t.Constants=i={}))}},t={};function i(s){var r=t[s];if(void 0!==r)return r.exports;var n=t[s]={exports:{}};return e[s].call(n.exports,n,n.exports,i),n.exports}var s={};return(()=>{var e=s;Object.defineProperty(e,"__esModule",{value:!0}),e.Terminal=void 0;const t=i(7721),r=i(1718),n=i(7150),o=i(3027),a=i(5101),l=i(6097),h=i(4335),c=["cols","rows"];let d=0;class u extends n.Disposable{constructor(e){super(),this._core=this._register(new r.CoreBrowserTerminal(e)),this._addonManager=this._register(new o.AddonManager),this._publicOptions={...this._core.options};const t=e=>this._core.options[e],i=(e,t)=>{this._checkReadonlyOptions(e),this._core.options[e]=t};for(const e in this._core.options){const s={get:t.bind(this,e),set:i.bind(this,e)};Object.defineProperty(this._publicOptions,e,s)}}_checkReadonlyOptions(e){if(c.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get element(){return this._core.element}get parser(){return this._parser||(this._parser=new l.ParserApi(this._core)),this._parser}get unicode(){return this._checkProposedApi(),new h.UnicodeApi(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer||(this._buffer=this._register(new a.BufferNamespaceApi(this._core))),this._buffer}get markers(){return this._checkProposedApi(),this._core.markers}get modes(){const e=this._core.coreService.decPrivateModes;let t="none";switch(this._core.coreMouseService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any"}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,synchronizedOutputMode:e.synchronizedOutput,wraparoundMode:e.wraparound}}get options(){return this._publicOptions}set options(e){for(const t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._checkProposedApi(),this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._checkProposedApi(),this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._checkProposedApi(),this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write("\r\n",t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return t.promptLabel.get()},set promptLabel(e){t.promptLabel.set(e)},get tooMuchOutput(){return t.tooMuchOutput.get()},set tooMuchOutput(e){t.tooMuchOutput.set(e)}}}_verifyIntegers(...e){for(d of e)if(d===1/0||isNaN(d)||d%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(d of e)if(d&&(d===1/0||isNaN(d)||d%1!=0||d<0))throw new Error("This API only accepts positive integers")}}e.Terminal=u})(),s})())); -//# sourceMappingURL=xterm.js.map \ No newline at end of file diff --git a/frontend/src/js/notifications.js b/frontend/src/js/notifications.js deleted file mode 100644 index 34a417e..0000000 --- a/frontend/src/js/notifications.js +++ /dev/null @@ -1,101 +0,0 @@ -(function() { -/** - * Alchementary Link — toast notification system. - * Provides non-blocking, auto-dismissing notifications that stack - * in the top-right corner of the viewport. - */ - -const { createElement, escapeHtml } = window.AlchementaryLink; - -/** Small SVG icons for each notification type (16×16 viewBox). */ -const ICONS = { - success: '✓', - error: '✕', - warning: '!', - info: 'i', -}; - -class Notifications { - constructor() { - /** @type {HTMLElement} */ - this.container = document.getElementById('notifications'); - if (!this.container) { - this.container = createElement('div', 'notifications-container', { id: 'notifications' }); - document.body.appendChild(this.container); - } - } - - /** - * Displays a notification. - * @param {string} message - Notification text. - * @param {'info'|'success'|'warning'|'error'} [type='info'] - Severity. - * @param {number} [duration=4000] - Auto-dismiss delay in ms. - */ - show(message, type = 'info', duration = 4000) { - const el = createElement('div', `notification notification-${type}`); - - // Icon - const icon = createElement('span', 'notification-icon', { - textContent: ICONS[type] || ICONS.info, - }); - el.appendChild(icon); - - // Body - const body = createElement('div', 'notification-body'); - const msg = createElement('span', 'notification-message', { textContent: message }); - body.appendChild(msg); - el.appendChild(body); - - // Dismiss button - const close = createElement('button', 'notification-close', { - textContent: '×', - 'aria-label': 'Dismiss', - }); - close.addEventListener('click', () => this._dismiss(el)); - el.appendChild(close); - - // Insert at the top of the stack - this.container.prepend(el); - - // Auto-dismiss - if (duration > 0) { - el._timer = setTimeout(() => this._dismiss(el), duration); - } - } - - /** @param {string} message */ - success(message) { this.show(message, 'success'); } - - /** @param {string} message */ - error(message) { this.show(message, 'error', 6000); } - - /** @param {string} message */ - warning(message) { this.show(message, 'warning'); } - - /** @param {string} message */ - info(message) { this.show(message, 'info'); } - - /** - * Animates out and removes a notification element. - * @param {HTMLElement} el - * @private - */ - _dismiss(el) { - if (el._dismissed) return; - el._dismissed = true; - clearTimeout(el._timer); - - el.classList.add('is-leaving'); - el.addEventListener('animationend', () => el.remove(), { once: true }); - - // Safety net — remove after 500 ms even if animation event does not fire - setTimeout(() => { - if (el.parentNode) el.remove(); - }, 500); - } -} - -window.AlchementaryLink = window.AlchementaryLink || {}; -window.AlchementaryLink.Notifications = Notifications; - -})(); diff --git a/frontend/src/js/profiles.js b/frontend/src/js/profiles.js deleted file mode 100644 index 679531f..0000000 --- a/frontend/src/js/profiles.js +++ /dev/null @@ -1,2769 +0,0 @@ -(function () { - /** - * Alchementary Link — connection profile editor. - * Renders a slide-out drawer for creating and editing connection profiles. - * Dynamically switches form sections based on the selected protocol. - */ - - const { createElement, escapeHtml, generateId, $ } = window.AlchementaryLink; - - /** - * Default values mirroring the Go backend defaults. - */ - const DEFAULTS = { - ssh: { - port: 22, - protocol: "ssh", - ssh: { - authMethods: ["publickey", "password", "keyboard-interactive"], - secretMode: "saved", - keySecretMode: "saved", - hostKeyPolicy: "ask", - terminalType: "xterm-256color", - keepaliveInterval: 30, - keepaliveCountMax: 3, - compression: false, - jumpHosts: [], - portForwards: [], - startupCommand: "", - envVars: {}, - loginScripts: [], - }, - }, - telnet: { - port: 23, - protocol: "telnet", - telnet: { - localEcho: false, - lineEnding: "crlf", - terminalType: "xterm-256color", - encoding: "utf-8", - negotiationDebug: false, - lineMode: false, - loginScripts: [], - }, - }, - raw: { - port: 23, - protocol: "raw", - raw: { - autoReconnect: false, - lineEnding: "crlf", - encoding: "utf-8", - showTimestamps: false, - showDirection: false, - payloadPresets: [], - }, - }, - github: { - port: 443, - protocol: "github", - host: "github.com", - github: { - baseURL: "https://api.github.com", - secretMode: "saved", - refreshIntervalSecs: 300, - }, - }, - vnc: { - port: 5900, - protocol: "vnc", - vnc: { - vncHost: "", - vncPort: 5900, - secretMode: "prompt", - viewOnly: false, - scaleToFit: true, - quality: 6, - sharedSession: true, - sshTunnel: false, - sshHost: "", - sshPort: 22, - sshUsername: "", - sshAuthMethod: "publickey", - sshSecretMode: "saved", - sshPrivateKey: "", - sshAgentAuth: false, - }, - }, - irc: { - port: 6667, - protocol: "irc", - irc: { - nick: "link", - realName: "Alchementary Link", - useTLS: false, - serverPasswordRequired: false, - serverPasswordMode: "ask-every-time", - nickServIdentify: false, - nickServAccount: "", - nickServMode: "saved", - autoJoinRooms: [], - sshTunnel: false, - sshHost: "", - sshPort: 22, - sshUsername: "", - sshAuthMethod: "publickey", - sshSecretMode: "saved", - sshPrivateKey: "", - sshAgentAuth: false, - autoReconnect: true, - maxReconnectTries: 5, - }, - }, - }; - - class ProfileEditor { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - - /** @type {HTMLElement|null} */ - this.drawer = null; - - /** @type {Object|null} The profile currently being edited. */ - this._profile = null; - - /** @type {boolean} */ - this._isNew = false; - - /** @type {string[]} */ - this._sshKeyOptions = []; - - /** @type {boolean} */ - this._sshKeyOptionsLoaded = false; - - /** @type {boolean} */ - this._sshKeyOptionsLoading = false; - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Opens the editor with a blank profile of the given protocol. - * @param {string} [protocol='ssh'] - */ - openNew(protocol = "ssh") { - const profile = this._defaultProfile(protocol); - this._isNew = true; - this._profile = profile; - this.render(profile, true); - } - - /** - * Opens the editor pre-populated with an existing profile. - * @param {string} profileId - */ - openEdit(profileId) { - const profile = this.app.state.getProfile(profileId); - if (!profile) { - this.app.notifications.error("Profile not found"); - return; - } - this._isNew = false; - this._profile = JSON.parse(JSON.stringify(profile)); // deep clone - this.render(this._profile, false); - } - - /** - * Renders (or re-renders) the editor drawer. - * @param {Object} profile - * @param {boolean} isNew - */ - render(profile, isNew) { - this.close(); - - const overlay = createElement("div", "drawer-backdrop"); - const drawer = createElement("div", "drawer"); - - // ── Header ────────────────────────────────────────────── - const header = createElement("div", "drawer-header"); - header.innerHTML = ` -

${isNew ? "New Connection" : "Edit Connection"}

- - `; - drawer.appendChild(header); - - // ── Protocol selector tabs ────────────────────────────── - const protocols = ["ssh", "telnet", "raw", "github", "vnc", "irc"]; - const tabBar = createElement("div", "drawer-protocol-tabs"); - for (const proto of protocols) { - const label = - proto === "github" ? "GitHub" : proto === "irc" ? "IRC" : proto.toUpperCase(); - const btn = createElement( - "button", - `drawer-proto-tab ${profile.protocol === proto ? "active" : ""}`, - { - textContent: label, - dataset: { proto }, - }, - ); - btn.addEventListener("click", () => this.handleProtocolChange(proto)); - tabBar.appendChild(btn); - } - drawer.appendChild(tabBar); - - // ── Scrollable body ───────────────────────────────────── - const body = createElement("div", "drawer-body"); - const sshKeyDatalist = this._buildSSHKeyDatalist(); - - // General section (common to all protocols) - body.appendChild(this._renderGeneralSection(profile)); - - // Protocol-specific sections - switch (profile.protocol) { - case "ssh": - this._renderSSHSections(profile).forEach((s) => body.appendChild(s)); - body.appendChild(sshKeyDatalist); - break; - case "telnet": - this._renderTelnetSections(profile).forEach((s) => - body.appendChild(s), - ); - break; - case "raw": - this._renderRAWSections(profile).forEach((s) => body.appendChild(s)); - break; - case "github": - this._renderGitHubSections(profile).forEach((s) => - body.appendChild(s), - ); - break; - case "vnc": - this._renderVNCSections(profile).forEach((s) => body.appendChild(s)); - break; - case "irc": - this._renderIRCSections(profile).forEach((s) => body.appendChild(s)); - body.appendChild(sshKeyDatalist); - break; - } - - // Terminal section (SSH and Telnet only) - if ( - profile.protocol !== "raw" && - profile.protocol !== "github" && - profile.protocol !== "vnc" - ) { - body.appendChild(this._renderTerminalSection(profile)); - } - - // Notes & Tags section (common) - body.appendChild(this._renderNotesSection(profile)); - - drawer.appendChild(body); - - // ── Footer ────────────────────────────────────────────── - const footer = createElement("div", "drawer-footer"); - const cancelBtn = createElement("button", "btn btn-secondary", { - textContent: "Cancel", - }); - const testBtn = createElement("button", "btn btn-outline", { - textContent: "Test", - }); - const saveBtn = createElement("button", "btn btn-primary", { - textContent: isNew ? "Create" : "Save", - }); - const connectBtn = createElement("button", "btn btn-accent", { - textContent: isNew ? "Create & Connect" : "Save & Connect", - }); - const deleteBtn = !isNew - ? createElement("button", "btn btn-danger", { textContent: "Delete" }) - : null; - - cancelBtn.addEventListener("click", () => this.close()); - testBtn.addEventListener("click", () => this._testConnection()); - saveBtn.addEventListener("click", () => this._handleSave(false)); - connectBtn.addEventListener("click", () => this._handleSave(true)); - if (deleteBtn) { - deleteBtn.addEventListener("click", () => this._handleDelete()); - } - - if (deleteBtn) footer.appendChild(deleteBtn); - footer.appendChild(cancelBtn); - footer.appendChild(testBtn); - footer.appendChild(saveBtn); - footer.appendChild(connectBtn); - drawer.appendChild(footer); - - // ── Events ────────────────────────────────────────────── - header - .querySelector(".drawer-close") - .addEventListener("click", () => this.close()); - overlay.addEventListener("click", () => this.close()); - - overlay.appendChild(drawer); - document.body.appendChild(overlay); - this.drawer = overlay; - - // Trigger visibility after append so CSS transitions animate - requestAnimationFrame(() => { - overlay.classList.add("is-visible"); - drawer.classList.add("is-visible"); - }); - - // Prevent overlay click from reaching through the drawer - drawer.addEventListener("click", (e) => e.stopPropagation()); - - // Focus the first input - const firstInput = drawer.querySelector("input, select, textarea"); - if (firstInput) firstInput.focus(); - - this._loadSSHKeyOptions(); - } - - /** - * Closes the editor drawer. - */ - close() { - if (this.drawer) { - this.drawer.remove(); - this.drawer = null; - } - } - - /** - * Handles protocol change from the tab bar. - * @param {string} protocol - */ - handleProtocolChange(protocol) { - if (this._profile.protocol === protocol) return; - - // Preserve common fields, reset protocol-specific ones - this._collectFormValues(); - const base = this._defaultProfile(protocol); - base.id = this._profile.id; - base.displayName = this._profile.displayName; - base.description = this._profile.description; - base.host = this._profile.host; - base.username = this._profile.username; - base.tags = this._profile.tags; - base.notes = this._profile.notes; - base.favourite = this._profile.favourite; - - this._profile = base; - this.render(this._profile, this._isNew); - } - - // ── Section renderers ──────────────────────────────────────────── - - /** @private */ - _renderGeneralSection(profile) { - const fields = [ - this._field( - "text", - "displayName", - "Display Name", - profile.displayName, - { placeholder: "My Server" }, - ), - this._field("text", "host", "Host", profile.host, { - placeholder: - profile.protocol === "github" - ? "github.com" - : "192.168.1.1 or example.com", - required: true, - }), - ]; - - // Port and username not relevant for GitHub connections - if (profile.protocol !== "github") { - fields.push( - this._field("number", "port", "Port", profile.port, { - min: 1, - max: 65535, - required: true, - }), - ); - fields.push( - this._field("text", "username", "Username", profile.username, { - placeholder: "root", - }), - ); - } - - fields.push(this._renderGroupField(profile)); - fields.push( - this._field("text", "description", "Description", profile.description, { - placeholder: "Optional description", - }), - ); - - return this._renderSection("General", fields); - } - - /** - * Renders a combo-box for the Group field — a text input backed by a - * datalist of existing group names. Typing a new name creates a group. - * @param {Object} profile - * @returns {HTMLElement} - * @private - */ - _renderGroupField(profile) { - const group = createElement("div", "form-group"); - const label = createElement("label", "form-label", { - textContent: "Group", - for: "field-folder", - }); - group.appendChild(label); - - // Collect distinct group names from all profiles - const existingGroups = [ - ...new Set( - this.app.state.profiles - .map((p) => (p.folder || "").trim()) - .filter(Boolean), - ), - ].sort((a, b) => a.localeCompare(b)); - - // Create the datalist so the browser offers autocomplete - const listId = "folder-suggestions"; - const datalist = document.createElement("datalist"); - datalist.id = listId; - for (const g of existingGroups) { - const opt = document.createElement("option"); - opt.value = g; - datalist.appendChild(opt); - } - group.appendChild(datalist); - - const input = createElement("input", "form-input", { - type: "text", - id: "field-folder", - name: "folder", - value: profile.folder || "", - placeholder: "e.g. Production, Staging, Home Lab", - }); - input.setAttribute("list", listId); - input.setAttribute("autocomplete", "off"); - group.appendChild(input); - - const hint = createElement("span", "form-hint", { - textContent: - "Choose an existing group or type a new name to create one", - }); - group.appendChild(hint); - - return group; - } - - _buildSSHKeyDatalist() { - const list = createElement("datalist", "", { - id: "ssh-key-path-options", - }); - this._applySSHKeyOptionsToList(list); - return list; - } - - _applySSHKeyOptionsToList(listEl) { - if (!listEl) return; - listEl.innerHTML = ""; - for (const keyPath of this._sshKeyOptions) { - const opt = createElement("option", "", { value: keyPath }); - listEl.appendChild(opt); - } - } - - _loadSSHKeyOptions() { - if (this._sshKeyOptionsLoading || this._sshKeyOptionsLoaded) return; - if (!window.AlchementaryLink?.wailsService) return; - - this._sshKeyOptionsLoading = true; - window.AlchementaryLink.wailsService.ListSSHKeys() - .then((keys) => { - this._sshKeyOptions = Array.isArray(keys) ? keys : []; - this._sshKeyOptionsLoaded = true; - this._sshKeyOptionsLoading = false; - if (!this.drawer) return; - const listEl = this.drawer.querySelector("#ssh-key-path-options"); - this._applySSHKeyOptionsToList(listEl); - }) - .catch(() => { - this._sshKeyOptionsLoading = false; - }); - } - - /** @private */ - _renderSSHSections(profile) { - const ssh = profile.ssh || {}; - const sections = []; - - // Authentication - sections.push( - this._renderSection("Authentication", [ - this._field( - "select", - "ssh.secretMode", - "Password Mode", - ssh.secretMode, - { - options: [ - { value: "saved", label: "Saved in vault" }, - { value: "session-only", label: "Session only" }, - { value: "ask-every-time", label: "Ask every time" }, - ], - }, - ), - this._field( - "tags", - "ssh.authMethods", - "Auth Methods", - (ssh.authMethods || []).join(", "), - { - help: "Comma-separated: publickey, password, keyboard-interactive", - }, - ), - ]), - ); - - // Key & Passphrase - sections.push( - this._renderSection("Key & Passphrase", [ - this._renderSSHKeyPathField(ssh.privateKeyPath), - this._field( - "select", - "ssh.keySecretMode", - "Passphrase Mode", - ssh.keySecretMode, - { - options: [ - { value: "saved", label: "Saved in vault" }, - { value: "session-only", label: "Session only" }, - { value: "ask-every-time", label: "Ask every time" }, - ], - }, - ), - ]), - ); - - // Host Verification - sections.push( - this._renderSection("Host Verification", [ - this._field( - "select", - "ssh.hostKeyPolicy", - "Host Key Policy", - ssh.hostKeyPolicy, - { - options: [ - { value: "strict", label: "Strict (reject unknown)" }, - { value: "ask", label: "Ask (prompt on unknown)" }, - { value: "accept-new", label: "Accept new keys" }, - { value: "insecure", label: "Insecure (accept all)" }, - ], - }, - ), - ]), - ); - - // Jump Hosts - sections.push( - this._renderSection( - "Jump Hosts", - [this._renderJumpHostsList(ssh.jumpHosts || [])], - true, - ), - ); - - // Port Forwarding - sections.push( - this._renderSection( - "Port Forwarding", - [this._renderPortForwardsList(ssh.portForwards || [])], - true, - ), - ); - - // Startup - sections.push( - this._renderSection("Startup", [ - this._field( - "text", - "ssh.startupCommand", - "Startup Command", - ssh.startupCommand, - { - placeholder: "Optional command to run after connecting", - }, - ), - this._field( - "textarea", - "ssh.envVarsText", - "Environment Variables", - this._envVarsToText(ssh.envVars), - { - placeholder: "KEY=VALUE (one per line)", - rows: 3, - }, - ), - ]), - ); - - // Login Scripts (expect-style) - sections.push( - this._renderLoginScriptSection("ssh", ssh.loginScripts || []), - ); - - // SSH Agent - sections.push( - this._renderSection( - "SSH Agent", - [ - this._field( - "toggle", - "ssh.agentAuth", - "Use SSH Agent for Authentication", - ssh.agentAuth, - { - help: "Authenticate using keys from the local SSH agent (ssh-agent)", - }, - ), - this._field( - "toggle", - "ssh.agentForwarding", - "Forward Agent to Remote", - ssh.agentForwarding, - { - help: "Allow the remote host to use your local SSH agent keys (equivalent to ssh -A)", - }, - ), - ], - true, - ), - ); - - // TOTP / 2FA - sections.push(this._renderTOTPSection(profile)); - - // Proxy - sections.push( - this._renderSection( - "Proxy", - [ - this._field( - "select", - "ssh.proxyType", - "Proxy Type", - ssh.proxyType || "none", - { - options: [ - { value: "none", label: "None (direct connection)" }, - { value: "socks5", label: "SOCKS5" }, - { value: "http", label: "HTTP CONNECT" }, - ], - }, - ), - this._field( - "text", - "ssh.proxyHost", - "Proxy Host", - ssh.proxyHost || "", - { - placeholder: "proxy.example.com", - }, - ), - this._field( - "number", - "ssh.proxyPort", - "Proxy Port", - ssh.proxyPort || 1080, - { min: 1, max: 65535 }, - ), - this._field( - "text", - "ssh.proxyUsername", - "Proxy Username", - ssh.proxyUsername || "", - { - placeholder: "Optional", - }, - ), - this._field( - "text", - "ssh.proxyPassword", - "Proxy Password", - ssh.proxyPassword || "", - { - placeholder: "Optional", - }, - ), - ], - true, - ), - ); - - // Advanced - sections.push( - this._renderSection( - "Advanced", - [ - this._field( - "text", - "ssh.terminalType", - "Terminal Type", - ssh.terminalType, - { placeholder: "xterm-256color" }, - ), - this._field( - "number", - "ssh.keepaliveInterval", - "Keepalive Interval (s)", - ssh.keepaliveInterval, - { min: 0 }, - ), - this._field( - "number", - "ssh.keepaliveCountMax", - "Max Missed Keepalives", - ssh.keepaliveCountMax, - { min: 0 }, - ), - this._field( - "toggle", - "ssh.compression", - "Compression", - ssh.compression, - ), - this._field( - "toggle", - "ssh.autoReconnect", - "Auto-Reconnect", - ssh.autoReconnect, - { - help: "Automatically reconnect with exponential backoff when the connection drops", - }, - ), - this._field( - "number", - "ssh.maxReconnectTries", - "Max Reconnect Attempts", - ssh.maxReconnectTries || 5, - { min: 1, max: 20 }, - ), - ], - true, - ), - ); - - return sections; - } - - /** @private */ - _renderTelnetSections(profile) { - const tel = profile.telnet || {}; - const sections = []; - - sections.push( - this._renderSection("Login", [ - this._field( - "toggle", - "telnet.localEcho", - "Local Echo", - tel.localEcho, - ), - this._field("toggle", "telnet.lineMode", "Line Mode", tel.lineMode), - ]), - ); - - sections.push( - this._renderSection("Terminal & Encoding", [ - this._field( - "text", - "telnet.terminalType", - "Terminal Type", - tel.terminalType, - { placeholder: "xterm-256color" }, - ), - this._field("select", "telnet.encoding", "Encoding", tel.encoding, { - options: [ - { value: "utf-8", label: "UTF-8" }, - { value: "ascii", label: "ASCII" }, - { value: "iso-8859-1", label: "ISO 8859-1" }, - ], - }), - ]), - ); - - sections.push( - this._renderSection("Echo & Line Endings", [ - this._field( - "select", - "telnet.lineEnding", - "Line Ending", - tel.lineEnding, - { - options: [ - { value: "crlf", label: "CR+LF (\\r\\n)" }, - { value: "lf", label: "LF (\\n)" }, - { value: "cr", label: "CR (\\r)" }, - { value: "none", label: "None" }, - ], - }, - ), - ]), - ); - - sections.push( - this._renderSection( - "Negotiation", - [ - this._field( - "toggle", - "telnet.negotiationDebug", - "Debug Negotiation", - tel.negotiationDebug, - ), - ], - true, - ), - ); - - // Login Scripts (expect-style) - sections.push( - this._renderLoginScriptSection("telnet", tel.loginScripts || []), - ); - - sections.push( - this._renderSection( - "Reconnection", - [ - this._field( - "toggle", - "telnet.autoReconnect", - "Auto-Reconnect", - tel.autoReconnect, - { - help: "Automatically reconnect with exponential backoff when the connection drops", - }, - ), - this._field( - "number", - "telnet.maxReconnectTries", - "Max Reconnect Attempts", - tel.maxReconnectTries || 5, - { min: 1, max: 20 }, - ), - ], - true, - ), - ); - - return sections; - } - - /** @private */ - _renderRAWSections(profile) { - const raw = profile.raw || {}; - const sections = []; - - sections.push( - this._renderSection("Encoding & Framing", [ - this._field("select", "raw.encoding", "Encoding", raw.encoding, { - options: [ - { value: "utf-8", label: "UTF-8" }, - { value: "ascii", label: "ASCII" }, - { value: "hex", label: "Hex" }, - ], - }), - this._field( - "select", - "raw.lineEnding", - "Line Ending", - raw.lineEnding, - { - options: [ - { value: "crlf", label: "CR+LF (\\r\\n)" }, - { value: "lf", label: "LF (\\n)" }, - { value: "cr", label: "CR (\\r)" }, - { value: "none", label: "None" }, - ], - }, - ), - ]), - ); - - sections.push( - this._renderSection("Display", [ - this._field( - "toggle", - "raw.showTimestamps", - "Show Timestamps", - raw.showTimestamps, - ), - this._field( - "toggle", - "raw.showDirection", - "Show Direction", - raw.showDirection, - ), - ]), - ); - - sections.push( - this._renderSection( - "Payload Presets", - [this._renderPayloadPresetsList(raw.payloadPresets || [])], - true, - ), - ); - - sections.push( - this._renderSection("Timeouts & Reconnect", [ - this._field( - "number", - "connectTimeout", - "Connect Timeout (s)", - profile.connectTimeout, - { min: 0 }, - ), - this._field( - "number", - "readTimeout", - "Read Timeout (s)", - profile.readTimeout, - { min: 0 }, - ), - this._field( - "toggle", - "raw.autoReconnect", - "Auto-reconnect", - raw.autoReconnect, - ), - ]), - ); - - return sections; - } - - /** @private */ - _renderGitHubSections(profile) { - const sections = []; - const gh = profile.github || DEFAULTS.github.github; - - sections.push( - this._renderSection("API", [ - this._field("text", "github.baseURL", "API Base URL", gh.baseURL, { - placeholder: "https://api.github.com", - }), - this._field( - "select", - "github.secretMode", - "Token Storage", - gh.secretMode, - { - options: [ - { value: "saved", label: "Save to keychain" }, - { value: "session-only", label: "Remember for session" }, - { value: "ask-every-time", label: "Ask every time" }, - ], - }, - ), - ]), - ); - - sections.push( - this._renderSection("Refresh", [ - this._field( - "number", - "github.refreshIntervalSecs", - "Refresh Interval (seconds)", - gh.refreshIntervalSecs, - { - min: 60, - max: 3600, - }, - ), - ]), - ); - - return sections; - } - - /** @private */ - _renderVNCSections(profile) { - const sections = []; - const vnc = profile.vnc || DEFAULTS.vnc.vnc; - - // VNC connection settings - sections.push( - this._renderSection("VNC Server", [ - this._field("text", "vnc.vncHost", "VNC Host", vnc.vncHost, { - placeholder: "192.168.1.100", - }), - this._field("number", "vnc.vncPort", "VNC Port", vnc.vncPort, { - min: 1, - max: 65535, - }), - this._field( - "select", - "vnc.secretMode", - "Password Storage", - vnc.secretMode, - { - options: [ - { value: "saved", label: "Save to keychain" }, - { value: "session-only", label: "Remember for session" }, - { value: "prompt", label: "Ask every time" }, - ], - }, - ), - ]), - ); - - // Display settings - sections.push( - this._renderSection("Display", [ - this._field("checkbox", "vnc.viewOnly", "View Only", vnc.viewOnly), - this._field( - "checkbox", - "vnc.scaleToFit", - "Scale to Fit", - vnc.scaleToFit, - ), - this._field( - "checkbox", - "vnc.sharedSession", - "Shared Session", - vnc.sharedSession, - ), - this._field("number", "vnc.quality", "Quality (1-9)", vnc.quality, { - min: 1, - max: 9, - }), - ]), - ); - - // SSH tunnel section - sections.push( - this._renderSection("SSH Tunnel", [ - this._field( - "checkbox", - "vnc.sshTunnel", - "Enable SSH Tunnel", - vnc.sshTunnel, - ), - this._field("text", "vnc.sshHost", "SSH Host", vnc.sshHost, { - placeholder: "jump.example.com", - }), - this._field("number", "vnc.sshPort", "SSH Port", vnc.sshPort, { - min: 1, - max: 65535, - }), - this._field( - "text", - "vnc.sshUsername", - "SSH Username", - vnc.sshUsername, - { - placeholder: "admin", - }, - ), - this._field( - "select", - "vnc.sshAuthMethod", - "SSH Auth Method", - vnc.sshAuthMethod, - { - options: [ - { value: "publickey", label: "Public Key" }, - { value: "password", label: "Password" }, - ], - }, - ), - this._field( - "text", - "vnc.sshPrivateKey", - "SSH Private Key Path", - vnc.sshPrivateKey, - { - placeholder: "~/.ssh/id_ed25519", - }, - ), - this._field( - "checkbox", - "vnc.sshAgentAuth", - "Use SSH Agent", - vnc.sshAgentAuth, - ), - this._field( - "select", - "vnc.sshSecretMode", - "SSH Secret Storage", - vnc.sshSecretMode, - { - options: [ - { value: "saved", label: "Save to keychain" }, - { value: "session-only", label: "Remember for session" }, - { value: "prompt", label: "Ask every time" }, - ], - }, - ), - ]), - ); - - return sections; - } - - /** @private */ - _renderIRCSections(profile) { - const sections = []; - const irc = profile.irc || DEFAULTS.irc.irc; - - sections.push( - this._renderSection("IRC", [ - this._field("text", "irc.nick", "Nick", irc.nick, { - placeholder: "yourNick", - }), - this._field("text", "irc.realName", "Real Name", irc.realName, { - placeholder: "Shown in WHOIS", - }), - this._field("toggle", "irc.useTLS", "Use TLS", irc.useTLS, { - help: "Use port 6697 for most TLS IRC networks.", - }), - this._field( - "toggle", - "irc.serverPasswordRequired", - "Server Password", - irc.serverPasswordRequired, - { - help: "Prompt for an IRC PASS secret before registration.", - }, - ), - this._field( - "select", - "irc.serverPasswordMode", - "Server Password Storage", - irc.serverPasswordMode, - { - options: [ - { value: "saved", label: "Save to keychain" }, - { value: "session-only", label: "Remember for session" }, - { value: "ask-every-time", label: "Ask every time" }, - ], - }, - ), - ]), - ); - - sections.push( - this._renderSection("NickServ", [ - this._field( - "toggle", - "irc.nickServIdentify", - "Identify on Connect", - irc.nickServIdentify, - ), - this._field( - "text", - "irc.nickServAccount", - "Account", - irc.nickServAccount, - { - placeholder: "Optional if it matches your nick", - }, - ), - this._field( - "select", - "irc.nickServMode", - "Password Storage", - irc.nickServMode, - { - options: [ - { value: "saved", label: "Save to keychain" }, - { value: "session-only", label: "Remember for session" }, - { value: "ask-every-time", label: "Ask every time" }, - ], - }, - ), - ]), - ); - - sections.push( - this._renderSection("Rooms", [ - this._field( - "textarea", - "irc.autoJoinRoomsText", - "Auto-Join Rooms", - this._roomsToText(irc.autoJoinRooms), - { - placeholder: "#general\n#ops", - rows: 4, - help: "One room per line or comma-separated.", - }, - ), - ]), - ); - - sections.push( - this._renderSection("SSH Tunnel", [ - this._field( - "toggle", - "irc.sshTunnel", - "Enable SSH Tunnel", - irc.sshTunnel, - ), - this._field("text", "irc.sshHost", "SSH Host", irc.sshHost, { - placeholder: "jump.example.com", - }), - this._field("number", "irc.sshPort", "SSH Port", irc.sshPort, { - min: 1, - max: 65535, - }), - this._field( - "text", - "irc.sshUsername", - "SSH Username", - irc.sshUsername, - { - placeholder: "admin", - }, - ), - this._field( - "select", - "irc.sshAuthMethod", - "SSH Auth Method", - irc.sshAuthMethod, - { - options: [ - { value: "publickey", label: "Public Key" }, - { value: "password", label: "Password" }, - ], - }, - ), - this._field( - "text", - "irc.sshPrivateKey", - "SSH Private Key Path", - irc.sshPrivateKey, - { - placeholder: "~/.ssh/id_ed25519", - datalistId: "ssh-key-path-options", - }, - ), - this._field( - "toggle", - "irc.sshAgentAuth", - "Use SSH Agent", - irc.sshAgentAuth, - ), - this._field( - "select", - "irc.sshSecretMode", - "SSH Secret Storage", - irc.sshSecretMode, - { - options: [ - { value: "saved", label: "Save to keychain" }, - { value: "session-only", label: "Remember for session" }, - { value: "ask-every-time", label: "Ask every time" }, - ], - }, - ), - ]), - ); - - sections.push( - this._renderSection("Reconnection", [ - this._field( - "toggle", - "irc.autoReconnect", - "Auto-Reconnect", - irc.autoReconnect, - ), - this._field( - "number", - "irc.maxReconnectTries", - "Max Reconnect Attempts", - irc.maxReconnectTries || 5, - { min: 1, max: 20 }, - ), - ]), - ); - - return sections; - } - - /** @private */ - _renderTerminalSection(profile) { - return this._renderSection("Terminal", [ - this._field( - "text", - "terminalFontFamily", - "Font Family", - profile.terminalFontFamily, - { - placeholder: "JetBrains Mono, Menlo, Monaco, Consolas, monospace", - }, - ), - this._field( - "number", - "terminalFontSize", - "Font Size", - profile.terminalFontSize, - { min: 8, max: 36 }, - ), - this._field( - "number", - "terminalScrollback", - "Scrollback Lines", - profile.terminalScrollback, - { min: 0, max: 100000 }, - ), - this._field( - "select", - "terminalCursorStyle", - "Cursor Style", - profile.terminalCursorStyle, - { - options: [ - { value: "block", label: "Block" }, - { value: "underline", label: "Underline" }, - { value: "bar", label: "Bar" }, - ], - }, - ), - ]); - } - - /** @private */ - _renderNotesSection(profile) { - return this._renderSection("Notes & Tags", [ - this._field("tags", "tags", "Tags", (profile.tags || []).join(", "), { - placeholder: "production, london, vpn", - help: "Comma-separated tags for filtering", - }), - this._field("textarea", "notes", "Notes", profile.notes, { - placeholder: "Free-form notes…", - rows: 4, - }), - ]); - } - - // ── Form helpers ───────────────────────────────────────────────── - - /** - * Renders a collapsible form section. - * @param {string} title - * @param {HTMLElement[]} fields - * @returns {HTMLElement} - * @private - */ - /** - * Renders the TOTP/2FA section with secret management and test button. - * @param {Object} profile - * @returns {HTMLElement} - * @private - */ - _renderTOTPSection(profile) { - const section = createElement("div", "form-section"); - const header = createElement("div", "form-section-header"); - let collapsed = false; - - header.innerHTML = ` - ▾ - TOTP / 2FA - `; - - const content = createElement("div", "form-section-content"); - - const helpText = createElement("div", "form-help", { - textContent: - "Configure a TOTP secret to auto-fill 2FA codes during SSH keyboard-interactive authentication. The secret is stored encrypted.", - }); - content.appendChild(helpText); - - // TOTP secret input - const group = createElement("div", "form-group"); - const label = createElement("label", "form-label", { - textContent: "TOTP Secret", - }); - group.appendChild(label); - - const row = createElement("div", "totp-input-row"); - - const secretInput = createElement("input", "form-input", { - type: "password", - placeholder: - "Base32-encoded secret (e.g. from authenticator app setup)", - id: "field-ssh.totpSecret", - dataset: { field: "ssh.totpSecret" }, - autocapitalize: "off", - autocorrect: "off", - spellcheck: false, - }); - - // Show/hide toggle - const toggleBtn = createElement( - "button", - "btn btn-small btn-secondary totp-toggle", - { - textContent: "👁", - title: "Show/hide secret", - type: "button", - }, - ); - toggleBtn.addEventListener("mousedown", (e) => e.preventDefault()); - toggleBtn.addEventListener("click", () => { - secretInput.type = - secretInput.type === "password" ? "text" : "password"; - }); - - // Test button — generates a live code - const testBtn = createElement( - "button", - "btn btn-small btn-accent totp-test", - { - textContent: "Test", - title: "Generate a code with this secret", - type: "button", - }, - ); - testBtn.addEventListener("mousedown", (e) => e.preventDefault()); - testBtn.addEventListener("click", async () => { - const profileId = profile.id; - if (profileId) { - try { - const code = await window.AlchementaryLink.wailsService.GetTOTPCode(profileId); - testBtn.textContent = code; - setTimeout(() => { - testBtn.textContent = "Test"; - }, 5000); - } catch (err) { - // If no saved secret yet, try computing locally using the input value - testBtn.textContent = "Save first"; - setTimeout(() => { - testBtn.textContent = "Test"; - }, 2000); - } - } - }); - - // Save button - const saveBtn = createElement( - "button", - "btn btn-small btn-primary totp-save", - { - textContent: "Save", - title: "Encrypt and save the TOTP secret", - type: "button", - }, - ); - saveBtn.addEventListener("mousedown", (e) => e.preventDefault()); - saveBtn.addEventListener("click", async () => { - const profileId = profile.id; - const secret = secretInput.value.trim(); - if (!profileId) { - AlchementaryLink.Notifications.show( - "Save the profile first, then set the TOTP secret.", - "warning", - ); - return; - } - if (!secret) { - // Delete existing secret - try { - await window.AlchementaryLink.wailsService.DeleteTOTPSecret(profileId); - AlchementaryLink.Notifications.show("TOTP secret removed.", "info"); - } catch (err) { - AlchementaryLink.Notifications.show( - "Failed to remove TOTP secret.", - "error", - ); - } - return; - } - try { - await window.AlchementaryLink.wailsService.SetTOTPSecret(profileId, secret); - AlchementaryLink.Notifications.show( - "TOTP secret saved securely.", - "success", - ); - secretInput.value = ""; - secretInput.placeholder = "••••••••• (secret saved)"; - } catch (err) { - AlchementaryLink.Notifications.show( - "Failed to save TOTP secret: " + err, - "error", - ); - } - }); - - // Delete button - const deleteBtn = createElement( - "button", - "btn btn-small btn-danger totp-delete", - { - textContent: "Delete", - title: "Remove the TOTP secret", - type: "button", - }, - ); - deleteBtn.addEventListener("mousedown", (e) => e.preventDefault()); - deleteBtn.addEventListener("click", async () => { - const profileId = profile.id; - if (!profileId) return; - try { - await window.AlchementaryLink.wailsService.DeleteTOTPSecret(profileId); - AlchementaryLink.Notifications.show("TOTP secret removed.", "info"); - secretInput.placeholder = - "Base32-encoded secret (e.g. from authenticator app setup)"; - } catch (err) { - AlchementaryLink.Notifications.show( - "Failed to remove TOTP secret.", - "error", - ); - } - }); - - row.appendChild(secretInput); - row.appendChild(toggleBtn); - row.appendChild(testBtn); - row.appendChild(saveBtn); - row.appendChild(deleteBtn); - group.appendChild(row); - content.appendChild(group); - - // Check if secret is already saved and show placeholder - if (profile.id) { - (async () => { - try { - const has = await window.AlchementaryLink.wailsService.HasTOTPSecret(profile.id); - if (has) { - secretInput.placeholder = "••••••••• (secret saved)"; - } - } catch (_) { - // Ignore — might not be unlocked. - } - })(); - } - - const toggleSection = () => { - collapsed = !collapsed; - content.style.display = collapsed ? "none" : "block"; - header.querySelector(".form-section-chevron").textContent = collapsed - ? "▸" - : "▾"; - }; - header.addEventListener("mousedown", (e) => { - if (e.button !== 0) return; - e.preventDefault(); - toggleSection(); - }); - - section.appendChild(header); - section.appendChild(content); - return section; - } - - /** - * Renders the login script section (expect-style send/wait steps). - * @param {string} prefix - 'ssh' or 'telnet' - * @param {Array} steps - Array of {type, value, timeout} - * @returns {HTMLElement} - * @private - */ - _renderLoginScriptSection(prefix, steps) { - const section = createElement("div", "form-section"); - const header = createElement("div", "form-section-header"); - let collapsed = false; - - header.innerHTML = ` - ▾ - Login Script - `; - - const content = createElement("div", "form-section-content"); - - const helpText = createElement("div", "form-help", { - textContent: - "Automate login sequences: wait for text from the server, then send a response. Steps execute in order after connection.", - }); - content.appendChild(helpText); - - // Hidden input to hold JSON - const hiddenInput = createElement("input", "", { - type: "hidden", - id: `field-${prefix}.loginScripts`, - dataset: { field: `${prefix}.loginScripts` }, - }); - content.appendChild(hiddenInput); - - // List container - const listContainer = createElement("div", "login-script-list"); - content.appendChild(listContainer); - - // Working copy - let currentSteps = JSON.parse(JSON.stringify(steps || [])); - - const renderSteps = () => { - listContainer.innerHTML = ""; - hiddenInput.value = JSON.stringify(currentSteps); - - if (currentSteps.length === 0) { - const empty = createElement("div", "login-script-empty", { - textContent: "No login script steps configured.", - }); - listContainer.appendChild(empty); - } - - currentSteps.forEach((step, idx) => { - const row = createElement("div", "login-script-row"); - - const typeSelect = createElement( - "select", - "form-input login-script-type", - ); - ["wait", "send"].forEach((t) => { - const opt = createElement("option", "", { - value: t, - textContent: t === "wait" ? "Wait for" : "Send", - }); - if (t === step.type) opt.selected = true; - typeSelect.appendChild(opt); - }); - typeSelect.addEventListener("change", () => { - currentSteps[idx].type = typeSelect.value; - hiddenInput.value = JSON.stringify(currentSteps); - renderSteps(); - }); - - const valueInput = createElement( - "input", - "form-input login-script-value", - { - type: "text", - value: step.value || "", - placeholder: - step.type === "wait" ? "Text to match..." : "Text to send...", - autocapitalize: "off", - autocorrect: "off", - spellcheck: false, - }, - ); - valueInput.addEventListener("input", () => { - currentSteps[idx].value = valueInput.value; - hiddenInput.value = JSON.stringify(currentSteps); - }); - - row.appendChild(typeSelect); - row.appendChild(valueInput); - - // Timeout field for wait steps - if (step.type === "wait") { - const timeoutInput = createElement( - "input", - "form-input login-script-timeout", - { - type: "number", - value: step.timeout || 10, - min: 1, - max: 120, - title: "Timeout (seconds)", - }, - ); - timeoutInput.style.width = "60px"; - timeoutInput.addEventListener("input", () => { - currentSteps[idx].timeout = - parseInt(timeoutInput.value, 10) || 10; - hiddenInput.value = JSON.stringify(currentSteps); - }); - const timeoutLabel = createElement( - "span", - "login-script-timeout-label", - { textContent: "s" }, - ); - row.appendChild(timeoutInput); - row.appendChild(timeoutLabel); - } - - // Move up/down/remove buttons - const actions = createElement("div", "login-script-actions"); - - if (idx > 0) { - const upBtn = createElement("button", "login-script-btn", { - textContent: "▲", - title: "Move up", - type: "button", - }); - upBtn.addEventListener("mousedown", (e) => e.preventDefault()); - upBtn.addEventListener("click", () => { - [currentSteps[idx - 1], currentSteps[idx]] = [ - currentSteps[idx], - currentSteps[idx - 1], - ]; - renderSteps(); - }); - actions.appendChild(upBtn); - } - - if (idx < currentSteps.length - 1) { - const downBtn = createElement("button", "login-script-btn", { - textContent: "▼", - title: "Move down", - type: "button", - }); - downBtn.addEventListener("mousedown", (e) => e.preventDefault()); - downBtn.addEventListener("click", () => { - [currentSteps[idx], currentSteps[idx + 1]] = [ - currentSteps[idx + 1], - currentSteps[idx], - ]; - renderSteps(); - }); - actions.appendChild(downBtn); - } - - const removeBtn = createElement( - "button", - "login-script-btn login-script-btn-remove", - { textContent: "✕", title: "Remove", type: "button" }, - ); - removeBtn.addEventListener("mousedown", (e) => e.preventDefault()); - removeBtn.addEventListener("click", () => { - currentSteps.splice(idx, 1); - renderSteps(); - }); - actions.appendChild(removeBtn); - - row.appendChild(actions); - listContainer.appendChild(row); - }); - }; - - renderSteps(); - - // Add step buttons - const addRow = createElement("div", "login-script-add-row"); - const addWaitBtn = createElement( - "button", - "btn btn-small btn-secondary", - { textContent: "+ Wait", type: "button" }, - ); - addWaitBtn.addEventListener("mousedown", (e) => e.preventDefault()); - addWaitBtn.addEventListener("click", () => { - currentSteps.push({ type: "wait", value: "", timeout: 10 }); - renderSteps(); - }); - const addSendBtn = createElement( - "button", - "btn btn-small btn-secondary", - { textContent: "+ Send", type: "button" }, - ); - addSendBtn.addEventListener("mousedown", (e) => e.preventDefault()); - addSendBtn.addEventListener("click", () => { - currentSteps.push({ type: "send", value: "", timeout: 0 }); - renderSteps(); - }); - addRow.appendChild(addWaitBtn); - addRow.appendChild(addSendBtn); - content.appendChild(addRow); - - const toggleSection = () => { - collapsed = !collapsed; - content.style.display = collapsed ? "none" : "block"; - header.querySelector(".form-section-chevron").textContent = collapsed - ? "▸" - : "▾"; - }; - header.addEventListener("mousedown", (e) => { - if (e.button !== 0) return; - e.preventDefault(); - toggleSection(); - }); - - section.appendChild(header); - section.appendChild(content); - return section; - } - - _renderSection(title, fields) { - const section = createElement("div", "form-section"); - const header = createElement("div", "form-section-header"); - let collapsed = false; - - header.innerHTML = ` - ${collapsed ? "▸" : "▾"} - ${escapeHtml(title)} - `; - - const content = createElement("div", "form-section-content"); - if (collapsed) content.style.display = "none"; - - for (const field of fields) { - if (field instanceof HTMLElement) { - content.appendChild(field); - } - } - - const toggleSection = () => { - collapsed = !collapsed; - content.style.display = collapsed ? "none" : "block"; - header.querySelector(".form-section-chevron").textContent = collapsed - ? "▸" - : "▾"; - }; - // Handle toggle on mousedown to avoid first-click swallowing in WKWebView. - header.addEventListener("mousedown", (e) => { - if (e.button !== 0) return; - e.preventDefault(); - toggleSection(); - }); - - section.appendChild(header); - section.appendChild(content); - return section; - } - - /** - * Renders a single form field. - * @param {string} type - text | number | select | toggle | textarea | tags | file - * @param {string} name - Dot-notated field name, e.g. "ssh.secretMode". - * @param {string} label - * @param {*} value - * @param {Object} [opts] - * @returns {HTMLElement} - * @private - */ - _field(type, name, label, value, opts = {}) { - const group = createElement("div", "form-group"); - - // Label - const labelEl = createElement("label", "form-label", { - textContent: label, - for: `field-${name}`, - }); - group.appendChild(labelEl); - - // Input element based on type - let input; - - switch (type) { - case "text": - case "file": - input = createElement("input", "form-input", { - type: type === "file" ? "text" : "text", - id: `field-${name}`, - name, - value: value || "", - placeholder: opts.placeholder || "", - }); - if (opts.required) input.required = true; - break; - - case "number": - input = createElement("input", "form-input", { - type: "number", - id: `field-${name}`, - name, - value: value != null ? String(value) : "", - }); - if (opts.min != null) input.min = opts.min; - if (opts.max != null) input.max = opts.max; - if (opts.required) input.required = true; - break; - - case "select": { - input = createElement("select", "form-select", { - id: `field-${name}`, - name, - }); - for (const opt of opts.options || []) { - const option = createElement("option", "", { - value: opt.value, - textContent: opt.label, - }); - if (opt.value === value) option.selected = true; - input.appendChild(option); - } - break; - } - - case "toggle": { - const wrapper = createElement("div", "form-toggle-wrapper"); - input = createElement("input", "form-toggle", { - type: "checkbox", - id: `field-${name}`, - name, - }); - input.checked = !!value; - const slider = createElement("label", "form-toggle-label", { - for: `field-${name}`, - }); - wrapper.appendChild(input); - wrapper.appendChild(slider); - group.appendChild(wrapper); - // Help text - if (opts.help) { - group.appendChild( - createElement("span", "form-hint", { textContent: opts.help }), - ); - } - return group; - } - - case "textarea": - input = createElement("textarea", "form-textarea", { - id: `field-${name}`, - name, - placeholder: opts.placeholder || "", - }); - input.rows = opts.rows || 3; - input.textContent = value || ""; - break; - - case "tags": - input = createElement("input", "form-input", { - type: "text", - id: `field-${name}`, - name, - value: value || "", - placeholder: opts.placeholder || "", - }); - break; - - default: - input = createElement("input", "form-input", { - type: "text", - id: `field-${name}`, - name, - value: value || "", - }); - } - - group.appendChild(input); - - if (opts.datalistId && input && input.tagName === "INPUT") { - input.setAttribute("list", opts.datalistId); - } - - // Help text - if (opts.help) { - group.appendChild( - createElement("span", "form-hint", { textContent: opts.help }), - ); - } - - return group; - } - - _renderSSHKeyPathField(value) { - const group = createElement("div", "form-group"); - const labelEl = createElement("label", "form-label", { - textContent: "Private Key Path", - for: "field-ssh.privateKeyPath", - }); - group.appendChild(labelEl); - - const row = createElement("div", "key-path-row"); - const input = createElement("input", "form-input", { - type: "text", - id: "field-ssh.privateKeyPath", - name: "ssh.privateKeyPath", - value: value || "", - placeholder: "/Users/username/.ssh/id_ed25519", - }); - input.setAttribute("list", "ssh-key-path-options"); - row.appendChild(input); - - const browseBtn = createElement("button", "btn btn-sm btn-outline", { - textContent: "Browse", - type: "button", - }); - browseBtn.addEventListener("mousedown", (e) => e.preventDefault()); - browseBtn.addEventListener("click", () => { - if (!window.AlchementaryLink?.wailsService) return; - window.AlchementaryLink.wailsService.SelectFile("Select SSH Private Key", "*") - .then((path) => { - if (path) input.value = path; - }) - .catch((err) => { - this.app.notifications.error( - "Failed to open file dialog: " + (err.message || err), - ); - }); - }); - row.appendChild(browseBtn); - group.appendChild(row); - - const error = createElement("span", "form-error"); - error.style.display = "none"; - group.appendChild(error); - - const success = createElement("span", "form-success"); - success.style.display = "none"; - group.appendChild(success); - - this._attachKeyPathValidation(input, error, success); - - group.appendChild( - createElement("span", "form-hint", { - textContent: "Select a key from your .ssh folder or type a full path", - }), - ); - - return group; - } - - /** - * Renders the jump-hosts list editor. - * @param {Array} jumpHosts - * @returns {HTMLElement} - * @private - */ - _renderJumpHostsList(jumpHosts) { - const wrapper = createElement("div", "jump-hosts-list"); - - for (let i = 0; i < jumpHosts.length; i++) { - const jh = jumpHosts[i]; - const row = createElement("div", "jump-host-row"); - row.innerHTML = ` -
- - - -
-
- - - -
- `; - const keyInput = row.querySelector( - `input[name="jumpHost-${i}-privateKeyPath"]`, - ); - const browseBtn = row.querySelector(".jump-host-browse"); - const error = createElement("span", "form-error"); - error.style.display = "none"; - const success = createElement("span", "form-success"); - success.style.display = "none"; - - browseBtn.addEventListener("mousedown", (e) => e.preventDefault()); - browseBtn.addEventListener("click", () => { - if (!window.AlchementaryLink?.wailsService) return; - window.AlchementaryLink.wailsService.SelectFile("Select SSH Private Key", "*") - .then((path) => { - if (path) keyInput.value = path; - this._validateKeyPath(keyInput, error); - }) - .catch((err) => { - this.app.notifications.error( - "Failed to open file dialog: " + (err.message || err), - ); - }); - }); - - this._attachKeyPathValidation(keyInput, error, success); - row.querySelector(".jump-host-remove").addEventListener("click", () => { - jumpHosts.splice(i, 1); - this.render(this._profile, this._isNew); - }); - wrapper.appendChild(row); - wrapper.appendChild(error); - wrapper.appendChild(success); - } - - const addBtn = createElement("button", "btn btn-sm btn-outline", { - textContent: "+ Add Jump Host", - }); - addBtn.addEventListener("click", () => { - jumpHosts.push({ - host: "", - port: 22, - username: "", - authMethod: "publickey", - secretMode: "ask-every-time", - privateKeyPath: "", - }); - this.render(this._profile, this._isNew); - }); - wrapper.appendChild(addBtn); - - return wrapper; - } - - _attachKeyPathValidation(input, errorEl, successEl) { - if (!input || !errorEl) return; - input.addEventListener("blur", () => - this._validateKeyPath(input, errorEl, successEl), - ); - input.addEventListener("input", () => { - errorEl.style.display = "none"; - errorEl.textContent = ""; - if (successEl) { - successEl.style.display = "none"; - successEl.textContent = ""; - } - }); - } - - _validateKeyPath(input, errorEl, successEl) { - const value = (input.value || "").trim(); - if (!value) { - errorEl.style.display = "none"; - errorEl.textContent = ""; - if (successEl) { - successEl.style.display = "none"; - successEl.textContent = ""; - } - return; - } - if (value.startsWith("~")) { - errorEl.textContent = "Please use a full path (no ~)."; - errorEl.style.display = "block"; - if (successEl) { - successEl.style.display = "none"; - successEl.textContent = ""; - } - return; - } - if (!window.AlchementaryLink?.wailsService) { - return; - } - window.AlchementaryLink.wailsService.FileExists(value) - .then((exists) => { - if (!exists) { - errorEl.textContent = "Key file not found."; - errorEl.style.display = "block"; - if (successEl) { - successEl.style.display = "none"; - successEl.textContent = ""; - } - return; - } - return window.AlchementaryLink.wailsService.FileIsPrivateKey(value).then((isKey) => { - if (!isKey) { - errorEl.textContent = "File is not a recognised private key."; - errorEl.style.display = "block"; - if (successEl) { - successEl.style.display = "none"; - successEl.textContent = ""; - } - return; - } - errorEl.style.display = "none"; - errorEl.textContent = ""; - if (successEl) { - successEl.textContent = "Key verified."; - successEl.style.display = "block"; - } - }); - }) - .catch(() => { - errorEl.textContent = "Could not verify this path."; - errorEl.style.display = "block"; - if (successEl) { - successEl.style.display = "none"; - successEl.textContent = ""; - } - }); - } - - /** - * Renders the port-forwarding rules list editor. - * @param {Array} portForwards - * @returns {HTMLElement} - * @private - */ - _renderPortForwardsList(portForwards) { - const wrapper = createElement("div", "port-forwards-list"); - - for (let i = 0; i < portForwards.length; i++) { - const pf = portForwards[i]; - const row = createElement("div", "port-forward-row"); - row.innerHTML = ` -
- - - - ⟶ - - -
-
- - - -
- `; - row - .querySelector(".port-forward-remove") - .addEventListener("click", () => { - portForwards.splice(i, 1); - this.render(this._profile, this._isNew); - }); - wrapper.appendChild(row); - } - - const addBtn = createElement("button", "btn btn-sm btn-outline", { - textContent: "+ Add Port Forward", - }); - addBtn.addEventListener("click", () => { - portForwards.push({ - id: generateId(), - direction: "local", - bindHost: "127.0.0.1", - bindPort: 0, - destHost: "", - destPort: 0, - enabled: true, - label: "", - }); - this.render(this._profile, this._isNew); - }); - wrapper.appendChild(addBtn); - - return wrapper; - } - - /** - * Renders the payload-presets list editor. - * @param {Array} presets - * @returns {HTMLElement} - * @private - */ - _renderPayloadPresetsList(presets) { - const wrapper = createElement("div", "payload-presets-list"); - - for (let i = 0; i < presets.length; i++) { - const ps = presets[i]; - const row = createElement("div", "preset-row"); - row.innerHTML = ` - - - - - `; - row.querySelector(".preset-remove").addEventListener("click", () => { - presets.splice(i, 1); - this.render(this._profile, this._isNew); - }); - wrapper.appendChild(row); - } - - const addBtn = createElement("button", "btn btn-sm btn-outline", { - textContent: "+ Add Preset", - }); - addBtn.addEventListener("click", () => { - presets.push({ - id: generateId(), - name: "", - payload: "", - format: "text", - }); - this.render(this._profile, this._isNew); - }); - wrapper.appendChild(addBtn); - - return wrapper; - } - - // ── Form data collection ───────────────────────────────────────── - - /** - * Reads current form values into this._profile. - * @private - */ - _collectFormValues() { - if (!this.drawer) return; - - const val = (name) => { - const el = this.drawer.querySelector(`[name="${name}"]`); - if (!el) return undefined; - if (el.type === "checkbox") return el.checked; - if (el.type === "number") return el.value !== "" ? Number(el.value) : 0; - return el.value; - }; - - const p = this._profile; - - // General - p.displayName = val("displayName") || ""; - p.host = val("host") || ""; - p.port = val("port") || p.port; - p.username = val("username") || ""; - p.description = val("description") || ""; - p.folder = (val("folder") || "").trim(); - p.terminalFontFamily = val("terminalFontFamily") || ""; - p.terminalFontSize = val("terminalFontSize") || 14; - p.terminalScrollback = val("terminalScrollback") || 10000; - p.terminalCursorStyle = val("terminalCursorStyle") || "block"; - p.connectTimeout = val("connectTimeout") || 30; - p.readTimeout = val("readTimeout") || 0; - - // Tags - const tagsStr = val("tags"); - if (tagsStr !== undefined) { - p.tags = tagsStr - .split(",") - .map((t) => t.trim()) - .filter(Boolean); - } - - // Notes - const notes = val("notes"); - if (notes !== undefined) p.notes = notes; - - // SSH-specific - if (p.protocol === "ssh" && p.ssh) { - p.ssh.secretMode = val("ssh.secretMode") || p.ssh.secretMode; - p.ssh.privateKeyPath = val("ssh.privateKeyPath") || ""; - p.ssh.keySecretMode = val("ssh.keySecretMode") || p.ssh.keySecretMode; - p.ssh.hostKeyPolicy = val("ssh.hostKeyPolicy") || p.ssh.hostKeyPolicy; - p.ssh.terminalType = val("ssh.terminalType") || "xterm-256color"; - p.ssh.keepaliveInterval = val("ssh.keepaliveInterval") ?? 30; - p.ssh.keepaliveCountMax = val("ssh.keepaliveCountMax") ?? 3; - p.ssh.compression = val("ssh.compression") || false; - p.ssh.agentAuth = val("ssh.agentAuth") || false; - p.ssh.agentForwarding = val("ssh.agentForwarding") || false; - p.ssh.autoReconnect = val("ssh.autoReconnect") || false; - p.ssh.maxReconnectTries = val("ssh.maxReconnectTries") ?? 5; - p.ssh.startupCommand = val("ssh.startupCommand") || ""; - - // Proxy settings - p.ssh.proxyType = val("ssh.proxyType") || "none"; - p.ssh.proxyHost = val("ssh.proxyHost") || ""; - p.ssh.proxyPort = val("ssh.proxyPort") || 1080; - p.ssh.proxyUsername = val("ssh.proxyUsername") || ""; - p.ssh.proxyPassword = val("ssh.proxyPassword") || ""; - - const authStr = val("ssh.authMethods"); - if (authStr !== undefined) { - p.ssh.authMethods = authStr - .split(",") - .map((s) => s.trim()) - .filter(Boolean); - } - - // Env vars from textarea - const envText = val("ssh.envVarsText"); - if (envText !== undefined) { - p.ssh.envVars = {}; - envText.split("\n").forEach((line) => { - const eq = line.indexOf("="); - if (eq > 0) { - p.ssh.envVars[line.slice(0, eq).trim()] = line - .slice(eq + 1) - .trim(); - } - }); - } - - // Jump hosts - const jh = []; - let ji = 0; - while (this.drawer.querySelector(`[name="jumpHost-${ji}-host"]`)) { - jh.push({ - host: val(`jumpHost-${ji}-host`) || "", - port: val(`jumpHost-${ji}-port`) || 22, - username: val(`jumpHost-${ji}-username`) || "", - authMethod: "publickey", - secretMode: "ask-every-time", - privateKeyPath: val(`jumpHost-${ji}-privateKeyPath`) || "", - }); - ji++; - } - p.ssh.jumpHosts = jh; - - // Port forwards - const pfs = []; - let fi = 0; - while (this.drawer.querySelector(`[name="portFwd-${fi}-direction"]`)) { - pfs.push({ - id: - (p.ssh.portForwards && p.ssh.portForwards[fi]?.id) || - generateId(), - direction: val(`portFwd-${fi}-direction`) || "local", - bindHost: val(`portFwd-${fi}-bindHost`) || "127.0.0.1", - bindPort: val(`portFwd-${fi}-bindPort`) || 0, - destHost: val(`portFwd-${fi}-destHost`) || "", - destPort: val(`portFwd-${fi}-destPort`) || 0, - enabled: val(`portFwd-${fi}-enabled`) !== false, - label: val(`portFwd-${fi}-label`) || "", - }); - fi++; - } - p.ssh.portForwards = pfs; - - // Login scripts - const loginScriptsRaw = val("ssh.loginScripts"); - if (loginScriptsRaw) { - try { - p.ssh.loginScripts = JSON.parse(loginScriptsRaw); - } catch (_) { - p.ssh.loginScripts = []; - } - } else { - p.ssh.loginScripts = []; - } - } - - // Telnet-specific - if (p.protocol === "telnet" && p.telnet) { - p.telnet.localEcho = val("telnet.localEcho") || false; - p.telnet.lineMode = val("telnet.lineMode") || false; - p.telnet.terminalType = val("telnet.terminalType") || "xterm-256color"; - p.telnet.encoding = val("telnet.encoding") || "utf-8"; - p.telnet.lineEnding = val("telnet.lineEnding") || "crlf"; - p.telnet.negotiationDebug = val("telnet.negotiationDebug") || false; - p.telnet.autoReconnect = val("telnet.autoReconnect") || false; - p.telnet.maxReconnectTries = val("telnet.maxReconnectTries") ?? 5; - - // Login scripts - const telLoginScriptsRaw = val("telnet.loginScripts"); - if (telLoginScriptsRaw) { - try { - p.telnet.loginScripts = JSON.parse(telLoginScriptsRaw); - } catch (_) { - p.telnet.loginScripts = []; - } - } else { - p.telnet.loginScripts = []; - } - } - - // RAW-specific - if (p.protocol === "raw" && p.raw) { - p.raw.encoding = val("raw.encoding") || "utf-8"; - p.raw.lineEnding = val("raw.lineEnding") || "crlf"; - p.raw.showTimestamps = val("raw.showTimestamps") || false; - p.raw.showDirection = val("raw.showDirection") || false; - p.raw.autoReconnect = val("raw.autoReconnect") || false; - - // Presets - const presets = []; - let pi = 0; - while (this.drawer.querySelector(`[name="preset-${pi}-name"]`)) { - presets.push({ - id: - (p.raw.payloadPresets && p.raw.payloadPresets[pi]?.id) || - generateId(), - name: val(`preset-${pi}-name`) || "", - payload: val(`preset-${pi}-payload`) || "", - format: val(`preset-${pi}-format`) || "text", - }); - pi++; - } - p.raw.payloadPresets = presets; - } - - // GitHub-specific - if (p.protocol === "github") { - if (!p.github) p.github = {}; - p.github.baseURL = val("github.baseURL") || "https://api.github.com"; - p.github.secretMode = val("github.secretMode") || "saved"; - p.github.refreshIntervalSecs = val("github.refreshIntervalSecs") || 300; - } - - // VNC-specific - if (p.protocol === "vnc") { - if (!p.vnc) p.vnc = {}; - p.vnc.vncHost = val("vnc.vncHost") || ""; - p.vnc.vncPort = val("vnc.vncPort") || 5900; - p.vnc.secretMode = val("vnc.secretMode") || "prompt"; - p.vnc.viewOnly = val("vnc.viewOnly") || false; - p.vnc.scaleToFit = val("vnc.scaleToFit") !== false; - p.vnc.quality = val("vnc.quality") || 6; - p.vnc.sharedSession = val("vnc.sharedSession") !== false; - p.vnc.sshTunnel = val("vnc.sshTunnel") || false; - p.vnc.sshHost = val("vnc.sshHost") || ""; - p.vnc.sshPort = val("vnc.sshPort") || 22; - p.vnc.sshUsername = val("vnc.sshUsername") || ""; - p.vnc.sshAuthMethod = val("vnc.sshAuthMethod") || "publickey"; - p.vnc.sshPrivateKey = val("vnc.sshPrivateKey") || ""; - p.vnc.sshAgentAuth = val("vnc.sshAgentAuth") || false; - p.vnc.sshSecretMode = val("vnc.sshSecretMode") || "saved"; - } - - // IRC-specific - if (p.protocol === "irc") { - if (!p.irc) p.irc = {}; - p.irc.nick = val("irc.nick") || "link"; - p.irc.realName = val("irc.realName") || "Alchementary Link"; - p.irc.useTLS = val("irc.useTLS") || false; - p.irc.serverPasswordRequired = - val("irc.serverPasswordRequired") || false; - p.irc.serverPasswordMode = - val("irc.serverPasswordMode") || "ask-every-time"; - p.irc.nickServIdentify = val("irc.nickServIdentify") || false; - p.irc.nickServAccount = val("irc.nickServAccount") || ""; - p.irc.nickServMode = val("irc.nickServMode") || "saved"; - p.irc.autoJoinRooms = this._parseRoomList( - val("irc.autoJoinRoomsText") || "", - ); - p.irc.sshTunnel = val("irc.sshTunnel") || false; - p.irc.sshHost = val("irc.sshHost") || ""; - p.irc.sshPort = val("irc.sshPort") || 22; - p.irc.sshUsername = val("irc.sshUsername") || ""; - p.irc.sshAuthMethod = val("irc.sshAuthMethod") || "publickey"; - p.irc.sshPrivateKey = val("irc.sshPrivateKey") || ""; - p.irc.sshAgentAuth = val("irc.sshAgentAuth") || false; - p.irc.sshSecretMode = val("irc.sshSecretMode") || "saved"; - p.irc.autoReconnect = val("irc.autoReconnect") !== false; - p.irc.maxReconnectTries = val("irc.maxReconnectTries") || 5; - } - } - - // ── Validation ─────────────────────────────────────────────────── - - /** - * Validates the collected profile. Returns an array of error strings (empty = valid). - * @param {Object} profile - * @returns {string[]} - * @private - */ - _validate(profile) { - const errors = []; - if (profile.protocol === "vnc") { - const vncHost = (profile.vnc && profile.vnc.vncHost) || profile.host; - if (!vncHost || !vncHost.trim()) { - errors.push( - "VNC Host is required (in General or VNC Server section).", - ); - } - } else if (!profile.host || !profile.host.trim()) { - errors.push("Host is required."); - } - if (!profile.port || profile.port < 1 || profile.port > 65535) { - errors.push("Port must be between 1 and 65535."); - } - if (profile.protocol === "irc") { - const nick = profile.irc?.nick || ""; - if (!nick.trim()) { - errors.push("IRC nick is required."); - } - for (const room of profile.irc?.autoJoinRooms || []) { - if (!/^[#&+!][^\s,]+$/.test(room)) { - errors.push(`Invalid IRC room "${room}".`); - } - } - if (profile.irc?.sshTunnel) { - if (!profile.irc.sshHost?.trim()) { - errors.push("SSH Host is required when tunnelling IRC."); - } - if (!profile.irc.sshUsername?.trim()) { - errors.push("SSH Username is required when tunnelling IRC."); - } - } - } - return errors; - } - - // ── Actions ────────────────────────────────────────────────────── - - /** @private */ - async _handleSave(andConnect = false) { - this._collectFormValues(); - const errors = this._validate(this._profile); - if (errors.length > 0) { - this.app.notifications.warning(errors.join(" ")); - return; - } - - if ( - this.app._requiresDirectVNCWarning && - this.app._requiresDirectVNCWarning(this._profile) - ) { - const ok = await this.app.confirmDirectVNCProfile( - this._profile, - andConnect ? "connect" : "save", - ); - if (!ok) return; - } - - try { - let saved; - if (this._isNew) { - saved = await window.AlchementaryLink.wailsService.CreateProfile(this._profile); - this.app.state.addProfile(saved); - this.app.notifications.success("Profile created"); - } else { - saved = await window.AlchementaryLink.wailsService.UpdateProfile(this._profile); - this.app.state.updateProfile(saved); - this.app.notifications.success("Profile updated"); - } - this.app.sidebar.render(); - this.close(); - - if (andConnect && saved && saved.id) { - this.app.connectProfile(saved.id, { skipDirectVNCWarning: true }); - } - } catch (err) { - this.app.notifications.error("Save failed: " + err); - } - } - - /** @private */ - async _handleDelete() { - const ok = await this._confirmDelete(); - if (!ok) return; - - try { - await window.AlchementaryLink.wailsService.DeleteProfile(this._profile.id); - this.app.state.removeProfile(this._profile.id); - this.app.sidebar.render(); - this.app.notifications.success("Profile deleted"); - this.close(); - } catch (err) { - this.app.notifications.error("Failed to delete profile: " + err); - } - } - - /** @private */ - async _testConnection() { - this._collectFormValues(); - const errors = this._validate(this._profile); - if (errors.length > 0) { - this.app.notifications.warning(errors.join(" ")); - return; - } - - const host = this._profile.host; - const port = this._profile.port; - this.app.notifications.info(`Testing connection to ${host}:${port}…`); - - try { - const result = await window.AlchementaryLink.wailsService.TestConnection(host, port); - this.app.notifications.success( - `Reachable — ${result.latencyMs}ms latency`, - ); - } catch (err) { - this.app.notifications.error("Test failed: " + err); - } - } - - /** - * Shows a confirmation modal for profile deletion. - * @returns {Promise} - * @private - */ - _confirmDelete() { - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - const label = escapeHtml( - this._profile.displayName || this._profile.host || "Untitled", - ); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = (val) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(val); - }; - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-cancel") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-ok") - .addEventListener("click", () => close(true)); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(false); - }); - }); - } - - // ── Utilities ──────────────────────────────────────────────────── - - /** - * Returns a default profile object for the given protocol. - * @param {string} protocol - * @returns {Object} - * @private - */ - _defaultProfile(protocol) { - const base = { - id: generateId(), - displayName: "", - description: "", - protocol, - host: "", - port: 0, - username: "", - tags: [], - folder: "", - environment: "", - notes: "", - favourite: false, - lastConnected: null, - connectTimeout: 30, - readTimeout: 0, - terminalFontFamily: - "JetBrains Mono, Menlo, Monaco, Consolas, monospace", - terminalFontSize: 14, - terminalScrollback: 10000, - terminalCursorStyle: "block", - ssh: null, - telnet: null, - raw: null, - github: null, - vnc: null, - irc: null, - }; - - const defaults = DEFAULTS[protocol] || {}; - return { ...base, ...defaults }; - } - - /** - * Converts an envVars map into "KEY=VALUE\n" text. - * @param {Object} envVars - * @returns {string} - * @private - */ - _envVarsToText(envVars) { - if (!envVars) return ""; - return Object.entries(envVars) - .map(([k, v]) => `${k}=${v}`) - .join("\n"); - } - - _roomsToText(rooms) { - return Array.isArray(rooms) ? rooms.join("\n") : ""; - } - - _parseRoomList(value) { - return String(value || "") - .split(/[\n,]/) - .map((room) => room.trim()) - .filter(Boolean); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.ProfileEditor = ProfileEditor; -})(); diff --git a/frontend/src/js/raw.js b/frontend/src/js/raw.js deleted file mode 100644 index 073e5c7..0000000 --- a/frontend/src/js/raw.js +++ /dev/null @@ -1,625 +0,0 @@ -(function () { - /** - * Alchementary Link — RAW TCP data viewer. - * Provides text, hex, and split display modes for raw socket data, - * along with a send bar, pause/resume, and capture saving. - */ - - const { createElement, escapeHtml, formatTimestamp, formatBytes } = - window.AlchementaryLink; - - /** - * Maximum number of data entries retained in the scrollback buffer. - * @type {number} - */ - const MAX_BUFFER_ENTRIES = 5000; - - class RawViewer { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - - /** - * Map of sessionId → viewer state. - * @type {Map} - */ - this.viewers = new Map(); - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Creates a RAW viewer inside the given panel container. - * @param {string} sessionId - * @param {string} containerId - DOM id of the panel element. - * @param {Object} profile - */ - createViewer(sessionId, containerId, profile) { - const container = document.getElementById(containerId); - if (!container) return; - - const state = { - mode: "text", // text | hex | split - paused: false, - buffer: [], - totalBytes: 0, - sendFormat: "text", // text | hex | escaped - }; - - // ── Toolbar ────────────────────────────────────────────── - const toolbar = createElement("div", "raw-toolbar"); - - // Mode toggle - const modeGroup = createElement("div", "raw-mode-group"); - for (const mode of ["text", "hex", "split"]) { - const btn = createElement( - "button", - `raw-mode-btn ${mode === state.mode ? "active" : ""}`, - { - textContent: mode.charAt(0).toUpperCase() + mode.slice(1), - dataset: { mode }, - }, - ); - btn.addEventListener("click", () => this._setMode(sessionId, mode)); - modeGroup.appendChild(btn); - } - toolbar.appendChild(modeGroup); - - // Controls - const controls = createElement("div", "raw-controls"); - - const pauseBtn = createElement( - "button", - "btn btn-sm btn-outline raw-pause", - { textContent: "⏸ Pause" }, - ); - pauseBtn.addEventListener("click", () => this.togglePause(sessionId)); - controls.appendChild(pauseBtn); - - const clearBtn = createElement("button", "btn btn-sm btn-outline", { - textContent: "✕ Clear", - }); - clearBtn.addEventListener("click", () => this.clearBuffer(sessionId)); - controls.appendChild(clearBtn); - - const saveBtn = createElement("button", "btn btn-sm btn-outline", { - textContent: "💾 Save", - }); - saveBtn.addEventListener("click", () => this.saveCapture(sessionId)); - controls.appendChild(saveBtn); - - // Byte counter - const counter = createElement("span", "raw-counter", { - textContent: "0 B", - }); - controls.appendChild(counter); - - toolbar.appendChild(controls); - container.appendChild(toolbar); - - // ── Data display ───────────────────────────────────────── - const display = createElement("div", "raw-display"); - container.appendChild(display); - - // ── Send bar ───────────────────────────────────────────── - const sendBar = this._renderSendBar(sessionId, profile); - container.appendChild(sendBar); - - this.viewers.set(sessionId, { - ...state, - container, - display, - toolbar, - pauseBtn, - counter, - profile, - }); - } - - /** - * Processes incoming data from the backend. - * @param {Object} payload - { sessionID, data, direction, timestamp } - */ - handleData(payload) { - const viewer = this.viewers.get(payload.sessionID); - if (!viewer) return; - - // Decode base64 payload - let rawStr; - try { - rawStr = atob(payload.data); - } catch (_) { - rawStr = payload.data || ""; - } - - const bytes = new Uint8Array(rawStr.length); - for (let i = 0; i < rawStr.length; i++) { - bytes[i] = rawStr.charCodeAt(i); - } - - const entry = { - direction: payload.direction || "recv", // 'send' | 'recv' - timestamp: payload.timestamp ? new Date(payload.timestamp) : new Date(), - data: rawStr, - bytes, - }; - - viewer.buffer.push(entry); - viewer.totalBytes += bytes.length; - - // Trim buffer if it exceeds the maximum - if (viewer.buffer.length > MAX_BUFFER_ENTRIES) { - viewer.buffer.splice(0, viewer.buffer.length - MAX_BUFFER_ENTRIES); - } - - // Update counter - viewer.counter.textContent = formatBytes(viewer.totalBytes); - - // Render unless paused - if (!viewer.paused) { - this._renderEntry(viewer, entry); - this._scrollToBottom(viewer); - } - } - - /** - * Toggles pause/resume on the data display. - * @param {string} sessionId - */ - togglePause(sessionId) { - const viewer = this.viewers.get(sessionId); - if (!viewer) return; - - viewer.paused = !viewer.paused; - viewer.pauseBtn.textContent = viewer.paused ? "▶ Resume" : "⏸ Pause"; - viewer.pauseBtn.classList.toggle("active", viewer.paused); - - // On resume, flush any buffered entries - if (!viewer.paused) { - this._renderAllEntries(viewer); - } - } - - /** - * Clears the data buffer and display. - * @param {string} sessionId - */ - clearBuffer(sessionId) { - const viewer = this.viewers.get(sessionId); - if (!viewer) return; - - viewer.buffer = []; - viewer.totalBytes = 0; - viewer.display.innerHTML = ""; - viewer.counter.textContent = "0 B"; - } - - /** - * Prompts the user to save the capture buffer. - * @param {string} sessionId - */ - async saveCapture(sessionId) { - const viewer = this.viewers.get(sessionId); - if (!viewer || viewer.buffer.length === 0) { - this.app.notifications.warning("Nothing to save"); - return; - } - - try { - const lines = viewer.buffer.map((e) => { - const ts = formatTimestamp(e.timestamp); - const dir = e.direction === "send" ? "TX" : "RX"; - return `[${ts}] [${dir}] ${e.data}`; - }); - const content = lines.join("\n"); - await window.AlchementaryLink.wailsService.SaveSessionCapture(sessionId, content); - this.app.notifications.success("Capture saved"); - } catch (err) { - this.app.notifications.error("Save failed: " + err); - } - } - - /** - * Sends data from the send bar to the backend. - * @param {string} sessionId - */ - async send(sessionId) { - const viewer = this.viewers.get(sessionId); - if (!viewer) return; - - const input = viewer.container.querySelector(".raw-send-input"); - const formatSelect = viewer.container.querySelector(".raw-send-format"); - if (!input) return; - - const rawValue = input.value; - if (!rawValue) return; - - const format = formatSelect?.value || "text"; - let dataToSend; - - switch (format) { - case "hex": - dataToSend = this._hexToString(rawValue); - break; - case "escaped": - dataToSend = this._unescapeString(rawValue); - break; - default: - dataToSend = rawValue; - } - - try { - // For hex/escaped, data is already raw bytes (0-255) so btoa is correct. - // For text mode, UTF-8 encode so non-ASCII characters are transmitted properly. - const b64 = - format === "hex" || format === "escaped" - ? btoa(dataToSend) - : window.AlchementaryLink.utf8ToBase64(dataToSend); - await window.AlchementaryLink.wailsService.WriteSession(sessionId, b64); - input.value = ""; - - // Also show in viewer as sent data - this.handleData({ - sessionID: sessionId, - data: b64, - direction: "send", - timestamp: new Date().toISOString(), - }); - } catch (err) { - this.app.notifications.error("Send failed: " + err); - } - } - - /** - * Tears down a viewer and frees resources. - * @param {string} sessionId - */ - destroyViewer(sessionId) { - this.viewers.delete(sessionId); - } - - // ── Rendering ──────────────────────────────────────────────────── - - /** - * Renders the send bar at the bottom of the viewer. - * @param {string} sessionId - * @param {Object} profile - * @returns {HTMLElement} - * @private - */ - _renderSendBar(sessionId, profile) { - const bar = createElement("div", "raw-send-bar"); - - // Format selector - const formatSelect = createElement( - "select", - "form-select form-select-sm raw-send-format", - ); - for (const [value, label] of [ - ["text", "Text"], - ["hex", "Hex"], - ["escaped", "Escaped"], - ]) { - const opt = createElement("option", "", { value, textContent: label }); - formatSelect.appendChild(opt); - } - bar.appendChild(formatSelect); - - // Input - const input = createElement("input", "form-input raw-send-input", { - type: "text", - placeholder: "Type data to send…", - }); - input.addEventListener("keydown", (e) => { - if (e.key === "Enter") this.send(sessionId); - }); - bar.appendChild(input); - - // Quick-insert buttons for common control bytes - const quickBtns = createElement("div", "raw-quick-btns"); - const controlBytes = [ - { label: "NUL", value: "\\x00" }, - { label: "CR", value: "\\r" }, - { label: "LF", value: "\\n" }, - { label: "ETX", value: "\\x03" }, - { label: "EOT", value: "\\x04" }, - ]; - for (const cb of controlBytes) { - const btn = createElement("button", "btn btn-xs btn-outline", { - textContent: cb.label, - title: cb.value, - }); - btn.addEventListener("click", () => { - input.value += cb.value; - input.focus(); - }); - quickBtns.appendChild(btn); - } - bar.appendChild(quickBtns); - - // Payload preset dropdown - const presets = profile?.raw?.payloadPresets || []; - if (presets.length > 0) { - const presetSelect = createElement( - "select", - "form-select form-select-sm raw-preset-select", - ); - const defaultOpt = createElement("option", "", { - value: "", - textContent: "Presets…", - }); - presetSelect.appendChild(defaultOpt); - for (const ps of presets) { - const opt = createElement("option", "", { - value: ps.payload, - textContent: ps.name, - }); - opt.dataset.format = ps.format; - presetSelect.appendChild(opt); - } - presetSelect.addEventListener("change", () => { - if (presetSelect.value) { - input.value = presetSelect.value; - const selOpt = presetSelect.selectedOptions[0]; - if (selOpt?.dataset.format) { - formatSelect.value = selOpt.dataset.format; - } - presetSelect.value = ""; - input.focus(); - } - }); - bar.appendChild(presetSelect); - } - - // Send button - const sendBtn = createElement("button", "btn btn-sm btn-primary", { - textContent: "Send", - }); - sendBtn.addEventListener("click", () => this.send(sessionId)); - bar.appendChild(sendBtn); - - return bar; - } - - /** - * Renders a single data entry into the display area. - * @param {Object} viewer - * @param {Object} entry - * @private - */ - _renderEntry(viewer, entry) { - let el; - switch (viewer.mode) { - case "hex": - el = this._renderHexView(entry); - break; - case "split": - el = this._renderSplitView(entry, viewer.profile); - break; - default: - el = this._renderTextView(entry, viewer.profile); - } - viewer.display.appendChild(el); - } - - /** - * Re-renders all buffered entries (e.g. after mode change or resume). - * @param {Object} viewer - * @private - */ - _renderAllEntries(viewer) { - viewer.display.innerHTML = ""; - for (const entry of viewer.buffer) { - this._renderEntry(viewer, entry); - } - this._scrollToBottom(viewer); - } - - /** - * Renders a text-mode entry. - * @param {Object} entry - * @param {Object} profile - * @returns {HTMLElement} - * @private - */ - _renderTextView(entry, profile) { - const row = createElement( - "div", - `raw-entry raw-entry-${entry.direction}`, - ); - const raw = profile?.raw || {}; - - let prefix = ""; - if (raw.showTimestamps) { - prefix += `[${formatTimestamp(entry.timestamp)}] `; - } - if (raw.showDirection) { - prefix += entry.direction === "send" ? "[TX] " : "[RX] "; - } - - const pre = createElement("pre", "raw-text"); - pre.textContent = prefix + entry.data; - row.appendChild(pre); - return row; - } - - /** - * Renders a hex-mode entry. - * @param {Object} entry - * @returns {HTMLElement} - * @private - */ - _renderHexView(entry) { - const row = createElement( - "div", - `raw-entry raw-entry-${entry.direction}`, - ); - const pre = createElement("pre", "raw-hex"); - pre.textContent = this.formatHexDump(entry.bytes); - row.appendChild(pre); - return row; - } - - /** - * Renders a split-mode entry (text + hex side by side). - * @param {Object} entry - * @param {Object} profile - * @returns {HTMLElement} - * @private - */ - _renderSplitView(entry, profile) { - const row = createElement( - "div", - `raw-entry raw-entry-split raw-entry-${entry.direction}`, - ); - const raw = profile?.raw || {}; - - // Direction/timestamp header - const header = createElement("div", "raw-split-header"); - let headerText = ""; - if (raw.showTimestamps) - headerText += formatTimestamp(entry.timestamp) + " "; - headerText += entry.direction === "send" ? "▲ TX" : "▼ RX"; - header.textContent = headerText; - row.appendChild(header); - - // Split panes - const panes = createElement("div", "raw-split-panes"); - - const textPane = createElement("pre", "raw-text raw-split-text"); - textPane.textContent = entry.data; - panes.appendChild(textPane); - - const hexPane = createElement("pre", "raw-hex raw-split-hex"); - hexPane.textContent = this.formatHexDump(entry.bytes); - panes.appendChild(hexPane); - - row.appendChild(panes); - return row; - } - - /** - * Formats a Uint8Array into a traditional hex dump: - * offset | hex bytes | ASCII representation. - * @param {Uint8Array} bytes - * @returns {string} - */ - formatHexDump(bytes) { - const lines = []; - const bytesPerLine = 16; - - for (let offset = 0; offset < bytes.length; offset += bytesPerLine) { - const chunk = bytes.slice(offset, offset + bytesPerLine); - - // Offset column - const offsetStr = offset.toString(16).padStart(8, "0"); - - // Hex column - const hexParts = []; - for (let i = 0; i < bytesPerLine; i++) { - if (i < chunk.length) { - hexParts.push(chunk[i].toString(16).padStart(2, "0")); - } else { - hexParts.push(" "); - } - // Extra space after 8th byte for readability - if (i === 7) hexParts.push(""); - } - const hexStr = hexParts.join(" "); - - // ASCII column - let ascii = ""; - for (let i = 0; i < chunk.length; i++) { - const b = chunk[i]; - ascii += b >= 0x20 && b <= 0x7e ? String.fromCharCode(b) : "."; - } - - lines.push(`${offsetStr} ${hexStr} |${ascii}|`); - } - - return lines.join("\n"); - } - - // ── Internal helpers ───────────────────────────────────────────── - - /** - * Changes the display mode (text, hex, split) and re-renders. - * @param {string} sessionId - * @param {string} mode - * @private - */ - _setMode(sessionId, mode) { - const viewer = this.viewers.get(sessionId); - if (!viewer) return; - - viewer.mode = mode; - - // Update active button - viewer.toolbar.querySelectorAll(".raw-mode-btn").forEach((btn) => { - btn.classList.toggle("active", btn.dataset.mode === mode); - }); - - this._renderAllEntries(viewer); - } - - /** - * Scrolls the data display to the bottom. - * @param {Object} viewer - * @private - */ - _scrollToBottom(viewer) { - requestAnimationFrame(() => { - viewer.display.scrollTop = viewer.display.scrollHeight; - }); - } - - /** - * Converts a hex string (e.g. "48656c6c6f") into a character string. - * @param {string} hex - * @returns {string} - * @private - */ - _hexToString(hex) { - const cleaned = hex.replace(/\s+/g, ""); - let str = ""; - for (let i = 0; i < cleaned.length; i += 2) { - str += String.fromCharCode(parseInt(cleaned.substring(i, i + 2), 16)); - } - return str; - } - - /** - * Converts C-style escape sequences (\\n, \\r, \\x00, etc.) into their literal bytes. - * @param {string} str - * @returns {string} - * @private - */ - _unescapeString(str) { - return str.replace(/\\(n|r|t|0|x[0-9a-fA-F]{2}|\\)/g, (match, seq) => { - switch (seq) { - case "n": - return "\n"; - case "r": - return "\r"; - case "t": - return "\t"; - case "0": - return "\0"; - case "\\": - return "\\"; - default: - // \\xHH - if (seq.startsWith("x")) { - return String.fromCharCode(parseInt(seq.slice(1), 16)); - } - return match; - } - }); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.RawViewer = RawViewer; -})(); diff --git a/frontend/src/js/recordings.js b/frontend/src/js/recordings.js deleted file mode 100644 index d418651..0000000 --- a/frontend/src/js/recordings.js +++ /dev/null @@ -1,701 +0,0 @@ -(function() { -/** - * Alchementary Link — Session Recording & Playback. - * Provides controls for starting/stopping recordings on active sessions, - * listing saved recordings, and a terminal-based playback viewer with - * speed controls and a progress scrubber. - */ - -const { createElement, formatDuration } = window.AlchementaryLink; - -// --------------------------------------------------------------------------- -// Recording Manager -// --------------------------------------------------------------------------- - -class RecordingManager { - constructor(app) { - this.app = app; - /** @type {Set} Session IDs currently being recorded. */ - this.activeSessions = new Set(); - } - - /** - * Toggles recording for the given session. - * @param {string} sessionId - */ - async toggle(sessionId) { - if (this.activeSessions.has(sessionId)) { - await this.stop(sessionId); - } else { - await this.start(sessionId); - } - } - - /** - * Starts recording a session. - * @param {string} sessionId - */ - async start(sessionId) { - try { - const entry = this.app.terminals.terminals.get(sessionId); - const cols = entry ? entry.term.cols : 80; - const rows = entry ? entry.term.rows : 24; - - await window.AlchementaryLink.wailsService.StartRecording(sessionId, cols, rows); - this.activeSessions.add(sessionId); - this._updateRecordingIndicator(sessionId, true); - this.app.notifications.success('Recording started'); - } catch (err) { - this.app.notifications.error('Failed to start recording: ' + err); - } - } - - /** - * Stops recording a session. - * @param {string} sessionId - */ - async stop(sessionId) { - try { - await window.AlchementaryLink.wailsService.StopRecording(sessionId); - this.activeSessions.delete(sessionId); - this._updateRecordingIndicator(sessionId, false); - this.app.notifications.success('Recording saved'); - } catch (err) { - this.app.notifications.error('Failed to stop recording: ' + err); - } - } - - /** - * Drops frontend recording state when a session is closed. The backend - * finalises the recorder as part of DisconnectSession. - * @param {string} sessionId - */ - cleanup(sessionId) { - this.activeSessions.delete(sessionId); - this._updateRecordingIndicator(sessionId, false); - } - - /** - * Opens the recordings browser modal. - */ - async openBrowser() { - try { - const [recordings, storageInfo] = await Promise.all([ - window.AlchementaryLink.wailsService.ListRecordings(), - window.AlchementaryLink.wailsService.GetRecordingStorageInfo(), - ]); - this._showBrowserModal(recordings || [], storageInfo || {}); - } catch (err) { - this.app.notifications.error('Failed to list recordings: ' + err); - } - } - - /** - * Plays back a recording in a modal terminal viewer. - * @param {string} id — Recording ID. - */ - async playback(id) { - try { - const data = await window.AlchementaryLink.wailsService.GetRecording(id); - this._showPlaybackModal(data); - } catch (err) { - this.app.notifications.error('Failed to load recording: ' + err); - } - } - - /** - * Deletes a recording by ID. - * @param {string} id - */ - async deleteRecording(id) { - try { - await window.AlchementaryLink.wailsService.DeleteRecording(id); - this.app.notifications.success('Recording deleted'); - } catch (err) { - this.app.notifications.error('Failed to delete recording: ' + err); - } - } - - // ── Recording indicator on tabs ────────────────────────────────── - - /** - * Updates the recording indicator on a session tab. - * @param {string} sessionId - * @param {boolean} recording - * @private - */ - _updateRecordingIndicator(sessionId, recording) { - const tab = document.querySelector(`.session-tab[data-session-id="${sessionId}"]`); - if (!tab) return; - - let dot = tab.querySelector('.recording-dot'); - if (recording) { - if (!dot) { - dot = createElement('span', 'recording-dot'); - dot.title = 'Recording'; - const label = tab.querySelector('.tab-label'); - if (label) label.parentNode.insertBefore(dot, label); - } - } else { - if (dot) dot.remove(); - } - } - - // ── Recordings browser modal ───────────────────────────────────── - - /** - * Renders a modal listing all saved recordings. - * @param {Array} recordings - * @param {Object} storageInfo - * @private - */ - _showBrowserModal(recordings, storageInfo = {}) { - const backdrop = createElement('div', 'modal-backdrop'); - const modal = createElement('div', 'modal modal-lg'); - - let listHTML = ''; - if (recordings.length === 0) { - listHTML = '

No recordings yet. Use the record button on a terminal tab to capture a session.

'; - } else { - listHTML = '
'; - for (const rec of recordings) { - const date = new Date(rec.createdAt).toLocaleString(); - const dur = _formatSeconds(rec.duration); - const size = _formatBytes(rec.size); - listHTML += ` -
-
- ${_esc(rec.title || rec.id)} - ${date} · ${dur} · ${size} · ${rec.width}×${rec.height} -
-
- - -
-
- `; - } - listHTML += '
'; - } - - const totalSize = _formatBytes(storageInfo.totalBytes || 0); - const count = storageInfo.count ?? recordings.length; - - modal.innerHTML = ` - - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => backdrop.classList.add('is-visible')); - - const close = () => { - backdrop.classList.remove('is-visible'); - setTimeout(() => backdrop.remove(), 200); - }; - - modal.querySelector('.modal-close').addEventListener('click', close); - modal.querySelector('.modal-close-btn').addEventListener('click', close); - - modal.querySelector('.recording-cleanup').addEventListener('click', async () => { - const ageDays = parseInt(modal.querySelector('.recording-cleanup-age').value, 10) || 0; - const maxBytes = parseInt(modal.querySelector('.recording-cleanup-size').value, 10) || 0; - const status = modal.querySelector('.recording-cleanup-status'); - const btn = modal.querySelector('.recording-cleanup'); - status.textContent = ''; - btn.disabled = true; - try { - const result = await window.AlchementaryLink.wailsService.CleanupRecordings(ageDays, maxBytes); - status.textContent = `Deleted ${result.deleted || 0}; freed ${_formatBytes(result.freedBytes || 0)}.`; - const fresh = await window.AlchementaryLink.wailsService.ListRecordings(); - close(); - this._showBrowserModal(fresh || [], await window.AlchementaryLink.wailsService.GetRecordingStorageInfo()); - } catch (err) { - status.textContent = 'Cleanup failed: ' + err; - } finally { - btn.disabled = false; - } - }); - - // Play buttons - modal.querySelectorAll('.rec-play').forEach(btn => { - btn.addEventListener('click', () => { - close(); - this.playback(btn.dataset.id); - }); - }); - - // Delete buttons - modal.querySelectorAll('.rec-delete').forEach(btn => { - btn.addEventListener('click', async () => { - await this.deleteRecording(btn.dataset.id); - const item = btn.closest('.recording-item'); - if (item) item.remove(); - }); - }); - } - - // ── Playback modal with embedded terminal ──────────────────────── - - /** - * Shows a playback modal with an xterm.js terminal, progress bar, and speed controls. - * @param {Object} data — { header, events } - * @private - */ - _showPlaybackModal(data) { - const header = data.header; - const events = data.events || []; - if (events.length === 0) { - this.app.notifications.info('Recording is empty — nothing to play back.'); - return; - } - - const totalDuration = events[events.length - 1][0] || 1; - - const backdrop = createElement('div', 'modal-backdrop'); - const modal = createElement('div', 'modal modal-playback'); - - modal.innerHTML = ` - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => backdrop.classList.add('is-visible')); - - // Create xterm instance for playback. - const termContainer = modal.querySelector('#playback-terminal'); - const { getTerminalTheme } = window.AlchementaryLink; - const themeName = (this.app.state.preferences && this.app.state.preferences.terminalTheme) || 'charcoal'; - const theme = getTerminalTheme ? getTerminalTheme(themeName) : {}; - - const term = new window.Terminal({ - cols: header.width || 80, - rows: header.height || 24, - theme: theme, - fontFamily: '"JetBrains Mono", "Fira Code", "Cascadia Code", "Menlo", monospace', - fontSize: 13, - cursorBlink: false, - disableStdin: true, - scrollback: 10000, - }); - term.open(termContainer); - - const fitAddon = new window.FitAddon.FitAddon(); - term.loadAddon(fitAddon); - setTimeout(() => fitAddon.fit(), 50); - - // Playback state. - let playing = false; - let speed = 1; - let eventIndex = 0; - let playbackTimer = null; - let startRealTime = 0; - let pausedAt = 0; // elapsed virtual time when paused - - const playPauseBtn = modal.querySelector('.playback-play-pause'); - const restartBtn = modal.querySelector('.playback-restart'); - const progressInput = modal.querySelector('.playback-progress'); - const timeLabel = modal.querySelector('.playback-time'); - const speedSelect = modal.querySelector('.playback-speed'); - - const updateTimeDisplay = (elapsed) => { - timeLabel.textContent = `${_formatSeconds(elapsed)} / ${_formatSeconds(totalDuration)}`; - progressInput.value = Math.round((elapsed / totalDuration) * 1000); - }; - - const scheduleNext = () => { - if (!playing || eventIndex >= events.length) { - if (eventIndex >= events.length) { - playing = false; - playPauseBtn.textContent = '▶'; - } - return; - } - - const evt = events[eventIndex]; - const evtTime = evt[0]; // seconds from start - const realElapsed = (Date.now() - startRealTime) / 1000; - const virtualElapsed = pausedAt + realElapsed * speed; - const delay = Math.max(0, (evtTime - virtualElapsed) / speed * 1000); - - playbackTimer = setTimeout(() => { - if (!playing) return; - - // Write output events to the terminal. - if (evt[1] === 'o') { - term.write(evt[2]); - } - - eventIndex++; - const currentVirtual = pausedAt + ((Date.now() - startRealTime) / 1000) * speed; - updateTimeDisplay(Math.min(currentVirtual, totalDuration)); - scheduleNext(); - }, delay); - }; - - const play = () => { - if (eventIndex >= events.length) { - // Restart from beginning. - eventIndex = 0; - pausedAt = 0; - term.reset(); - } - playing = true; - playPauseBtn.textContent = '⏸'; - startRealTime = Date.now(); - scheduleNext(); - }; - - const pause = () => { - playing = false; - playPauseBtn.textContent = '▶'; - if (playbackTimer) { - clearTimeout(playbackTimer); - playbackTimer = null; - } - const realElapsed = (Date.now() - startRealTime) / 1000; - pausedAt = pausedAt + realElapsed * speed; - }; - - const seekTo = (fraction) => { - const wasPlaying = playing; - if (playing) pause(); - - const targetTime = fraction * totalDuration; - term.reset(); - eventIndex = 0; - - // Replay all events up to targetTime. - for (let i = 0; i < events.length; i++) { - if (events[i][0] > targetTime) break; - if (events[i][1] === 'o') { - term.write(events[i][2]); - } - eventIndex = i + 1; - } - - pausedAt = targetTime; - updateTimeDisplay(targetTime); - - if (wasPlaying) play(); - }; - - playPauseBtn.addEventListener('click', () => { - if (playing) pause(); else play(); - }); - - restartBtn.addEventListener('click', () => { - if (playing) pause(); - term.reset(); - eventIndex = 0; - pausedAt = 0; - updateTimeDisplay(0); - }); - - progressInput.addEventListener('input', () => { - seekTo(parseInt(progressInput.value, 10) / 1000); - }); - - speedSelect.addEventListener('change', () => { - const newSpeed = parseFloat(speedSelect.value); - if (playing) { - const realElapsed = (Date.now() - startRealTime) / 1000; - pausedAt = pausedAt + realElapsed * speed; - speed = newSpeed; - startRealTime = Date.now(); - if (playbackTimer) clearTimeout(playbackTimer); - scheduleNext(); - } else { - speed = newSpeed; - } - }); - - // Auto-play on open. - setTimeout(play, 200); - - // Close handler. - const close = () => { - if (playing) pause(); - term.dispose(); - backdrop.classList.remove('is-visible'); - setTimeout(() => backdrop.remove(), 200); - }; - - modal.querySelector('.modal-close').addEventListener('click', close); - } -} - -// --------------------------------------------------------------------------- -// Encrypted Export/Import UI -// --------------------------------------------------------------------------- - -class ExportImportUI { - constructor(app) { - this.app = app; - } - - /** - * Shows the encrypted export modal. - */ - async showExportModal() { - const profiles = this.app.state.profiles || []; - if (profiles.length === 0) { - this.app.notifications.info('No profiles to export.'); - return; - } - - const backdrop = createElement('div', 'modal-backdrop'); - const modal = createElement('div', 'modal modal-md'); - - let profileListHTML = profiles.map(p => ` - - `).join(''); - - modal.innerHTML = ` - - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => backdrop.classList.add('is-visible')); - - const close = () => { - backdrop.classList.remove('is-visible'); - setTimeout(() => backdrop.remove(), 200); - }; - - modal.querySelector('.modal-close').addEventListener('click', close); - modal.querySelector('.export-cancel').addEventListener('click', close); - - modal.querySelector('.export-confirm').addEventListener('click', async () => { - const password = modal.querySelector('.export-password').value; - const confirm = modal.querySelector('.export-password-confirm').value; - - if (!password) { - this.app.notifications.error('Please enter a password.'); - return; - } - if (password !== confirm) { - this.app.notifications.error('Passwords do not match.'); - return; - } - - const checkboxes = modal.querySelectorAll('.export-profile-list input[type="checkbox"]:checked'); - const selectedIds = Array.from(checkboxes).map(cb => cb.value); - - if (selectedIds.length === 0) { - this.app.notifications.error('Please select at least one profile.'); - return; - } - - try { - const path = await window.AlchementaryLink.wailsService.ExportProfilesEncrypted(selectedIds, password); - this.app.notifications.success(`Exported ${selectedIds.length} profile(s) to ${path}`); - close(); - } catch (err) { - if (String(err).includes('cancelled')) { - return; - } - this.app.notifications.error('Export failed: ' + err); - } - }); - } - - /** - * Shows the encrypted import modal. - */ - async showImportModal() { - const backdrop = createElement('div', 'modal-backdrop'); - const modal = createElement('div', 'modal modal-md'); - - modal.innerHTML = ` - - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => backdrop.classList.add('is-visible')); - - const close = () => { - backdrop.classList.remove('is-visible'); - setTimeout(() => backdrop.remove(), 200); - }; - - modal.querySelector('.modal-close').addEventListener('click', close); - modal.querySelector('.import-cancel').addEventListener('click', close); - - modal.querySelector('.import-confirm').addEventListener('click', async () => { - const password = modal.querySelector('.import-password').value; - const strategy = modal.querySelector('.import-strategy').value; - const status = modal.querySelector('.import-status'); - const confirmBtn = modal.querySelector('.import-confirm'); - status.textContent = ''; - - if (!password) { - status.textContent = 'Please enter the decryption password.'; - return; - } - - try { - confirmBtn.disabled = true; - confirmBtn.textContent = 'Importing...'; - const count = await window.AlchementaryLink.wailsService.ImportProfilesEncrypted(password, strategy); - this.app.notifications.success(`Imported ${count} profile(s)`); - await this.app.loadProfiles(); - close(); - } catch (err) { - if (String(err).includes('cancelled')) { - return; - } - status.textContent = this._formatImportError(err); - this.app.notifications.error('Import failed'); - } finally { - confirmBtn.disabled = false; - confirmBtn.textContent = 'Import'; - } - }); - } - - _formatImportError(err) { - const msg = String(err || 'Unknown import error'); - return msg.replace(/\s+/g, ' ').trim(); - } -} - -// --------------------------------------------------------------------------- -// Utility helpers -// --------------------------------------------------------------------------- - -function _esc(s) { - const el = document.createElement('span'); - el.textContent = s || ''; - return el.innerHTML; -} - -function _formatSeconds(secs) { - if (!secs || secs <= 0) return '0:00'; - const m = Math.floor(secs / 60); - const s = Math.floor(secs % 60); - return `${m}:${s.toString().padStart(2, '0')}`; -} - -function _formatBytes(bytes) { - if (!bytes || bytes <= 0) return '0 B'; - const units = ['B', 'KB', 'MB', 'GB']; - let i = 0; - let val = bytes; - while (val >= 1024 && i < units.length - 1) { - val /= 1024; - i++; - } - return `${val.toFixed(i === 0 ? 0 : 1)} ${units[i]}`; -} - -// --------------------------------------------------------------------------- -// Export to global namespace -// --------------------------------------------------------------------------- - -window.AlchementaryLink = window.AlchementaryLink || {}; -window.AlchementaryLink.RecordingManager = RecordingManager; -window.AlchementaryLink.ExportImportUI = ExportImportUI; - -})(); diff --git a/frontend/src/js/secrets.js b/frontend/src/js/secrets.js deleted file mode 100644 index 33f9840..0000000 --- a/frontend/src/js/secrets.js +++ /dev/null @@ -1,373 +0,0 @@ -(function() { -/** - * Alchementary Link — secret/passphrase prompt dialogs. - * Provides modal prompts for collecting passwords, master-vault passphrases, - * and SSH host-key verification confirmations. - */ - -const { createElement, escapeHtml } = window.AlchementaryLink; - -class SecretPrompt { - constructor(app) { - /** @type {App} */ - this.app = app; - - /** @type {HTMLElement|null} Currently open overlay. */ - this._overlay = null; - } - - // ── Generic password prompt ────────────────────────────────────── - - /** - * Displays a modal with a single password field and returns the entered value. - * Rejects if the user cancels. - * @param {string} title - Dialog heading. - * @param {string} message - Instructional text shown above the input. - * @returns {Promise} - */ - prompt(title, message) { - return new Promise((resolve, reject) => { - this._close(); // dismiss any existing prompt - - const overlay = createElement('div', 'modal-backdrop'); - const modal = createElement('div', 'modal modal-secret'); - - // Header - const header = createElement('div', 'modal-header'); - header.innerHTML = ` - - - `; - modal.appendChild(header); - - // Body - const body = createElement('div', 'modal-body'); - body.innerHTML = ` -

${escapeHtml(message)}

-
- -
- `; - modal.appendChild(body); - - // Footer - const footer = createElement('div', 'modal-footer'); - const cancelBtn = createElement('button', 'btn btn-secondary', { textContent: 'Cancel' }); - const submitBtn = createElement('button', 'btn btn-primary', { textContent: 'Submit' }); - footer.appendChild(cancelBtn); - footer.appendChild(submitBtn); - modal.appendChild(footer); - - overlay.appendChild(modal); - document.body.appendChild(overlay); - this._overlay = overlay; - - requestAnimationFrame(() => { - overlay.classList.add('is-visible'); - modal.classList.add('is-visible'); - }); - - const input = modal.querySelector('.secret-input'); - input.focus(); - - const handleSubmit = () => { - const value = input.value; - this._close(); - resolve(value); - }; - - const handleCancel = () => { - this._close(); - reject(new Error('User cancelled')); - }; - - submitBtn.addEventListener('click', handleSubmit); - cancelBtn.addEventListener('click', handleCancel); - header.querySelector('.modal-close').addEventListener('click', handleCancel); - overlay.addEventListener('click', (e) => { - if (e.target === overlay) handleCancel(); - }); - - input.addEventListener('keydown', (e) => { - if (e.key === 'Enter') handleSubmit(); - if (e.key === 'Escape') handleCancel(); - }); - }); - } - - // ── Master password (vault initialisation / unlock) ────────────── - - /** - * Prompts for the master vault password. - * When `isNew` is true, a confirmation field is shown. - * @param {boolean} isNew - Whether this is the first time (create vault). - * @returns {Promise} The master password. - */ - promptMasterPassword(isNew) { - return new Promise((resolve, reject) => { - this._close(); - - const overlay = createElement('div', 'modal-backdrop'); - const modal = createElement('div', 'modal modal-secret'); - - const title = isNew ? 'Create Master Password' : 'Unlock Secret Store'; - const description = isNew - ? 'Choose a strong master password. This protects all saved credentials.' - : 'Enter your master password to unlock the secret store.'; - - const header = createElement('div', 'modal-header'); - header.innerHTML = ` - - - `; - modal.appendChild(header); - - const body = createElement('div', 'modal-body'); - let formHtml = ` -

${escapeHtml(description)}

-
- - -
- `; - if (isNew) { - formHtml += ` -
-
- -
-
- - -
- - `; - } - body.innerHTML = formHtml; - modal.appendChild(body); - - const footer = createElement('div', 'modal-footer'); - const cancelBtn = createElement('button', 'btn btn-secondary', { textContent: 'Cancel' }); - const submitBtn = createElement('button', 'btn btn-primary', { textContent: isNew ? 'Create' : 'Unlock' }); - footer.appendChild(cancelBtn); - footer.appendChild(submitBtn); - modal.appendChild(footer); - - overlay.appendChild(modal); - document.body.appendChild(overlay); - this._overlay = overlay; - - requestAnimationFrame(() => { - overlay.classList.add('is-visible'); - modal.classList.add('is-visible'); - }); - - const passInput = modal.querySelector('.master-password'); - const confirmInput = modal.querySelector('.master-confirm'); - const errorEl = modal.querySelector('.secret-error'); - passInput.focus(); - - // Wire up the password strength meter for new vault creation. - if (isNew) { - const strengthFill = modal.querySelector('.strength-fill'); - const strengthLabel = modal.querySelector('.strength-label'); - passInput.addEventListener('input', () => { - const result = _measureStrength(passInput.value); - strengthFill.style.width = result.percent + '%'; - strengthFill.className = 'strength-fill strength-' + result.level; - strengthLabel.textContent = result.label; - strengthLabel.className = 'strength-label strength-' + result.level; - }); - } - - const handleSubmit = () => { - const pw = passInput.value; - if (!pw) { - passInput.classList.add('input-error'); - return; - } - if (isNew) { - const confirm = confirmInput.value; - if (pw !== confirm) { - errorEl.textContent = 'Passwords do not match.'; - errorEl.style.display = 'block'; - confirmInput.classList.add('input-error'); - return; - } - } - this._close(); - resolve(pw); - }; - - const handleCancel = () => { - this._close(); - reject(new Error('User cancelled')); - }; - - submitBtn.addEventListener('click', handleSubmit); - cancelBtn.addEventListener('click', handleCancel); - header.querySelector('.modal-close').addEventListener('click', handleCancel); - overlay.addEventListener('click', (e) => { - if (e.target === overlay) handleCancel(); - }); - - const handleKeydown = (e) => { - if (e.key === 'Enter') handleSubmit(); - if (e.key === 'Escape') handleCancel(); - }; - passInput.addEventListener('keydown', handleKeydown); - if (confirmInput) { - confirmInput.addEventListener('keydown', handleKeydown); - } - }); - } - - // ── SSH host-key verification ──────────────────────────────────── - - /** - * Asks the user whether to accept an SSH host key. - * @param {string} host - * @param {string} keyType - e.g. "ssh-ed25519" - * @param {string} fingerprint - SHA256 fingerprint - * @returns {Promise} true = accept, false = reject. - */ - promptHostKeyVerification(host, keyType, fingerprint) { - return new Promise((resolve) => { - this._close(); - - const overlay = createElement('div', 'modal-backdrop'); - const modal = createElement('div', 'modal modal-secret modal-hostkey'); - - const header = createElement('div', 'modal-header'); - header.innerHTML = ` - - - `; - modal.appendChild(header); - - const body = createElement('div', 'modal-body'); - body.innerHTML = ` -

- The authenticity of host ${escapeHtml(host)} cannot be established. -

-
-
- Key type - ${escapeHtml(keyType)} -
-
- Fingerprint - ${escapeHtml(fingerprint)} -
-
-

- Are you sure you want to continue connecting? -

- `; - modal.appendChild(body); - - const footer = createElement('div', 'modal-footer'); - const rejectBtn = createElement('button', 'btn btn-secondary', { textContent: 'Reject' }); - const acceptBtn = createElement('button', 'btn btn-primary', { textContent: 'Accept & Save' }); - footer.appendChild(rejectBtn); - footer.appendChild(acceptBtn); - modal.appendChild(footer); - - overlay.appendChild(modal); - document.body.appendChild(overlay); - this._overlay = overlay; - - requestAnimationFrame(() => { - overlay.classList.add('is-visible'); - modal.classList.add('is-visible'); - acceptBtn.focus(); - }); - - acceptBtn.addEventListener('click', () => { - this._close(); - resolve(true); - }); - - rejectBtn.addEventListener('click', () => { - this._close(); - resolve(false); - }); - - header.querySelector('.modal-close').addEventListener('click', () => { - this._close(); - resolve(false); - }); - - overlay.addEventListener('click', (e) => { - if (e.target === overlay) { - this._close(); - resolve(false); - } - }); - - overlay.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - this._close(); - resolve(false); - } - }); - }); - } - - // ── Internal helpers ───────────────────────────────────────────── - - /** - * Closes and removes the current overlay, if any. - * @private - */ - _close() { - if (this._overlay) { - this._overlay.remove(); - this._overlay = null; - } - } -} - -/** - * Estimates password strength based on length and character diversity. - * Returns an object with { percent, level, label } for the strength meter. - * @param {string} pw - * @returns {{ percent: number, level: string, label: string }} - * @private - */ -function _measureStrength(pw) { - if (!pw) return { percent: 0, level: 'none', label: '' }; - - let score = 0; - const len = pw.length; - - // Length contribution (0–40 points) - score += Math.min(len * 3, 40); - - // Character-class diversity (0–40 points) - const hasLower = /[a-z]/.test(pw); - const hasUpper = /[A-Z]/.test(pw); - const hasDigit = /[0-9]/.test(pw); - const hasSymbol = /[^a-zA-Z0-9]/.test(pw); - const classes = [hasLower, hasUpper, hasDigit, hasSymbol].filter(Boolean).length; - score += classes * 10; - - // Unique-character ratio bonus (0–20 points) - const unique = new Set(pw).size; - score += Math.min(Math.floor((unique / Math.max(len, 1)) * 20), 20); - - score = Math.min(score, 100); - - if (score < 25) return { percent: score, level: 'weak', label: 'Weak' }; - if (score < 50) return { percent: score, level: 'fair', label: 'Fair' }; - if (score < 75) return { percent: score, level: 'good', label: 'Good' }; - return { percent: score, level: 'strong', label: 'Strong' }; -} - -window.AlchementaryLink = window.AlchementaryLink || {}; -window.AlchementaryLink.SecretPrompt = SecretPrompt; - -})(); diff --git a/frontend/src/js/settings.js b/frontend/src/js/settings.js deleted file mode 100644 index 3bf6e37..0000000 --- a/frontend/src/js/settings.js +++ /dev/null @@ -1,1276 +0,0 @@ -(function () { - /** - * Alchementary Link — settings modal. - * Renders a tabbed settings dialog for application-wide preferences, - * terminal defaults, security, known-hosts management, and about info. - */ - - const { createElement, escapeHtml, formatTimestamp, $ } = - window.AlchementaryLink; - - /** Settings tab definitions. */ - const TABS = [ - { key: "general", label: "General" }, - { key: "terminal", label: "Terminal" }, - { key: "security", label: "Security" }, - { key: "knownhosts", label: "Known Hosts" }, - { key: "help", label: "Help" }, - { key: "about", label: "About" }, - ]; - - const KONAMI_SEQUENCE = [ - "ArrowUp", - "ArrowUp", - "ArrowDown", - "ArrowDown", - "ArrowLeft", - "ArrowRight", - "ArrowLeft", - "ArrowRight", - "b", - "a", - ]; - - class Settings { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - - /** @type {HTMLElement|null} */ - this._overlay = null; - - /** @type {string} Currently active tab key. */ - this._activeTab = "general"; - - /** @type {Object} Working copy of preferences. */ - this._prefs = {}; - - /** @type {Array} Cached known hosts list. */ - this._knownHosts = []; - - this._aboutKonamiHandler = null; - this._konamiIndex = 0; - this._konamiTogglePending = false; - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Opens the settings modal. - */ - async open(tabKey) { - this._prefs = JSON.parse(JSON.stringify(this.app.state.preferences)); - this._activeTab = tabKey || this._activeTab || "general"; - - try { - this._knownHosts = (await window.AlchementaryLink.wailsService.ListKnownHosts()) || []; - } catch (_) { - this._knownHosts = []; - } - - try { - this._defaultShell = await window.AlchementaryLink.wailsService.GetDefaultShell(); - } catch (_) { - this._defaultShell = ""; - } - - this._render(); - } - - /** - * Opens the settings modal on a specific tab. - * @param {string} tabKey - */ - async openTab(tabKey) { - this._activeTab = tabKey || "general"; - return this.open(this._activeTab); - } - - /** - * Closes the settings modal. - */ - close() { - this._detachAboutKonamiListener(); - if (this._overlay) { - this._overlay.remove(); - this._overlay = null; - } - } - - /** - * Saves the modified preferences to the backend. - */ - async save() { - this._collectValues(); - - try { - await window.AlchementaryLink.wailsService.SavePreferences(this._prefs); - this.app.state.setPreferences(this._prefs); - - // Apply terminal colour theme change immediately. - if (this.app.terminals && this._prefs.terminalTheme) { - this.app.terminals.applyTheme(this._prefs.terminalTheme); - } - if (this.app && typeof this.app._applyPreferences === "function") { - this.app._applyPreferences(); - } - - this.app.notifications.success("Settings saved"); - this.close(); - } catch (err) { - this.app.notifications.error("Failed to save settings: " + err); - } - } - - // ── Rendering ──────────────────────────────────────────────────── - - /** @private */ - _render() { - this.close(); - - const overlay = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-settings"); - - // Header - const header = createElement("div", "modal-header"); - header.innerHTML = ` - - - `; - header - .querySelector(".modal-close") - .addEventListener("click", () => this.close()); - modal.appendChild(header); - - // Tab bar - const tabBar = createElement("div", "settings-tab-bar"); - for (const tab of TABS) { - const btn = createElement( - "button", - `settings-tab ${tab.key === this._activeTab ? "active" : ""}`, - { - textContent: tab.label, - dataset: { tab: tab.key }, - }, - ); - btn.addEventListener("click", () => { - this._activeTab = tab.key; - this._renderBody(modal); - tabBar.querySelectorAll(".settings-tab").forEach((t) => { - t.classList.toggle("active", t.dataset.tab === tab.key); - }); - }); - tabBar.appendChild(btn); - } - modal.appendChild(tabBar); - - // Body (tab content) - const body = createElement("div", "modal-body settings-body"); - modal.appendChild(body); - this._renderTabContent(body); - - // Footer - const footer = createElement("div", "modal-footer"); - const cancelBtn = createElement("button", "btn btn-secondary", { - textContent: "Cancel", - }); - const saveBtn = createElement("button", "btn btn-primary", { - textContent: "Save", - }); - cancelBtn.addEventListener("click", () => this.close()); - saveBtn.addEventListener("click", () => this.save()); - footer.appendChild(cancelBtn); - footer.appendChild(saveBtn); - modal.appendChild(footer); - - overlay.appendChild(modal); - overlay.addEventListener("click", (e) => { - if (e.target === overlay) this.close(); - }); - modal.addEventListener("click", (e) => e.stopPropagation()); - document.body.appendChild(overlay); - this._overlay = overlay; - - // Trigger visibility after append so CSS transitions animate - requestAnimationFrame(() => { - overlay.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - } - - /** - * Re-renders only the body when the active tab changes. - * @param {HTMLElement} modal - * @private - */ - _renderBody(modal) { - const body = modal.querySelector(".settings-body"); - if (body) this._renderTabContent(body); - } - - /** - * Renders the content for the currently active tab. - * @param {HTMLElement} body - * @private - */ - _renderTabContent(body) { - this._detachAboutKonamiListener(); - body.innerHTML = ""; - body.classList.remove("help-layout"); - switch (this._activeTab) { - case "general": - this._renderGeneralTab(body); - break; - case "terminal": - this._renderTerminalTab(body); - break; - case "security": - this._renderSecurityTab(body); - break; - case "knownhosts": - this._renderKnownHostsTab(body); - break; - case "help": - this._renderHelpTab(body); - break; - case "about": - this._renderAboutTab(body); - break; - } - } - - // ── Tab content renderers ──────────────────────────────────────── - - /** - * General preferences tab. - * @param {HTMLElement} body - * @private - */ - _renderGeneralTab(body) { - const p = this._prefs; - body.appendChild( - this._settingField("select", "theme", "Theme", p.theme, { - options: [ - { value: "dark", label: "Dark" }, - { value: "light", label: "Light" }, - ], - }), - ); - body.appendChild( - this._settingField( - "select", - "defaultProtocol", - "Default Protocol", - p.defaultProtocol, - { - options: [ - { value: "ssh", label: "SSH" }, - { value: "telnet", label: "Telnet" }, - { value: "raw", label: "RAW" }, - ], - }, - ), - ); - body.appendChild( - this._settingField( - "toggle", - "confirmOnClose", - "Confirm on close", - p.confirmOnClose, - ), - ); - body.appendChild( - this._settingField( - "toggle", - "confirmOnDisconnect", - "Confirm on disconnect", - p.confirmOnDisconnect, - ), - ); - body.appendChild( - this._settingField( - "toggle", - "showStatusBar", - "Show status bar", - p.showStatusBar, - ), - ); - body.appendChild( - this._settingField( - "number", - "sidebarWidth", - "Sidebar width (px)", - p.sidebarWidth, - { min: 180, max: 500 }, - ), - ); - - // Local shell path with browse button - const shellGroup = createElement("div", "form-group settings-field"); - const shellLabel = createElement("label", "form-label", { - textContent: "Local shell", - for: "setting-localShell", - }); - shellGroup.appendChild(shellLabel); - - const shellRow = createElement("div", "settings-shell-row"); - const shellInput = createElement("input", "form-input", { - type: "text", - id: "setting-localShell", - name: "setting-localShell", - value: p.localShell || "", - placeholder: this._defaultShell || "/bin/zsh", - }); - shellRow.appendChild(shellInput); - - const browseBtn = createElement("button", "btn btn-sm btn-secondary", { - textContent: "Browse…", - type: "button", - }); - browseBtn.addEventListener("click", async () => { - try { - const path = await window.AlchementaryLink.wailsService.BrowseForShell(); - if (path) { - shellInput.value = path; - } - } catch (_) {} - }); - shellRow.appendChild(browseBtn); - shellGroup.appendChild(shellRow); - - const shellHint = createElement("span", "form-hint", { - textContent: "Leave empty to use system default", - }); - shellGroup.appendChild(shellHint); - body.appendChild(shellGroup); - } - - /** - * Terminal defaults tab. - * @param {HTMLElement} body - * @private - */ - _renderTerminalTab(body) { - const p = this._prefs; - - // Terminal colour theme selector - const themes = window.AlchementaryLink.listTerminalThemes(); - body.appendChild( - this._settingField( - "select", - "terminalTheme", - "Colour scheme", - p.terminalTheme || "charcoal", - { - options: themes, - }, - ), - ); - - body.appendChild( - this._settingField( - "text", - "defaultFontFamily", - "Font family", - p.defaultFontFamily, - { - placeholder: "JetBrains Mono, Menlo, Monaco, Consolas, monospace", - }, - ), - ); - body.appendChild( - this._settingField( - "number", - "defaultFontSize", - "Font size", - p.defaultFontSize, - { min: 8, max: 36 }, - ), - ); - body.appendChild( - this._settingField( - "number", - "defaultScrollback", - "Scrollback lines", - p.defaultScrollback, - { min: 0, max: 100000 }, - ), - ); - body.appendChild( - this._settingField( - "select", - "defaultCursorStyle", - "Cursor style", - p.defaultCursorStyle, - { - options: [ - { value: "block", label: "Block" }, - { value: "underline", label: "Underline" }, - { value: "bar", label: "Bar" }, - ], - }, - ), - ); - body.appendChild( - this._settingField( - "toggle", - "useNativePaste", - "Use native paste", - p.useNativePaste === true, - ), - ); - } - - /** - * Security settings tab. - * @param {HTMLElement} body - * @private - */ - _renderSecurityTab(body) { - const storeState = this.app.state.secretStoreUnlocked - ? "Unlocked" - : "Locked"; - const storeInit = this.app.state.secretStoreInitialised; - - const statusRow = createElement("div", "settings-row"); - statusRow.innerHTML = ` - Secret store - ${storeState} - `; - body.appendChild(statusRow); - - if (!storeInit) { - const initBtn = createElement("button", "btn btn-primary btn-sm", { - textContent: "Initialise Secret Store", - }); - initBtn.addEventListener("click", async () => { - try { - const pw = await this.app.secretPrompt.promptMasterPassword(true); - await window.AlchementaryLink.wailsService.InitialiseSecretStore(pw); - this.app.state.setSecretStoreInitialised(true); - this.app.state.setSecretStoreState(true); - this.app.notifications.success("Secret store created"); - this._renderTabContent(body); - } catch (err) { - if (err.message !== "User cancelled") { - this.app.notifications.error("Failed to initialise: " + err); - } - } - }); - body.appendChild(initBtn); - } else if (!this.app.state.secretStoreUnlocked) { - const unlockBtn = createElement("button", "btn btn-primary btn-sm", { - textContent: "Unlock", - }); - unlockBtn.addEventListener("click", async () => { - try { - const pw = await this.app.secretPrompt.promptMasterPassword(false); - await window.AlchementaryLink.wailsService.UnlockSecretStore(pw); - this.app.state.setSecretStoreState(true); - this.app.notifications.success("Secret store unlocked"); - this._renderTabContent(body); - } catch (err) { - if (err.message !== "User cancelled") { - this.app.notifications.error("Unlock failed: " + err); - } - } - }); - body.appendChild(unlockBtn); - } else { - const lockBtn = createElement("button", "btn btn-outline btn-sm", { - textContent: "Lock", - }); - lockBtn.addEventListener("click", async () => { - try { - await window.AlchementaryLink.wailsService.LockSecretStore(); - this.app.state.setSecretStoreState(false); - this.app.notifications.info("Secret store locked"); - this._renderTabContent(body); - } catch (err) { - this.app.notifications.error("Lock failed: " + err); - } - }); - body.appendChild(lockBtn); - } - } - - /** - * Known-hosts management tab. - * @param {HTMLElement} body - * @private - */ - _renderKnownHostsTab(body) { - if (this._knownHosts.length === 0) { - body.appendChild( - createElement("p", "text-muted", { - textContent: "No known hosts recorded.", - }), - ); - return; - } - - const table = createElement("table", "settings-table"); - table.innerHTML = ` - - - Host - Port - Key Type - Fingerprint - First Seen - - - - `; - const tbody = createElement("tbody"); - - for (const kh of this._knownHosts) { - const tr = createElement("tr"); - tr.innerHTML = ` - ${escapeHtml(kh.host)} - ${kh.port} - ${escapeHtml(kh.keyType)} - ${escapeHtml(kh.fingerprint.slice(0, 20))}… - ${formatTimestamp(kh.firstSeen)} - - `; - const deleteBtn = createElement("button", "btn btn-xs btn-danger", { - textContent: "Remove", - }); - deleteBtn.addEventListener("click", async () => { - try { - await window.AlchementaryLink.wailsService.DeleteKnownHost(kh.id); - this._knownHosts = this._knownHosts.filter((h) => h.id !== kh.id); - this._renderTabContent(body); - this.app.notifications.success("Known host removed"); - } catch (err) { - this.app.notifications.error("Failed to remove: " + err); - } - }); - tr.querySelector("td:last-child").appendChild(deleteBtn); - tbody.appendChild(tr); - } - - table.appendChild(tbody); - body.appendChild(table); - } - - /** - * In-app help and documentation tab. - * @param {HTMLElement} body - * @private - */ - _renderHelpTab(body) { - const HELP_SECTIONS = [ - { - id: "getting-started", - title: "Getting Started", - content: ` -

Welcome to Alchementary Link — a desktop application for SSH, Telnet, RAW TCP, VNC, GitHub, local terminal, and Leviathan binary-analysis workflows.

-

Creating Your First Connection

-
    -
  1. Click the + button at the top of the sidebar, or press ⌘N (Mac) / Ctrl+N (Windows/Linux).
  2. -
  3. Choose a protocol (SSH, Telnet, RAW, VNC, or GitHub).
  4. -
  5. Fill in the hostname, port, and any credentials required.
  6. -
  7. Optionally assign the connection to a group to keep your sidebar organised.
  8. -
  9. Click Save, then double-click the entry in the sidebar to connect.
  10. -
-

Leviathan Tools

-

Click Leviathan beside Alchementary Link in the sidebar header to switch from connection profiles to binary-analysis tools. Use Load to choose a local file, then open tools such as Hex Editor, Overview, Entropy, Carve, BinSnoop, Structures, and Spectrogram View.

-

Quick Connect

-

Need to connect quickly without saving a profile? Press ⌘⇧Q / Ctrl+Shift+Q to open the quick connect dialog. Choose SSH, Telnet, or RAW, enter the host and port, and you're connected immediately.

-

Managing Connections

-

Right-click any connection in the sidebar to edit, duplicate, or delete it. You can drag connections between groups, and use the search box to filter by name, host, or tag.

- `, - }, - { - id: "ssh", - title: "SSH Connections", - content: ` -

SSH (Secure Shell) is the most common way to connect to remote servers. Alchementary Link supports:

-
    -
  • Password authentication — enter your password when prompted, or save it in the encrypted vault.
  • -
  • Public key authentication — point to your private key file (e.g. ~/.ssh/id_rsa). If the key has a passphrase, you will be prompted.
  • -
  • Keyboard-interactive — for servers that use challenge-response (e.g. two-factor prompts).
  • -
  • SSH agent — tick "Use SSH Agent" to authenticate via your system's SSH agent (e.g. ssh-agent or 1Password).
  • -
-

TOTP / 2FA Auto-Fill

-

If your server requires a time-based one-time password (TOTP) during login, you can store the TOTP secret in the profile editor under the TOTP / 2FA section. When connecting, Alchementary Link will detect 2FA prompts (such as "Verification code" or "OTP") and automatically generate and submit a 6-digit code. Use the Test button to verify your secret generates valid codes before connecting.

-

Jump Hosts (Bastions)

-

If you need to reach a server through an intermediate host, add one or more jump hosts in the connection editor. Alchementary Link will tunnel through each hop automatically.

-

Port Forwarding

-

You can set up local, remote, and dynamic (SOCKS proxy) port forwards in the connection editor. These start automatically when the session connects.

-

Proxy Support

-

If you need to connect through a proxy, open the Proxy section in the profile editor. Alchementary Link supports SOCKS5 and HTTP CONNECT proxies, with optional username/password authentication. The proxy is used to reach the SSH server (or the first jump host if jump hosts are configured).

-

SFTP File Browser

-

Once connected to an SSH server, click the 📁 icon on the tab to open the SFTP file browser. You can browse directories, upload, download, rename, and delete files from a side panel.

-

SSH Agent Forwarding

-

Tick "Agent Forwarding" in the SSH settings to forward your local SSH agent to the remote server. This lets you use your local keys when connecting onward from that server.

-

Auto-Login Scripts

-

Define expect-style login scripts to automate interactions after connecting. In the Login Script section of the profile editor, add a sequence of Wait for (match text from the server) and Send (send text + newline) steps. For example:

-
    -
  1. Wait for: login:
  2. -
  3. Send: admin
  4. -
  5. Wait for: Password:
  6. -
  7. Send: mysecretpassword
  8. -
-

Each wait step has a configurable timeout (default 10 seconds). Steps execute in order after the connection is established.

-

Auto-Reconnect

-

Enable Auto-Reconnect in the Advanced section to automatically reconnect when the connection drops unexpectedly. Reconnection uses exponential backoff (1s, 2s, 4s, 8s... up to 30s). You can configure the maximum number of attempts (default 5).

- `, - }, - { - id: "telnet", - title: "Telnet Connections", - content: ` -

Telnet is an older, unencrypted protocol still used for network equipment, legacy systems, and some fun internet services.

-
    -
  • Option negotiation — Alchementary Link handles standard Telnet options (echo, terminal type, window size) automatically.
  • -
  • Character encoding — choose UTF-8, ASCII, or Latin-1 for the connection.
  • -
  • Local echo — enable this if the remote host does not echo back what you type.
  • -
  • Line mode — send data line-by-line (press Enter to send) rather than character-by-character.
  • -
  • Auto-login scripts — same expect-style send/wait sequences as SSH (see the Login Script section).
  • -
  • Auto-reconnect — automatically reconnect with exponential backoff when the connection drops.
  • -
-

Note: Telnet sends everything in plain text. Avoid using it for anything sensitive over an untrusted network.

- `, - }, - { - id: "raw", - title: "RAW TCP Connections", - content: ` -

RAW TCP lets you connect to any TCP port and exchange data directly — useful for debugging network services, testing APIs, or working with custom protocols.

-
    -
  • Text view — shows incoming data as readable text.
  • -
  • Hex view — shows raw bytes in a hex dump format.
  • -
  • Split view — text and hex side by side.
  • -
  • Payload presets — save frequently-used commands or data packets for quick sending.
  • -
  • Line endings — choose CR, LF, or CRLF to match the remote service.
  • -
  • Auto-reconnect — automatically reconnect if the connection drops.
  • -
- `, - }, - { - id: "vnc", - title: "VNC Connections", - content: ` -

VNC profiles open a remote desktop viewer inside the Link workspace.

-
    -
  • Password handling — ask each time, use the session only, or save the password in the encrypted secret store.
  • -
  • View-only mode — observe a desktop without sending mouse or keyboard input.
  • -
  • Scale-to-fit — resize the remote desktop to fit the available workspace.
  • -
  • Shared session — request a shared VNC session where the server supports it.
  • -
  • Quality — tune image quality for slower links.
  • -
  • SSH tunnel — carry VNC traffic through an SSH connection using password, public key, or SSH agent authentication.
  • -
-

Prefer SSH tunnel for VNC outside trusted networks. Direct VNC is supported for isolated or trusted networks, but many classic VNC deployments do not provide strong transport encryption.

- `, - }, - { - id: "github", - title: "GitHub Connections", - content: ` -

The GitHub connection type lets you browse your repositories without leaving Alchementary Link.

-

Setting Up

-
    -
  1. Create a new connection and choose GitHub as the protocol.
  2. -
  3. Enter your Personal Access Token (create one at GitHub → Settings → Developer settings → Personal access tokens). The token needs the repo scope.
  4. -
  5. Save and connect.
  6. -
-

What You'll See

-

Your repositories are listed grouped by organisation or owner, sorted by last commit date (newest first). The list refreshes automatically every few minutes.

- `, - }, - { - id: "leviathan", - title: "Leviathan Binary Tools", - content: ` -

Leviathan is the built-in binary-analysis and editing workbench. It is useful for firmware images, unknown binaries, archives, captures, embedded files, documents, and audio spectrogram experiments.

-

Opening and Saving Files

-

Switch to Leviathan in the sidebar header, click Load, and select a local file. The file becomes the active working document. Use Save to write the patched working copy to a destination you choose.

-

Leviathan tools open in normal tabs beside terminal and VNC sessions. They close immediately without a disconnect confirmation.

-

Hex Editor

-
    -
  • Edit byte cells directly; two hex digits commit the byte and move to the next cell.
  • -
  • The ASCII preview updates while editing.
  • -
  • Switch between 16-byte and 32-byte rows.
  • -
  • Search for strings or hex values and move through matches with previous/next.
  • -
  • Paste runs such as de ad be ef or deadbeef into consecutive cells.
  • -
  • Use Undo, Redo, and Revert from the Leviathan panel header.
  • -
-

Triage Tools

-

Overview, Hashes, Entropy, Strings, IOCs, Search, and Multi-search provide a first pass over the current document. The Entropy tool renders the whole-file graph and lets you open selected ranges in the Hex Editor.

-

Structure and Recovery Tools

-

Structures, Disasm, PDF, SquashFS, Carve, Keys, Timestamps, Stego, Metadata, BinSnoop, and Rules help identify embedded formats, files, signatures, strings, keys, and suspicious structures. Carve and BinSnoop can export discovered files or ranges.

-

Edit, Transform, and Case Tools

-

Patch Plan validates patch scripts, Patch Apply applies them, Transforms runs hex/Base64/URL/XOR/inflate/cipher pipelines, and Case Export packages investigation artefacts.

-

Spectrogram Tools

-

Spectrogram Encode turns an image into WAV audio or inserts an encoded image into an existing WAV/MP3 carrier. Spectrogram View visualises WAV or MP3 audio as a spectrogram image with controls for FFT size, height, aspect ratio, frequency range, brightness, and contrast.

-

Large Files

-

Leviathan keeps loaded files file-backed where possible and saves patched working copies in chunks. Tools that require complete image, audio, PDF, or other parser buffers enforce explicit size limits and show a clear message if an input is too large.

- `, - }, - { - id: "sessions", - title: "Sessions & Tabs", - content: ` -

Tab Management

-
    -
  • Rename — right-click a tab and choose "Rename".
  • -
  • Reorder — drag tabs left or right to rearrange.
  • -
  • Pin — right-click and pin a tab so it stays at the left of the bar.
  • -
  • Scroll — use the tab bar scrollbar or mouse wheel/trackpad over the tab strip when many tabs are open.
  • -
  • Hover hints — hover a tab to see its full name and connected-since time where available.
  • -
  • Close — click the × on the left side of a tab, or press ⌘W / Ctrl+W. Leviathan tabs close without a disconnect confirmation.
  • -
-

Split Panes

-

You can split the terminal into multiple panes:

-
    -
  • ⌘⇧D / Ctrl+Shift+D — split vertically (side by side).
  • -
  • ⌘⇧E / Ctrl+Shift+E — split horizontally (top and bottom).
  • -
-

Drag the divider between panes to resize them.

-

Broadcast Input

-

Press ⌘⇧B / Ctrl+Shift+B to toggle broadcast mode. When active, everything you type is sent to all connected sessions simultaneously — perfect for running the same command across multiple servers. The broadcast button in the tab bar pulses red to remind you it's active.

-

Session Recording

-

Press ⌘⇧R / Ctrl+Shift+R to start recording a session. A red dot will appear on the tab. Press the same shortcut again to stop. Browse and replay recordings from the ⏺ button in the sidebar.

-

Right-Click Context Menu

-

Right-click in the terminal to access a context menu with:

-
    -
  • Copy — copy selected text to the clipboard
  • -
  • Paste — paste from the clipboard into the terminal
  • -
  • Select All — select all terminal content
  • -
  • Find — open the terminal search overlay
  • -
  • Send Snippet — quickly send a saved command from the snippet library
  • -
  • Clear / Reset — clear the terminal buffer
  • -
- `, - }, - { - id: "power", - title: "Power Features", - content: ` -

Snippet Library

-

Press ⌘⇧S / Ctrl+Shift+S to open the snippet library. Save frequently-used commands with a name, description, and optional folder and tags. Click Send to Session to inject a command into the active terminal. You can also access snippets from the right-click context menu.

-

Terminal Search

-

Press ⌘F / Ctrl+F to open the search overlay on the active terminal. Features include:

-
    -
  • Aa button — toggle case-sensitive matching
  • -
  • .* button — toggle regular expression mode
  • -
  • Match count indicator showing current/total matches
  • -
  • Navigate matches with the up/down arrows or Enter / Shift+Enter
  • -
-

Quick Connect

-

Press ⌘⇧Q / Ctrl+Shift+Q to open a lightweight connect dialog. Choose SSH, Telnet, or RAW; enter the host and port; and connect immediately without creating a saved profile.

-

Auto-Login Scripts

-

For SSH and Telnet connections, you can define expect-style login scripts — a sequence of "wait for text" and "send text" steps that execute automatically after connecting. Configure them in the Login Script section of the profile editor.

-

Auto-Reconnect

-

Enable auto-reconnect in a profile's settings to automatically reconnect when a connection drops. Uses exponential backoff with a configurable maximum number of attempts.

- `, - }, - { - id: "security", - title: "Security & Credentials", - content: ` -

How Your Passwords Are Stored

-

Alchementary Link uses a two-tier approach to keep your credentials safe:

-
    -
  1. OS keychain (preferred) — your operating system's built-in credential store (macOS Keychain, Windows Credential Manager, or Linux Secret Service). If available, this is used automatically.
  2. -
  3. Encrypted vault (fallback) — if the OS keychain is not available, Alchementary Link creates a local vault encrypted with AES-256-GCM. You set a master password to unlock it.
  4. -
-

Encryption Details

-

The vault uses Argon2id to derive the encryption key from your master password (128 MiB memory, making brute-force attacks very expensive). Each secret is individually encrypted and never stored in plain text.

-

TOTP / 2FA Secrets

-

TOTP secrets for SSH two-factor authentication are stored alongside your other credentials — encrypted in the OS keychain or the file vault. They are never saved in the profile database in plain text.

-

Known Hosts

-

When you connect to an SSH server for the first time, Alchementary Link records the server's fingerprint. If the fingerprint changes on a subsequent connection, you'll be warned — this protects against man-in-the-middle attacks.

-

You can manage known hosts and change the verification policy in Settings → Known Hosts.

-

Encrypted Export

-

When exporting profiles, you can choose encrypted export (.link file) which protects the file with a password. This uses the same strong AES-256-GCM encryption as the vault.

- `, - }, - { - id: "shortcuts", - title: "Keyboard Shortcuts", - content: ` -

On macOS, use ⌘ (Cmd). On Windows and Linux, use Ctrl.

- - - - - - - - - - - - - - - - - - -
⌘KOpen the command palette
⌘NNew connection
⌘WClose the active tab
⌘TabNext tab
⌘⇧TabPrevious tab
⌘1–⌘9Switch to tab by number
⌘,Open Settings
⌘FSearch in terminal
⌘⇧BToggle broadcast input mode
⌘⇧DSplit pane vertically
⌘⇧ESplit pane horizontally
⌘⇧QQuick connect (no saved profile)
⌘⇧RToggle session recording
⌘⇧SOpen snippet library
F11Toggle fullscreen (Windows/Linux)
- `, - }, - { - id: "import-export", - title: "Import & Export", - content: ` -

Exporting Profiles

-

Click the ↗ export button in the sidebar footer. You can choose which profiles to include, and set a password to encrypt the file. Exported files use the .link extension.

-

Importing Profiles

-

Click the ↙ import button. Select a .link file and enter the password used when exporting. Imported profiles will appear in your sidebar immediately.

-

Importing from OpenSSH

-

If you have an existing ~/.ssh/config file, go to Settings → About and click Import ~/.ssh/config. Alchementary Link will create profiles from your SSH Host blocks, including hostnames, usernames, ports, identity files, and jump hosts.

- `, - }, - { - id: "troubleshooting", - title: "Troubleshooting", - content: ` -

I can't connect to my server

-
    -
  • Double-check the hostname and port — a common mistake is using port 22 for Telnet (which is usually 23) or vice versa.
  • -
  • Make sure the server is reachable from your machine. Try ping hostname from another terminal.
  • -
  • If using SSH keys, check that the key file path is correct and that the file permissions are not too open (should be 600 or 400).
  • -
  • If you're behind a firewall or VPN, ensure the relevant port is allowed through.
  • -
-

SSH connection fails through a proxy

-
    -
  • Check the proxy host, port, and type (SOCKS5 or HTTP CONNECT) are correct in the profile's Proxy section.
  • -
  • If the proxy requires authentication, make sure the username and password are filled in.
  • -
  • SOCKS5 defaults to port 1080; HTTP CONNECT defaults to port 8080.
  • -
  • Some proxies may block SSH traffic — check with your network administrator.
  • -
-

TOTP/2FA code is rejected

-
    -
  • Ensure the TOTP secret is correct (base32-encoded, as shown during authenticator app setup).
  • -
  • Check that your system clock is accurate — TOTP codes are only valid for 30-second windows.
  • -
  • Use the Test button in the profile's TOTP section to verify the secret generates valid codes.
  • -
-

Login script times out

-
    -
  • Each "wait" step has a configurable timeout (default 10 seconds). Increase it in the profile editor if the server takes longer.
  • -
  • Check that the wait pattern matches exactly — it's a plain text substring match, not a regular expression.
  • -
  • Make sure the server actually sends the expected text. Some servers may use different prompts depending on locale or configuration.
  • -
-

Auto-reconnect keeps failing

-
    -
  • Auto-reconnect only works after a successful initial connection.
  • -
  • If the server is genuinely unreachable, attempts will exhaust after the configured maximum (default 5).
  • -
  • Check the reconnection settings in the profile's Advanced section.
  • -
-

VNC will not connect

-
    -
  • Check the VNC host and port. The common port is 5900 plus the display number.
  • -
  • If a password is required, check whether the profile is set to saved, session-only, or ask-every-time mode.
  • -
  • If using an SSH tunnel, verify the tunnel host, username, authentication method, private key, and host-key policy.
  • -
  • If using direct VNC, test from a trusted network and prefer SSH tunnelling on untrusted networks.
  • -
-

The terminal looks garbled or the wrong size

-
    -
  • Try resizing the window — the terminal should re-fit automatically.
  • -
  • If you're using split panes, drag the divider to give the terminal more space.
  • -
  • Some older servers may not support modern terminal features. Try a different terminal theme in Settings → Terminal.
  • -
-

Leviathan cannot load, save, or run a tool

-
    -
  • Check that the selected file still exists and that your account can read it.
  • -
  • When saving, choose a writable destination and avoid files that are locked by another process.
  • -
  • Some tools have explicit size limits because their parsers or decoders need a complete buffer. Use a smaller file or selected range when available.
  • -
  • If SquashFS reports bad magic, the file is probably not a SquashFS image; inspect the header in Hex Editor or try Overview/BinSnoop first.
  • -
-

"Host key verification failed"

-

This means the server's fingerprint has changed since your last connection. This can happen if the server was reinstalled, but it could also indicate a security threat. If you're certain it's safe, go to Settings → Known Hosts, find the entry, and remove it. You'll be prompted to accept the new key on your next connection.

-

I forgot my vault master password

-

Unfortunately, the vault encryption is designed so that without the master password, the stored credentials cannot be recovered. You would need to delete the vault file and set up a new one. Your connection profiles (without saved passwords) will remain intact.

-

SFTP panel doesn't open

-

SFTP is only available for SSH connections (not Telnet, RAW, or GitHub). Make sure you are connected to an SSH session first, then click the 📁 icon on the tab.

-

The application looks blurry on my display

-

Make sure your system display scaling is set correctly. On macOS, Alchementary Link is fully Retina-aware. On Windows, check your DPI settings in Display preferences.

-

Export/import isn't working

-

Ensure you're using the same password to import that was used to export. The encrypted .link files cannot be opened with the wrong password.

- `, - }, - { - id: "fun", - title: "Fun Things to Try", - content: ` -

Alchementary Link isn't just for work! Here are some entertaining things you can connect to:

-

🌟 Star Wars in ASCII Art

-

Create a Telnet connection to towel.blinkenlights.nl on port 23. Sit back and enjoy the original Star Wars film retold entirely in ASCII art.

-

🗺️ MapSCII — World Map in Your Terminal

-

Connect via SSH to mapscii.me on port 22. Use the arrow keys to pan and a/z to zoom in and out of a fully interactive world map rendered in text.

-

🎮 Telnet BBS Systems

-

Connect via Telnet to particlesbbs.dyndns.org on port 23 for a retro bulletin board system experience, complete with door games, message boards, and ASCII art.

-

🐄 Fortune Cookies

-

Connect via Telnet to djxmmx.net on port 17 (the Quote of the Day protocol). You'll receive a random quote each time you connect — great for testing RAW TCP connections too.

-

🔒 Test Your SSH Setup

-

Connect via SSH to ssh.github.com on port 443 (or github.com on port 22) with your GitHub username. GitHub will greet you by name if your key is set up correctly — a quick way to verify your SSH configuration.

-

📡 RAW TCP Experiments

-

Create a RAW TCP connection to any HTTP server on port 80, then type GET / HTTP/1.0 followed by two blank lines. You'll see the raw HTTP response — a great way to understand how the web works under the hood.

-

📢 Broadcast Mode Fun

-

Open two or more sessions (e.g. to different servers) and press ⌘⇧B / Ctrl+Shift+B to turn on broadcast mode. Everything you type goes to all sessions at once — try running hostname and watch them all respond with different names!

- `, - }, - ]; - - body.innerHTML = ""; - - // Navigation sidebar - body.classList.add("help-layout"); - var nav = createElement("div", "help-nav"); - var content = createElement("div", "help-content"); - - for (var i = 0; i < HELP_SECTIONS.length; i++) { - var section = HELP_SECTIONS[i]; - var navItem = createElement("button", "help-nav-item", { - textContent: section.title, - dataset: { section: section.id }, - }); - navItem.addEventListener("mousedown", function (e) { - e.preventDefault(); - }); - (function (sec) { - navItem.addEventListener("click", function () { - nav.querySelectorAll(".help-nav-item").forEach(function (n) { - n.classList.remove("active"); - }); - this.classList.add("active"); - content.innerHTML = - '

' + - sec.title + - "

" + - sec.content; - }); - })(section); - nav.appendChild(navItem); - } - - body.appendChild(nav); - body.appendChild(content); - - // Show first section by default - if (nav.firstChild) { - nav.firstChild.classList.add("active"); - content.innerHTML = - '

' + - HELP_SECTIONS[0].title + - "

" + - HELP_SECTIONS[0].content; - } - } - - /** - * About tab showing application info. - * @param {HTMLElement} body - * @private - */ - _renderAboutTab(body) { - body.innerHTML = ` -
-

Alchementary Link

-

Version …

-

Developed by Andy Dixon - alchementary.co.uk

- -
- - -
-
-
- `; - - const versionEl = body.querySelector("[data-role='app-version']"); - if (versionEl) { - if (window.AlchementaryLink?.wailsService) { - window.AlchementaryLink.wailsService.GetAppInfo() - .then((info) => { - const version = (info && info.version) || "dev"; - versionEl.textContent = `Version ${version}`; - }) - .catch(() => { - versionEl.textContent = "Version unavailable"; - }); - } else { - versionEl.textContent = "Version unavailable"; - } - } - - const updateBtn = body.querySelector("#settings-check-update"); - if (updateBtn) { - updateBtn.addEventListener("click", async () => { - updateBtn.disabled = true; - updateBtn.textContent = "Checking…"; - try { - const info = await window.AlchementaryLink.wailsService.CheckForUpdates(); - const resultEl = body.querySelector("#settings-update-result"); - if (this.app && typeof this.app.setUpdateInfo === "function") { - this.app.setUpdateInfo(info); - } - if (info.available) { - resultEl.textContent = ""; - const updateText = createElement("span"); - updateText.style.color = "var(--accent)"; - updateText.appendChild(document.createTextNode("Update available: ")); - const versionText = createElement("strong"); - versionText.textContent = String(info.latestVersion || ""); - updateText.appendChild(versionText); - resultEl.appendChild(updateText); - if (info.releaseURL) { - resultEl.appendChild(document.createTextNode(" - ")); - const releaseLink = createElement("a", "", { - href: "#", - textContent: "View release", - }); - releaseLink.style.color = "var(--accent)"; - releaseLink.addEventListener("click", (e) => { - e.preventDefault(); - if (window.runtime && window.runtime.BrowserOpenURL) { - window.runtime.BrowserOpenURL(String(info.releaseURL)); - } - }); - resultEl.appendChild(releaseLink); - } - } else { - resultEl.textContent = "You are running the latest version."; - } - } catch (err) { - const resultEl = body.querySelector("#settings-update-result"); - resultEl.textContent = "Update check failed: " + err; - } finally { - updateBtn.disabled = false; - updateBtn.textContent = "Check for Updates"; - } - }); - } - - const importBtn = body.querySelector("#settings-import-sshconfig"); - if (importBtn) { - importBtn.addEventListener("click", async () => { - importBtn.disabled = true; - importBtn.textContent = "Importing…"; - try { - const count = await window.AlchementaryLink.wailsService.ImportSSHConfig( - "", - "SSH Config", - ); - this.app.notifications.success( - `Imported ${count} host(s) from ~/.ssh/config`, - ); - await this.app.loadProfiles(); - } catch (err) { - this.app.notifications.error("SSH config import failed: " + err); - } finally { - importBtn.disabled = false; - importBtn.textContent = "Import ~/.ssh/config"; - } - }); - } - - this._attachAboutKonamiListener(); - } - - // ── Form helpers ───────────────────────────────────────────────── - - _attachAboutKonamiListener() { - this._detachAboutKonamiListener(); - this._konamiIndex = 0; - this._aboutKonamiHandler = (event) => { - if (!this._overlay || this._activeTab !== "about") return; - if (event.ctrlKey || event.metaKey || event.altKey) { - this._konamiIndex = 0; - return; - } - - const key = event.key.length === 1 ? event.key.toLowerCase() : event.key; - if (key === KONAMI_SEQUENCE[this._konamiIndex]) { - event.preventDefault(); - this._konamiIndex += 1; - if (this._konamiIndex === KONAMI_SEQUENCE.length) { - this._konamiIndex = 0; - void this._toggleLeviathanFromKonami(); - } - return; - } - - this._konamiIndex = key === KONAMI_SEQUENCE[0] ? 1 : 0; - }; - window.addEventListener("keydown", this._aboutKonamiHandler); - } - - _detachAboutKonamiListener() { - if (this._aboutKonamiHandler) { - window.removeEventListener("keydown", this._aboutKonamiHandler); - this._aboutKonamiHandler = null; - } - this._konamiIndex = 0; - } - - async _toggleLeviathanFromKonami() { - if (this._konamiTogglePending) return; - this._konamiTogglePending = true; - - const enabled = - this.app.state.preferences?.leviathanEnabled !== true; - const prefs = { - ...this.app.state.preferences, - leviathanEnabled: enabled, - }; - - try { - await window.AlchementaryLink.wailsService.SavePreferences(prefs); - this._prefs.leviathanEnabled = enabled; - this.app.state.setPreferences(prefs); - this.app._applyPreferences?.(); - this.app.notifications.success( - enabled ? "Leviathan enabled" : "Leviathan disabled", - ); - } catch (err) { - this.app.notifications.error( - "Failed to update Leviathan setting: " + err, - ); - } finally { - this._konamiTogglePending = false; - } - } - - /** - * Renders a single settings form field. - * @param {string} type - * @param {string} name - * @param {string} label - * @param {*} value - * @param {Object} [opts] - * @returns {HTMLElement} - * @private - */ - _settingField(type, name, label, value, opts = {}) { - const group = createElement("div", "form-group settings-field"); - const labelEl = createElement("label", "form-label", { - textContent: label, - for: `setting-${name}`, - }); - group.appendChild(labelEl); - - let input; - switch (type) { - case "text": - input = createElement("input", "form-input", { - type: "text", - id: `setting-${name}`, - name: `setting-${name}`, - value: value || "", - placeholder: opts.placeholder || "", - }); - break; - - case "number": - input = createElement("input", "form-input", { - type: "number", - id: `setting-${name}`, - name: `setting-${name}`, - value: value != null ? String(value) : "", - }); - if (opts.min != null) input.min = opts.min; - if (opts.max != null) input.max = opts.max; - break; - - case "select": - input = createElement("select", "form-select", { - id: `setting-${name}`, - name: `setting-${name}`, - }); - for (const opt of opts.options || []) { - const option = createElement("option", "", { - value: opt.value, - textContent: opt.label, - }); - if (opt.value === value) option.selected = true; - input.appendChild(option); - } - break; - - case "toggle": { - const wrapper = createElement("div", "form-toggle-wrapper"); - input = createElement("input", "form-toggle", { - type: "checkbox", - id: `setting-${name}`, - name: `setting-${name}`, - }); - input.checked = !!value; - const slider = createElement("label", "form-toggle-label", { - for: `setting-${name}`, - }); - wrapper.appendChild(input); - wrapper.appendChild(slider); - group.appendChild(wrapper); - return group; - } - - default: - input = createElement("input", "form-input", { - type: "text", - id: `setting-${name}`, - name: `setting-${name}`, - value: value || "", - }); - } - - group.appendChild(input); - return group; - } - - /** - * Reads current form field values back into the working preferences copy. - * @private - */ - _collectValues() { - if (!this._overlay) return; - - const val = (name) => { - const el = this._overlay.querySelector(`[name="setting-${name}"]`); - if (!el) return undefined; - if (el.type === "checkbox") return el.checked; - if (el.type === "number") return el.value !== "" ? Number(el.value) : 0; - return el.value; - }; - - const fields = [ - "theme", - "terminalTheme", - "defaultProtocol", - "confirmOnClose", - "confirmOnDisconnect", - "showStatusBar", - "sidebarWidth", - "defaultFontFamily", - "defaultFontSize", - "defaultScrollback", - "defaultCursorStyle", - "useNativePaste", - "localShell", - ]; - - for (const field of fields) { - const v = val(field); - if (v !== undefined) { - this._prefs[field] = v; - } - } - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.Settings = Settings; -})(); diff --git a/frontend/src/js/sftp.js b/frontend/src/js/sftp.js deleted file mode 100644 index 8d370d7..0000000 --- a/frontend/src/js/sftp.js +++ /dev/null @@ -1,1247 +0,0 @@ -(function () { - /** - * Alchementary Link — SFTP File Browser - * - * Provides a right-side panel for browsing, uploading, downloading, - * renaming and deleting remote files alongside the terminal. - */ - - const { - createElement, - escapeHtml, - formatBytes, - relativeTime, - dismissContextMenus, - } = window.AlchementaryLink; - - /* ── Helpers ──────────────────────────────────────────────────────── */ - - function pathJoin(base, name) { - if (base === "/") return "/" + name; - return base.replace(/\/+$/, "") + "/" + name; - } - - function pathSegments(p) { - const parts = p.split("/").filter(Boolean); - var segs = [{ label: "/", path: "/" }]; - var acc = ""; - for (var i = 0; i < parts.length; i++) { - acc += "/" + parts[i]; - segs.push({ label: parts[i], path: acc }); - } - return segs; - } - - function parentPath(p) { - if (p === "/") return "/"; - var trimmed = p.replace(/\/+$/, ""); - var idx = trimmed.lastIndexOf("/"); - return idx <= 0 ? "/" : trimmed.substring(0, idx); - } - - function fileIcon(entry) { - if (entry.isDir) return "📁"; - var ext = (entry.name.split(".").pop() || "").toLowerCase(); - switch (ext) { - case "jpg": - case "jpeg": - case "png": - case "gif": - case "svg": - case "webp": - return "🖼"; - case "mp3": - case "wav": - case "ogg": - case "flac": - return "🎵"; - case "mp4": - case "avi": - case "mkv": - case "mov": - return "🎬"; - case "zip": - case "tar": - case "gz": - case "bz2": - case "xz": - case "7z": - return "📦"; - case "sh": - case "bash": - case "zsh": - return "⚙"; - case "py": - case "js": - case "ts": - case "go": - case "rs": - case "c": - case "h": - case "cpp": - case "java": - return "◇"; - case "md": - case "txt": - case "log": - case "csv": - return "📝"; - case "json": - case "yaml": - case "yml": - case "toml": - case "xml": - return "⚙"; - case "key": - case "pem": - case "crt": - case "pub": - return "🔑"; - default: - return "📄"; - } - } - - function modeString(mode) { - if (!mode && mode !== 0) return ""; - var s = ""; - var perms = ["---", "--x", "-w-", "-wx", "r--", "r-x", "rw-", "rwx"]; - s += perms[(mode >> 6) & 7]; - s += perms[(mode >> 3) & 7]; - s += perms[mode & 7]; - return s; - } - - /* ── SFTPBrowser class ────────────────────────────────────────────── */ - - class SFTPBrowser { - /** - * @param {App} app — the application instance - */ - constructor(app) { - this.app = app; - /** @type {Map} */ - this.panels = new Map(); - } - - /* ── Public API ───────────────────────────────────────────────── */ - - /** - * Opens (or focuses) the SFTP panel for a session. - * @param {string} sessionId - */ - async open(sessionId) { - if (this.panels.has(sessionId)) return; - - var sessionPanel = document.getElementById("panel-" + sessionId); - if (!sessionPanel) { - this.app.notifications.error( - "Cannot open file browser: session panel not found.", - ); - return; - } - - var state = { - el: null, - handle: null, - cwd: "/", - files: [], - selected: null, - loading: false, - }; - - // Build the panel DOM - var panel = createElement("div", "sftp-panel"); - var handle = createElement("div", "sftp-resize-handle"); - panel.appendChild(handle); - state.el = panel; - state.handle = handle; - - // Toolbar - var toolbar = this._buildToolbar(sessionId, state); - panel.appendChild(toolbar); - - // File list container - var fileList = createElement("div", "sftp-file-list"); - panel.appendChild(fileList); - - // Context action bar (hidden initially) - var ctxBar = createElement("div", "sftp-context-bar"); - ctxBar.style.display = "none"; - panel.appendChild(ctxBar); - - this.panels.set(sessionId, state); - - // Append the overlay panel into the session panel - sessionPanel.appendChild(panel); - - this._setupResize(sessionId, state); - - // Fetch initial directory - await this._navigate(sessionId, null); - - // Refit terminal after layout change - this._refitTerminal(sessionId); - } - - /** - * Closes the SFTP panel for a session. - * @param {string} sessionId - */ - close(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return; - - if (state.cleanupResize) state.cleanupResize(); - state.el.remove(); - - this.panels.delete(sessionId); - this._refitTerminal(sessionId); - } - - /** - * Toggles the SFTP panel for a session. - * @param {string} sessionId - */ - toggle(sessionId) { - if (this.panels.has(sessionId)) { - this.close(sessionId); - } else { - this.open(sessionId); - } - } - - /** - * Returns the current directory for an open SFTP panel, or an empty string. - * @param {string} sessionId - * @returns {string} - */ - getCwd(sessionId) { - var state = this.panels.get(sessionId); - return state ? state.cwd : ""; - } - - /* ── Toolbar ──────────────────────────────────────────────────── */ - - _buildToolbar(sessionId, state) { - var toolbar = createElement("div", "sftp-toolbar"); - - var breadcrumb = createElement("div", "sftp-breadcrumb"); - breadcrumb.dataset.role = "breadcrumb"; - toolbar.appendChild(breadcrumb); - - var actions = createElement("div", "sftp-toolbar-actions"); - - var goToBtn = this._toolbarBtn("⤳", "Go to path", ""); - goToBtn.addEventListener("click", () => this._promptGoToPath(sessionId)); - actions.appendChild(goToBtn); - - var bookmarkBtn = this._toolbarBtn("★", "Bookmarks", ""); - bookmarkBtn.addEventListener("click", () => - this._showBookmarks(sessionId), - ); - actions.appendChild(bookmarkBtn); - - var mkdirBtn = this._toolbarBtn("+", "New folder", "is-accent"); - mkdirBtn.addEventListener("click", () => this._promptMkdir(sessionId)); - actions.appendChild(mkdirBtn); - - var uploadBtn = this._toolbarBtn("↑", "Upload file", "is-accent"); - uploadBtn.addEventListener("click", () => this._upload(sessionId)); - actions.appendChild(uploadBtn); - - var refreshBtn = this._toolbarBtn("⟳", "Refresh"); - refreshBtn.addEventListener("click", () => this._refresh(sessionId)); - actions.appendChild(refreshBtn); - - var closeBtn = this._toolbarBtn("✕", "Close", "is-close"); - closeBtn.addEventListener("click", () => this.close(sessionId)); - actions.appendChild(closeBtn); - - toolbar.appendChild(actions); - return toolbar; - } - - _toolbarBtn(label, title, extraClass) { - var cls = "sftp-toolbar-btn" + (extraClass ? " " + extraClass : ""); - return createElement("button", cls, { textContent: label, title: title }); - } - - /* ── Navigation ───────────────────────────────────────────────── */ - - /** - * Navigates to a directory path. If path is null, fetches pwd first. - */ - async _navigate(sessionId, path) { - var state = this.panels.get(sessionId); - if (!state || state.loading) return; - - state.loading = true; - state.selected = null; - this._renderLoading(sessionId); - - try { - if (path === null) { - path = await window.AlchementaryLink.wailsService.SFTPPwd(sessionId); - } - var files = await window.AlchementaryLink.wailsService.SFTPList(sessionId, path); - state.cwd = path; - state.files = this._sortFiles(files || []); - state.loading = false; - this._renderBreadcrumb(sessionId); - this._renderFiles(sessionId); - } catch (err) { - state.loading = false; - this._renderError(sessionId, err.message || String(err)); - } - } - - async _refresh(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return; - await this._navigate(sessionId, state.cwd); - } - - _sortFiles(files) { - return files.slice().sort(function (a, b) { - // Directories first - if (a.isDir !== b.isDir) return a.isDir ? -1 : 1; - return a.name.localeCompare(b.name, undefined, { sensitivity: "base" }); - }); - } - - /* ── Rendering ────────────────────────────────────────────────── */ - - _getFileList(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return null; - return state.el.querySelector(".sftp-file-list"); - } - - _getCtxBar(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return null; - return state.el.querySelector(".sftp-context-bar"); - } - - _renderBreadcrumb(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return; - var container = state.el.querySelector('[data-role="breadcrumb"]'); - container.innerHTML = ""; - - var segs = pathSegments(state.cwd); - var self = this; - for (var i = 0; i < segs.length; i++) { - if (i > 0) { - container.appendChild( - createElement("span", "sftp-breadcrumb-sep", { textContent: "›" }), - ); - } - var btn = createElement("button", "sftp-breadcrumb-segment", { - textContent: segs[i].label, - title: segs[i].path, - }); - (function (p) { - btn.addEventListener("click", function () { - self._navigate(sessionId, p); - }); - })(segs[i].path); - container.appendChild(btn); - } - } - - _renderFiles(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return; - var list = this._getFileList(sessionId); - list.innerHTML = ""; - - // Hide context bar - var ctxBar = this._getCtxBar(sessionId); - if (ctxBar) ctxBar.style.display = "none"; - - // Parent directory row if not at root - if (state.cwd !== "/") { - var parentRow = this._buildFileRow( - { - name: "..", - isDir: true, - path: parentPath(state.cwd), - size: 0, - modifiedAt: 0, - }, - sessionId, - true, - ); - parentRow.classList.add("is-parent"); - list.appendChild(parentRow); - } - - if (state.files.length === 0) { - var empty = createElement("div", "sftp-state"); - empty.innerHTML = - '
📂
Empty directory
'; - list.appendChild(empty); - return; - } - - var self = this; - for (var i = 0; i < state.files.length; i++) { - list.appendChild(this._buildFileRow(state.files[i], sessionId, false)); - } - } - - _buildFileRow(entry, sessionId, isParent) { - var row = createElement("div", "sftp-file-row"); - var self = this; - - // Icon - var icon = createElement( - "span", - "sftp-file-icon" + (entry.isDir ? " is-dir" : ""), - ); - icon.textContent = isParent ? "↩" : fileIcon(entry); - row.appendChild(icon); - - // Name - var name = createElement("span", "sftp-file-name", { - textContent: entry.name, - title: entry.name, - }); - row.appendChild(name); - - // Meta (size or date) - var meta = createElement("span", "sftp-file-meta"); - if (!isParent && !entry.isDir) { - meta.textContent = formatBytes(entry.size || 0); - meta.title = entry.modifiedAt - ? new Date(entry.modifiedAt).toLocaleString() - : ""; - } else if (!isParent && entry.isDir) { - meta.textContent = ""; - } - row.appendChild(meta); - - // Double-click: navigate into directory - if (entry.isDir) { - row.addEventListener("dblclick", function () { - var target = isParent - ? parentPath(self.panels.get(sessionId).cwd) - : entry.path; - self._navigate(sessionId, target); - }); - } - - // Single click: select (files only, or directories too for context actions) - if (!isParent) { - row.addEventListener("click", function (e) { - e.stopPropagation(); - self._selectFile(sessionId, entry, row, e); - }); - } - - return row; - } - - _renderLoading(sessionId) { - var list = this._getFileList(sessionId); - if (!list) return; - list.innerHTML = ""; - var s = createElement("div", "sftp-state"); - s.innerHTML = - '
Loading…
'; - list.appendChild(s); - var ctxBar = this._getCtxBar(sessionId); - if (ctxBar) ctxBar.style.display = "none"; - } - - _renderError(sessionId, msg) { - var list = this._getFileList(sessionId); - if (!list) return; - list.innerHTML = ""; - var s = createElement("div", "sftp-state"); - s.innerHTML = - '
⚠
' + - '
' + - escapeHtml(msg) + - "
" + - ''; - var self = this; - var retryBtn = s.querySelector(".sftp-state-retry"); - if (retryBtn) { - retryBtn.addEventListener("click", function () { - self._refresh(sessionId); - }); - } - list.appendChild(s); - } - - /* ── Selection & context actions ──────────────────────────────── */ - - _selectFile(sessionId, entry, rowEl, event) { - var state = this.panels.get(sessionId); - if (!state) return; - - // Initialise multi-select set if needed - if (!state.selectedFiles) state.selectedFiles = new Map(); - - var multiKey = event && (event.metaKey || event.ctrlKey); - - if (multiKey) { - // Toggle this entry in the multi-selection - if (state.selectedFiles.has(entry.path)) { - state.selectedFiles.delete(entry.path); - rowEl.classList.remove("is-selected"); - } else { - state.selectedFiles.set(entry.path, entry); - rowEl.classList.add("is-selected"); - } - } else { - // Single click without modifier — clear all, select this one - state.el - .querySelectorAll(".sftp-file-row.is-selected") - .forEach(function (r) { - r.classList.remove("is-selected"); - }); - - // If clicking the same entry (and it was the only selection), deselect - if ( - state.selectedFiles.size === 1 && - state.selectedFiles.has(entry.path) - ) { - state.selectedFiles.clear(); - state.selected = null; - var ctxBar = this._getCtxBar(sessionId); - if (ctxBar) ctxBar.style.display = "none"; - return; - } - - state.selectedFiles.clear(); - state.selectedFiles.set(entry.path, entry); - rowEl.classList.add("is-selected"); - } - - state.selected = state.selectedFiles.size > 0 ? entry.path : null; - this._showContextBar(sessionId, null); - } - - _showContextBar(sessionId, _unused) { - var state = this.panels.get(sessionId); - if (!state) return; - var ctxBar = this._getCtxBar(sessionId); - if (!ctxBar) return; - - if (!state.selectedFiles || state.selectedFiles.size === 0) { - ctxBar.style.display = "none"; - return; - } - - ctxBar.innerHTML = ""; - ctxBar.style.display = "flex"; - - var self = this; - var entries = Array.from(state.selectedFiles.values()); - var count = entries.length; - var isSingle = count === 1; - var entry = entries[0]; - - var label = createElement("span", "sftp-ctx-label", { - textContent: isSingle ? entry.name : count + " items selected", - title: isSingle - ? entry.path - : entries - .map(function (e) { - return e.name; - }) - .join(", "), - }); - ctxBar.appendChild(label); - - // Download button — only for files (single or batch) - var downloadableFiles = entries.filter(function (e) { - return !e.isDir; - }); - if (downloadableFiles.length > 0) { - var dlLabel = - downloadableFiles.length === 1 - ? "↓ Download" - : "↓ Download (" + downloadableFiles.length + ")"; - var dlBtn = createElement("button", "sftp-ctx-btn is-accent", { - textContent: dlLabel, - }); - dlBtn.addEventListener("click", function () { - self._downloadBatch(sessionId, downloadableFiles); - }); - ctxBar.appendChild(dlBtn); - } - - if (isSingle) { - var renameBtn = createElement("button", "sftp-ctx-btn", { - textContent: "✎ Rename", - }); - renameBtn.addEventListener("click", function () { - self._promptRename(sessionId, entry); - }); - ctxBar.appendChild(renameBtn); - } - - var delLabel = isSingle ? "✕ Delete" : "✕ Delete (" + count + ")"; - var delBtn = createElement("button", "sftp-ctx-btn is-danger", { - textContent: delLabel, - }); - delBtn.addEventListener("click", function () { - if (isSingle) { - self._confirmDelete(sessionId, entry); - } else { - self._confirmDeleteBatch(sessionId, entries); - } - }); - ctxBar.appendChild(delBtn); - } - - /* ── File operations ──────────────────────────────────────────── */ - - async _download(sessionId, filePath) { - try { - await window.AlchementaryLink.wailsService.SFTPDownload(sessionId, filePath); - this.app.notifications.success("Download started"); - } catch (err) { - this.app.notifications.error( - "Download failed: " + (err.message || err), - ); - } - } - - async _upload(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return; - try { - await window.AlchementaryLink.wailsService.SFTPUpload(sessionId, state.cwd); - this.app.notifications.success("Upload complete"); - await this._refresh(sessionId); - } catch (err) { - // User may have cancelled the dialog — only show error if meaningful - var msg = err.message || String(err); - if (msg && msg.toLowerCase().indexOf("cancel") === -1) { - this.app.notifications.error("Upload failed: " + msg); - } - } - } - - async _mkdir(sessionId, name) { - var state = this.panels.get(sessionId); - if (!state) return; - var fullPath = pathJoin(state.cwd, name); - try { - await window.AlchementaryLink.wailsService.SFTPMkdir(sessionId, fullPath); - this.app.notifications.success("Directory created"); - await this._refresh(sessionId); - } catch (err) { - this.app.notifications.error("mkdir failed: " + (err.message || err)); - } - } - - async _rename(sessionId, oldPath, newName) { - var state = this.panels.get(sessionId); - if (!state) return; - var newPath = pathJoin(parentPath(oldPath), newName); - try { - await window.AlchementaryLink.wailsService.SFTPRename(sessionId, oldPath, newPath); - this.app.notifications.success("Renamed"); - await this._refresh(sessionId); - } catch (err) { - this.app.notifications.error("Rename failed: " + (err.message || err)); - } - } - - async _delete(sessionId, entry) { - try { - await window.AlchementaryLink.wailsService.SFTPRemove(sessionId, entry.path); - this.app.notifications.success( - (entry.isDir ? "Directory" : "File") + " deleted", - ); - await this._refresh(sessionId); - } catch (err) { - this.app.notifications.error("Delete failed: " + (err.message || err)); - } - } - - async _downloadBatch(sessionId, files) { - this.app.notifications.info("Downloading " + files.length + " files…"); - var failed = 0; - for (var i = 0; i < files.length; i++) { - try { - await window.AlchementaryLink.wailsService.SFTPDownload(sessionId, files[i].path); - } catch (err) { - failed++; - this.app.notifications.error( - "Download failed: " + files[i].name + " — " + (err.message || err), - ); - } - } - if (failed === 0) { - this.app.notifications.success( - "All " + files.length + " files downloaded", - ); - } - } - - _confirmDeleteBatch(sessionId, entries) { - this._showConfirmModal({ - title: "Delete " + entries.length + " Items", - message: - "Permanently delete " + - entries.length + - " selected items?", - confirmLabel: "Delete All", - isDanger: true, - onConfirm: function () { - this._deleteBatch(sessionId, entries); - }.bind(this), - }); - } - - async _deleteBatch(sessionId, entries) { - var failed = 0; - for (var i = 0; i < entries.length; i++) { - try { - await window.AlchementaryLink.wailsService.SFTPRemove(sessionId, entries[i].path); - } catch (err) { - failed++; - this.app.notifications.error( - "Delete failed: " + entries[i].name + " — " + (err.message || err), - ); - } - } - if (failed === 0) { - this.app.notifications.success("Deleted " + entries.length + " items"); - } - await this._refresh(sessionId); - } - - /* ── Modal dialogs (no prompt/confirm/alert) ──────────────────── */ - - _promptMkdir(sessionId) { - this._showInputModal({ - title: "New Folder", - placeholder: "Folder name", - confirmLabel: "Create", - onConfirm: function (value) { - if (value.trim()) this._mkdir(sessionId, value.trim()); - }.bind(this), - }); - } - - _promptRename(sessionId, entry) { - this._showInputModal({ - title: "Rename", - placeholder: "New name", - initialValue: entry.name, - confirmLabel: "Rename", - onConfirm: function (value) { - var v = value.trim(); - if (v && v !== entry.name) this._rename(sessionId, entry.path, v); - }.bind(this), - }); - } - - _confirmDelete(sessionId, entry) { - this._showConfirmModal({ - title: "Delete " + (entry.isDir ? "Folder" : "File"), - message: - "Permanently delete " + escapeHtml(entry.name) + "?", - confirmLabel: "Delete", - isDanger: true, - onConfirm: function () { - this._delete(sessionId, entry); - }.bind(this), - }); - } - - /** - * Displays a modal with a text input field. - */ - _showInputModal(opts) { - var backdrop = createElement("div", "modal-backdrop"); - var modal = createElement("div", "modal modal-sm"); - modal.innerHTML = - '" + - '" + - '"; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - - var input = modal.querySelector(".sftp-inline-input"); - requestAnimationFrame(function () { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - input.focus(); - input.select(); - }); - - var closed = false; - function close(confirmed) { - if (closed) return; - closed = true; - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(function () { - backdrop.remove(); - }, 200); - if (confirmed && opts.onConfirm) { - opts.onConfirm(input.value); - } - } - - modal - .querySelector(".modal-close") - .addEventListener("click", function () { - close(false); - }); - modal - .querySelector(".sftp-modal-cancel") - .addEventListener("click", function () { - close(false); - }); - modal - .querySelector(".sftp-modal-confirm") - .addEventListener("click", function () { - close(true); - }); - backdrop.addEventListener("click", function (e) { - if (e.target === backdrop) close(false); - }); - input.addEventListener("keydown", function (e) { - if (e.key === "Enter") close(true); - if (e.key === "Escape") close(false); - }); - } - - /** - * Displays a confirmation modal (no native confirm). - */ - _showConfirmModal(opts) { - var backdrop = createElement("div", "modal-backdrop"); - var modal = createElement("div", "modal modal-sm confirm-dialog"); - modal.innerHTML = - '" + - '" + - '"; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - - requestAnimationFrame(function () { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - var closed = false; - function close(confirmed) { - if (closed) return; - closed = true; - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(function () { - backdrop.remove(); - }, 200); - if (confirmed && opts.onConfirm) { - opts.onConfirm(); - } - } - - modal - .querySelector(".modal-close") - .addEventListener("click", function () { - close(false); - }); - modal - .querySelector(".sftp-modal-cancel") - .addEventListener("click", function () { - close(false); - }); - modal - .querySelector(".sftp-modal-confirm") - .addEventListener("click", function () { - close(true); - }); - backdrop.addEventListener("click", function (e) { - if (e.target === backdrop) close(false); - }); - } - - /* ── Go to path & Bookmarks ───────────────────────────────────── */ - - _promptGoToPath(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return; - this._showInputModal({ - title: "Go to Path", - placeholder: "/path/to/directory", - initialValue: state.cwd, - confirmLabel: "Navigate", - onConfirm: function (value) { - var path = value.trim(); - if (path) this._navigate(sessionId, path); - }.bind(this), - }); - } - - _showBookmarks(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return; - - var self = this; - var bookmarks = this._getBookmarks(sessionId); - - var backdrop = createElement("div", "modal-backdrop"); - var modal = createElement("div", "modal modal-sm"); - - var listHtml = ""; - if (bookmarks.length === 0) { - listHtml = - '
No bookmarks yet. Navigate to a directory and click "Bookmark Current".
'; - } else { - for (var i = 0; i < bookmarks.length; i++) { - listHtml += - '
'; - listHtml += - '' + - escapeHtml(bookmarks[i]) + - ""; - listHtml += - ''; - listHtml += "
"; - } - } - - modal.innerHTML = - '" + - '" + - '"; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - - requestAnimationFrame(function () { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - var closed = false; - function close() { - if (closed) return; - closed = true; - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(function () { - backdrop.remove(); - }, 200); - } - - modal.querySelector(".modal-close").addEventListener("click", close); - modal - .querySelector(".sftp-modal-cancel") - .addEventListener("click", close); - backdrop.addEventListener("click", function (e) { - if (e.target === backdrop) close(); - }); - - // Navigate to bookmark on click - modal.querySelectorAll(".sftp-bookmark-row").forEach(function (row) { - row.addEventListener("click", function (e) { - if (e.target.classList.contains("sftp-bookmark-del")) return; - var path = row.dataset.path; - close(); - self._navigate(sessionId, path); - }); - row.addEventListener("mousedown", function (e) { - e.preventDefault(); - }); - }); - - // Delete bookmark - modal.querySelectorAll(".sftp-bookmark-del").forEach(function (btn) { - btn.addEventListener("click", function (e) { - e.stopPropagation(); - var idx = parseInt(btn.dataset.index, 10); - self._removeBookmark(sessionId, idx); - close(); - self._showBookmarks(sessionId); - }); - }); - - // Add current directory as bookmark - modal - .querySelector(".sftp-bookmark-add") - .addEventListener("click", function () { - self._addBookmark(sessionId, state.cwd); - close(); - self._showBookmarks(sessionId); - }); - } - - _getBookmarks(sessionId) { - try { - var key = "sftp-bookmarks-" + sessionId; - var stored = localStorage.getItem(key); - return stored ? JSON.parse(stored) : []; - } catch (_) { - return []; - } - } - - _addBookmark(sessionId, path) { - var bookmarks = this._getBookmarks(sessionId); - if (bookmarks.indexOf(path) === -1) { - bookmarks.push(path); - localStorage.setItem( - "sftp-bookmarks-" + sessionId, - JSON.stringify(bookmarks), - ); - this.app.notifications.success("Bookmarked: " + path); - } else { - this.app.notifications.info("Already bookmarked"); - } - } - - _removeBookmark(sessionId, index) { - var bookmarks = this._getBookmarks(sessionId); - bookmarks.splice(index, 1); - localStorage.setItem( - "sftp-bookmarks-" + sessionId, - JSON.stringify(bookmarks), - ); - } - - /* ── Transfer progress queue ─────────────────────────────────── */ - - /** - * Handles a transfer progress event from the backend. - * @param {Object} payload - { sessionID, direction, transferred, total, path, done } - */ - handleTransferProgress(payload) { - var state = this.panels.get(payload.sessionID); - if (!state) { - // Panel not open — still track so we can show a notification on completion - if (payload.done) { - var dir = - payload.direction === "upload" ? "↑ Uploaded" : "↓ Downloaded"; - var name = payload.path.split("/").pop() || payload.path; - this.app.notifications.success(dir + " " + name); - } - return; - } - - // Initialise transfers map if needed - if (!state.transfers) state.transfers = new Map(); - - var key = payload.direction + ":" + payload.path; - - if (payload.done) { - state.transfers.delete(key); - this._renderTransferQueue(payload.sessionID); - // Refresh file list after upload completes - if (payload.direction === "upload") { - this._refresh(payload.sessionID); - } - return; - } - - state.transfers.set(key, { - direction: payload.direction, - path: payload.path, - name: payload.path.split("/").pop() || payload.path, - transferred: payload.transferred, - total: payload.total, - }); - - this._renderTransferQueue(payload.sessionID); - } - - /** - * Renders the transfer queue panel showing active transfers. - */ - _renderTransferQueue(sessionId) { - var state = this.panels.get(sessionId); - if (!state) return; - - var queue = state.el.querySelector(".sftp-transfer-queue"); - - if (!state.transfers || state.transfers.size === 0) { - if (queue) queue.remove(); - return; - } - - if (!queue) { - queue = createElement("div", "sftp-transfer-queue"); - // Insert between toolbar and file-list - var fileList = state.el.querySelector(".sftp-file-list"); - state.el.insertBefore(queue, fileList); - } - - queue.innerHTML = ""; - - var header = createElement("div", "sftp-transfer-header"); - header.textContent = "Transfers (" + state.transfers.size + ")"; - queue.appendChild(header); - - var self = this; - state.transfers.forEach(function (transfer) { - var row = createElement("div", "sftp-transfer-row"); - - var icon = createElement("span", "sftp-transfer-icon"); - icon.textContent = transfer.direction === "upload" ? "↑" : "↓"; - row.appendChild(icon); - - var info = createElement("div", "sftp-transfer-info"); - - var nameEl = createElement("span", "sftp-transfer-name", { - textContent: transfer.name, - title: transfer.path, - }); - info.appendChild(nameEl); - - var progress = createElement("div", "sftp-transfer-progress"); - var bar = createElement("div", "sftp-transfer-bar"); - var pct = - transfer.total > 0 - ? Math.min( - 100, - Math.round((transfer.transferred / transfer.total) * 100), - ) - : 0; - bar.style.width = pct + "%"; - progress.appendChild(bar); - info.appendChild(progress); - - var meta = createElement("span", "sftp-transfer-meta"); - var transferredStr = formatBytes(transfer.transferred); - var totalStr = transfer.total > 0 ? formatBytes(transfer.total) : "?"; - meta.textContent = - transferredStr + " / " + totalStr + " (" + pct + "%)"; - info.appendChild(meta); - - row.appendChild(info); - queue.appendChild(row); - }); - } - - /* ── Resize handle ────────────────────────────────────────────── */ - - _setupResize(sessionId, state) { - var handle = state.handle; - var panel = state.el; - var self = this; - var startX, startWidth; - - function onMouseMove(e) { - var delta = startX - e.clientX; - var newWidth = Math.max( - 180, - Math.min(startWidth + delta, window.innerWidth * 0.5), - ); - panel.style.width = newWidth + "px"; - } - - function onMouseUp() { - cleanupResize(); - self._refitTerminal(sessionId); - } - - function cleanupResize() { - handle.classList.remove("is-dragging"); - document.body.style.userSelect = ""; - document.body.style.cursor = ""; - document.removeEventListener("mousemove", onMouseMove); - document.removeEventListener("mouseup", onMouseUp); - } - - state.cleanupResize = cleanupResize; - - handle.addEventListener("mousedown", function (e) { - e.preventDefault(); - startX = e.clientX; - startWidth = panel.offsetWidth; - handle.classList.add("is-dragging"); - document.body.style.userSelect = "none"; - document.body.style.cursor = "col-resize"; - document.addEventListener("mousemove", onMouseMove); - document.addEventListener("mouseup", onMouseUp); - }); - } - - /* ── Terminal refit ────────────────────────────────────────────── */ - - _refitTerminal(sessionId) { - if ( - this.app.terminals && - typeof this.app.terminals.fitSession === "function" - ) { - // Small delay lets the layout settle before measuring - setTimeout( - function () { - this.app.terminals.fitSession(sessionId); - }.bind(this), - 50, - ); - } - } - } - - /* ── Export ────────────────────────────────────────────────────────── */ - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.SFTPBrowser = SFTPBrowser; -})(); diff --git a/frontend/src/js/sidebar.js b/frontend/src/js/sidebar.js deleted file mode 100644 index 03739d0..0000000 --- a/frontend/src/js/sidebar.js +++ /dev/null @@ -1,939 +0,0 @@ -(function () { - /** - * Alchementary Link — sidebar module. - * Renders the profile list with search, sections (favourites, recent, all), - * and context menus. - */ - - const { - createElement, - escapeHtml, - fuzzyMatch, - protocolColour, - dismissContextMenus, - truncate, - } = window.AlchementaryLink; - - /** Maximum number of profiles shown in the "Recent" section. */ - const RECENT_LIMIT = 5; - - class Sidebar { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - this.el = document.getElementById("sidebar-profiles"); - this.searchQuery = ""; - - /** @type {Set} */ - this.selectedProfiles = new Set(); - - // Section collapse state - this.collapsedSections = new Set(); - - // Dismiss context menus on any outside click - document.addEventListener("click", () => dismissContextMenus()); - - if (this.el) { - this.el.addEventListener("click", (e) => { - if ( - !e.target.closest(".sidebar-item") && - this.selectedProfiles.size > 0 - ) { - this._clearSelection(); - } - }); - } - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Full re-render of the sidebar profile list. - * Profiles are organised into: - * 1. Favourites (if any) - * 2. Recent (last 5 connected) - * 3. One section per group (sorted alphabetically) - * 4. Ungrouped (profiles with no group assigned) - */ - render() { - if (!this.el) return; - if (this.app.mode === "leviathan") { - this.app.leviathan.renderSidebar(); - return; - } - this.el.innerHTML = ""; - - const profiles = this._filteredProfiles(); - this._syncSelection(profiles); - - if (this.selectedProfiles.size > 0) { - this.el.appendChild(this._renderBulkActions()); - } - - // Local Machine quick-connect button - this.el.appendChild(this._renderLocalMachineItem()); - - // Favourites - const favourites = profiles.filter((p) => p.favourite); - if (favourites.length > 0) { - this._renderSection("★ Favourites", "favourites", favourites); - } - - // Recent - const recent = profiles - .filter((p) => p.lastConnected) - .sort((a, b) => new Date(b.lastConnected) - new Date(a.lastConnected)) - .slice(0, RECENT_LIMIT); - if (recent.length > 0) { - this._renderSection("Recent", "recent", recent); - } - - // ── Group-based sections ───────────────────────────────────── - // Collect distinct groups and sort them alphabetically. - const groupMap = new Map(); - const ungrouped = []; - - for (const p of profiles) { - const grp = (p.folder || "").trim(); - if (grp) { - if (!groupMap.has(grp)) groupMap.set(grp, []); - groupMap.get(grp).push(p); - } else { - ungrouped.push(p); - } - } - - // Render each group as its own collapsible section - const sortedGroups = [...groupMap.keys()].sort((a, b) => - a.localeCompare(b), - ); - for (const grp of sortedGroups) { - this._renderSection(grp, `group:${grp}`, groupMap.get(grp)); - } - - // Ungrouped connections - if (ungrouped.length > 0) { - const label = sortedGroups.length > 0 ? "Ungrouped" : "All Connections"; - this._renderSection(label, "ungrouped", ungrouped); - } - - // Empty state - if (profiles.length === 0) { - this.el.appendChild(this._renderEmptyState()); - } - } - - /** - * Filters the displayed profiles by query. - * @param {string} query - */ - search(query) { - this.searchQuery = query.trim(); - this.render(); - } - - /** - * Highlights the sidebar item for the currently active profile. - * @param {string|null} profileId - */ - highlightActive(profileId) { - this.el.querySelectorAll(".sidebar-item").forEach((item) => { - item.classList.toggle( - "sidebar-item-active", - item.dataset.profileId === profileId, - ); - }); - } - - /** - * Toggles the collapsed state of a named section. - * @param {string} sectionName - */ - toggleSection(sectionName) { - if (this.collapsedSections.has(sectionName)) { - this.collapsedSections.delete(sectionName); - } else { - this.collapsedSections.add(sectionName); - } - this.render(); - } - - // ── Rendering helpers ──────────────────────────────────────────── - - /** - * Renders the sidebar empty state for first-run and no-search-results cases. - * @returns {HTMLElement} - * @private - */ - _renderEmptyState() { - const isSearching = this.searchQuery.length > 0; - const empty = createElement("div", "sidebar-empty"); - - const icon = createElement("div", "sidebar-empty-icon", { - textContent: isSearching ? "⌕" : "+", - "aria-hidden": "true", - }); - empty.appendChild(icon); - - empty.appendChild( - createElement("div", "sidebar-empty-title", { - textContent: isSearching ? "No matching profiles" : "No saved profiles", - }), - ); - - empty.appendChild( - createElement("p", "sidebar-empty-text", { - textContent: isSearching - ? "Try a different name, host, group, or protocol." - : "Create a profile for the servers and tools you use often.", - }), - ); - - if (!isSearching) { - const actions = createElement("div", "sidebar-empty-actions"); - const addButton = createElement("button", "btn btn-primary btn-sm", { - type: "button", - textContent: "New Profile", - }); - addButton.addEventListener("click", (e) => { - e.preventDefault(); - e.stopPropagation(); - this.app.profileEditor.openNew(); - }); - actions.appendChild(addButton); - empty.appendChild(actions); - } - - return empty; - } - - /** - * Renders a collapsible section of profiles. - * @param {string} title - * @param {string} key - * @param {Array} profiles - * @private - */ - _renderSection(title, key, profiles) { - const section = createElement("div", "sidebar-section"); - const isCollapsed = this.collapsedSections.has(key); - const isGroupSection = key.startsWith("group:"); - - // Section header - const header = createElement( - "div", - `sidebar-section-header ${isGroupSection ? "sidebar-section-header-group" : ""}`.trim(), - ); - header.innerHTML = ` - ${isCollapsed ? "▸" : "▾"} - ${escapeHtml(title)} - ${profiles.length} - `; - // Prevent mousedown from starting a text selection gesture in WKWebView, - // which would swallow the first click and require a double-click. - header.addEventListener("mousedown", (e) => e.preventDefault()); - header.addEventListener("click", () => this.toggleSection(key)); - section.appendChild(header); - - // Items - if (!isCollapsed) { - const list = createElement("div", "sidebar-list"); - for (const profile of profiles) { - list.appendChild(this.renderProfileItem(profile)); - } - section.appendChild(list); - } - - this.el.appendChild(section); - } - - /** - * Creates a single sidebar list item for a profile. - * @param {Object} profile - * @returns {HTMLElement} - */ - renderProfileItem(profile) { - const item = createElement("div", "sidebar-item", { - dataset: { profileId: profile.id }, - }); - - if (this.selectedProfiles.has(profile.id)) { - item.classList.add("sidebar-item-selected"); - } - - // Highlight if this profile's session is active - const activeSession = this.app.state.activeSessionId - ? this.app.state.getSession(this.app.state.activeSessionId) - : null; - if (activeSession && activeSession.profileId === profile.id) { - item.classList.add("sidebar-item-active"); - } - - // Protocol icon - const protocolLabel = ""; // Seemed pretty naff to have this as well as the little icon badboy this._protocolLabel(profile.protocol); - const indicator = createElement( - "span", - `protocol-indicator ${protocolColour(profile.protocol)}`, - { - title: protocolLabel, - "aria-label": protocolLabel, - }, - ); - item.appendChild(indicator); - - // Text content - const textWrapper = createElement("div", "sidebar-item-text"); - const displayName = (profile.displayName || "").trim(); - const primaryLabel = displayName || profile.host || "Untitled"; - const nameEl = createElement("span", "sidebar-item-name", { - textContent: primaryLabel, - }); - textWrapper.appendChild(nameEl); - - const subtitle = protocolLabel; - const subEl = createElement("span", "sidebar-item-host", { - textContent: truncate(subtitle, 30), - }); - textWrapper.appendChild(subEl); - item.appendChild(textWrapper); - - // Edit button (visible on hover) - const editBtn = createElement("button", "sidebar-item-edit", { - textContent: "✎", - title: "Edit profile", - type: "button", - "aria-label": "Edit profile", - }); - let editTriggered = false; - const triggerEdit = (e) => { - e.preventDefault(); - e.stopPropagation(); - if (editTriggered) return; - editTriggered = true; - requestAnimationFrame(() => { - editTriggered = false; - }); - this.app.profileEditor.openEdit(profile.id); - }; - editBtn.addEventListener("mousedown", triggerEdit); - editBtn.addEventListener("click", triggerEdit); - item.appendChild(editBtn); - - // Favourite star - if (profile.favourite) { - const star = createElement("span", "sidebar-item-star is-favourite", { - textContent: "★", - }); - item.appendChild(star); - } - - // Prevent mousedown text selection in WKWebView, but allow real controls. - item.addEventListener("mousedown", (e) => { - if (e.target.closest("button, input, select, textarea")) return; - e.preventDefault(); - }); - - // Click → connect / multi-select - item.addEventListener("click", (e) => { - const multi = e.metaKey || e.ctrlKey; - if (multi) { - e.preventDefault(); - this._toggleSelection(profile.id); - return; - } - - if (this.selectedProfiles.size > 0) { - this._clearSelection(); - } - this.app.connectProfile(profile.id); - }); - - // Double-click → edit - item.addEventListener("dblclick", (e) => { - e.stopPropagation(); - this.app.profileEditor.openEdit(profile.id); - }); - - // Right-click → context menu - item.addEventListener("contextmenu", (e) => { - e.preventDefault(); - e.stopPropagation(); - this.renderContextMenu(profile, e.clientX, e.clientY); - }); - - return item; - } - - /** - * Shows a right-click context menu for a profile. - * @param {Object} profile - * @param {number} x - Viewport X. - * @param {number} y - Viewport Y. - */ - renderContextMenu(profile, x, y) { - dismissContextMenus(); - - const menu = createElement("div", "context-menu"); - menu.style.left = `${x}px`; - menu.style.top = `${y}px`; - - const items = [ - { label: "Connect", action: () => this.app.connectProfile(profile.id) }, - { - label: "Edit", - action: () => this.app.profileEditor.openEdit(profile.id), - }, - { label: "Duplicate", action: () => this._duplicateProfile(profile) }, - { divider: true }, - { - label: profile.favourite - ? "Remove from Favourites" - : "Add to Favourites", - action: () => this._toggleFavourite(profile), - }, - { divider: true }, - { - label: "Delete", - className: "context-menu-danger", - action: () => this._deleteProfile(profile), - }, - ]; - - for (const entry of items) { - if (entry.divider) { - menu.appendChild(createElement("div", "context-menu-divider")); - continue; - } - const btn = createElement( - "button", - `context-menu-item ${entry.className || ""}`, - { - textContent: entry.label, - }, - ); - btn.addEventListener("click", () => { - dismissContextMenus(); - entry.action(); - }); - menu.appendChild(btn); - } - - // Keep menu within viewport - document.body.appendChild(menu); - const rect = menu.getBoundingClientRect(); - if (rect.right > window.innerWidth) { - menu.style.left = `${window.innerWidth - rect.width - 8}px`; - } - if (rect.bottom > window.innerHeight) { - menu.style.top = `${window.innerHeight - rect.height - 8}px`; - } - } - - // ── Actions ────────────────────────────────────────────────────── - - /** - * Toggles the favourite status of a profile. - * @param {Object} profile - * @private - */ - async _toggleFavourite(profile) { - try { - const newVal = !profile.favourite; - await window.AlchementaryLink.wailsService.SetFavourite(profile.id, newVal); - profile.favourite = newVal; - this.app.state.updateProfile(profile); - this.render(); - } catch (err) { - this.app.notifications.error("Failed to update favourite: " + err); - } - } - - /** - * Duplicates a profile. - * @param {Object} profile - * @private - */ - async _duplicateProfile(profile) { - try { - const dup = await window.AlchementaryLink.wailsService.DuplicateProfile(profile.id); - this.app.state.addProfile(dup); - this.render(); - this.app.notifications.success(`Duplicated "${profile.displayName}"`); - } catch (err) { - this.app.notifications.error("Failed to duplicate profile: " + err); - } - } - - /** - * Deletes a profile after confirmation. - * @param {Object} profile - * @private - */ - async _deleteProfile(profile) { - // Use a custom confirmation modal — window.confirm() is silently - // blocked in WKWebView and always returns false. - const ok = await this._confirmDelete(profile); - if (!ok) return; - - try { - await window.AlchementaryLink.wailsService.DeleteProfile(profile.id); - this.app.state.removeProfile(profile.id); - this.render(); - this.app.notifications.success("Profile deleted"); - } catch (err) { - this.app.notifications.error("Failed to delete profile: " + err); - } - } - - /** - * Shows a confirmation modal for profile deletion. - * @param {Object} profile - * @returns {Promise} - * @private - */ - _confirmDelete(profile) { - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - const label = escapeHtml( - profile.displayName || profile.host || "Untitled", - ); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = (val) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(val); - }; - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-cancel") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-ok") - .addEventListener("click", () => close(true)); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(false); - }); - }); - } - - // ── Multi-select + bulk actions ───────────────────────────────── - - /** - * Removes selections that are no longer visible in the filtered list. - * @param {Array} profiles - * @private - */ - _syncSelection(profiles) { - const valid = new Set(profiles.map((p) => p.id)); - for (const id of [...this.selectedProfiles]) { - if (!valid.has(id)) this.selectedProfiles.delete(id); - } - } - - /** @private */ - _toggleSelection(profileId) { - if (this.selectedProfiles.has(profileId)) { - this.selectedProfiles.delete(profileId); - } else { - this.selectedProfiles.add(profileId); - } - this.render(); - } - - /** @private */ - _clearSelection() { - if (this.selectedProfiles.size === 0) return; - this.selectedProfiles.clear(); - this.render(); - } - - /** @private */ - _getSelectedProfiles() { - return this.app.state.profiles.filter((p) => - this.selectedProfiles.has(p.id), - ); - } - - /** @private */ - _renderLocalMachineItem() { - const item = createElement("div", "sidebar-item sidebar-item-local"); - item.addEventListener("mousedown", (e) => e.preventDefault()); - - const indicator = createElement( - "span", - "protocol-indicator proto-local", - { - title: "Local", - }, - ); - item.appendChild(indicator); - - const textWrapper = createElement("div", "sidebar-item-text"); - const nameEl = createElement("span", "sidebar-item-name", { - textContent: "Local Machine", - }); - textWrapper.appendChild(nameEl); - item.appendChild(textWrapper); - - item.addEventListener("click", () => { - if (this.onConnectLocal) { - this.onConnectLocal(); - } - }); - - return item; - } - - /** @private */ - _renderBulkActions() { - const bar = createElement("div", "sidebar-bulk-actions"); - const count = createElement("span", "sidebar-bulk-count", { - textContent: `${this.selectedProfiles.size} selected`, - }); - bar.appendChild(count); - - const actions = createElement("div", "sidebar-bulk-buttons"); - const makeBtn = (label, handler, className) => { - const btn = createElement( - "button", - `btn btn-xs ${className || ""}`.trim(), - { - textContent: label, - }, - ); - btn.addEventListener("click", handler); - actions.appendChild(btn); - }; - - makeBtn("Connect", () => this._bulkConnect()); - makeBtn("Disconnect", () => this._bulkDisconnect(), "btn-outline"); - makeBtn("Tags", () => this._bulkEditTags(), "btn-outline"); - makeBtn("Move Group", () => this._bulkMoveGroup(), "btn-outline"); - makeBtn("Clear", () => this._clearSelection(), "btn-ghost"); - - bar.appendChild(actions); - return bar; - } - - /** @private */ - _bulkConnect() { - const profiles = this._getSelectedProfiles(); - for (const p of profiles) { - this.app.connectProfile(p.id); - } - this._clearSelection(); - } - - /** @private */ - async _bulkDisconnect() { - const sessions = this.app.state - .getAllSessions() - .filter((s) => this.selectedProfiles.has(s.profileId)); - if (sessions.length === 0) { - this.app.notifications.info("No active sessions to disconnect"); - return; - } - - const ok = await this._confirmBulkDisconnect(sessions.length); - if (!ok) return; - - for (const s of sessions) { - await this.app.closeSessionImmediate(s.id); - } - this._clearSelection(); - } - - /** @private */ - async _bulkEditTags() { - const result = await this._promptTagEdit(); - if (!result) return; - - const profiles = this._getSelectedProfiles(); - for (const p of profiles) { - const updated = JSON.parse(JSON.stringify(p)); - const existing = updated.tags || []; - if (result.mode === "replace") { - updated.tags = result.tags; - } else { - const merged = new Set([...existing, ...result.tags]); - updated.tags = [...merged]; - } - - try { - const saved = await window.AlchementaryLink.wailsService.UpdateProfile(updated); - this.app.state.updateProfile(saved); - } catch (err) { - this.app.notifications.error("Failed to update tags: " + err); - } - } - - this.render(); - this._clearSelection(); - } - - /** @private */ - async _bulkMoveGroup() { - const folder = await this._promptGroupMove(); - if (folder === null) return; - - const profiles = this._getSelectedProfiles(); - for (const p of profiles) { - const updated = JSON.parse(JSON.stringify(p)); - updated.folder = folder; - try { - const saved = await window.AlchementaryLink.wailsService.UpdateProfile(updated); - this.app.state.updateProfile(saved); - } catch (err) { - this.app.notifications.error("Failed to move group: " + err); - } - } - - this.render(); - this._clearSelection(); - } - - /** @private */ - _confirmBulkDisconnect(count) { - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = (val) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(val); - }; - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-cancel") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-ok") - .addEventListener("click", () => close(true)); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(false); - }); - }); - } - - /** @private */ - _promptTagEdit() { - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const input = modal.querySelector(".tag-input"); - const parseTags = () => { - const raw = (input.value || "").split(","); - return raw.map((t) => t.trim()).filter((t) => t.length > 0); - }; - - const close = (val) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(val); - }; - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(null)); - modal - .querySelector(".confirm-cancel") - .addEventListener("click", () => close(null)); - modal.querySelector(".confirm-add").addEventListener("click", () => { - close({ mode: "add", tags: parseTags() }); - }); - modal - .querySelector(".confirm-replace") - .addEventListener("click", () => { - close({ mode: "replace", tags: parseTags() }); - }); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(null); - }); - setTimeout(() => input.focus(), 50); - }); - } - - /** @private */ - _promptGroupMove() { - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const input = modal.querySelector(".group-input"); - const close = (val) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(val); - }; - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(null)); - modal - .querySelector(".confirm-cancel") - .addEventListener("click", () => close(null)); - modal - .querySelector(".confirm-ok") - .addEventListener("click", () => close(input.value.trim())); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(null); - }); - setTimeout(() => input.focus(), 50); - }); - } - - // ── Filtering ──────────────────────────────────────────────────── - - /** - * Returns profiles filtered by the current search query. - * @returns {Array} - * @private - */ - _filteredProfiles() { - if (!this.searchQuery) return this.app.state.profiles; - - return this.app.state.profiles - .map((p) => { - // Match against display name, host, tags, and protocol - const fields = [ - p.displayName || "", - p.host || "", - (p.tags || []).join(" "), - p.protocol || "", - ].join(" "); - const result = fuzzyMatch(fields, this.searchQuery); - return { profile: p, ...result }; - }) - .filter((r) => r.match) - .sort((a, b) => b.score - a.score) - .map((r) => r.profile); - } - - /** - * Returns a display label for a protocol. - * @param {string} protocol - * @returns {string} - * @private - */ - _protocolLabel(protocol) { - switch (protocol) { - case "github": - return "GitHub"; - case "ssh": - return "SSH"; - case "telnet": - return "TELNET"; - case "raw": - return "RAW"; - case "irc": - return "IRC"; - default: - return (protocol || "").toUpperCase() || "UNKNOWN"; - } - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.Sidebar = Sidebar; -})(); diff --git a/frontend/src/js/snippets.js b/frontend/src/js/snippets.js deleted file mode 100644 index 172ceae..0000000 --- a/frontend/src/js/snippets.js +++ /dev/null @@ -1,454 +0,0 @@ -(function () { - /** - * Alchementary Link — snippet / command library. - * Provides a panel for managing and sending reusable commands to active sessions. - */ - - const { createElement, escapeHtml, dismissContextMenus } = - window.AlchementaryLink; - - class SnippetLibrary { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - /** @type {Array} */ - this.snippets = []; - this.searchQuery = ""; - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Opens the snippet library modal. - */ - async open() { - await this._loadSnippets(); - this._showModal(); - } - - /** - * Sends a snippet command to the active session. - * Supports {{variable}} placeholders that prompt the user for values. - * Built-in variables: {{hostname}}, {{date}}, {{time}}, {{timestamp}}. - * @param {string} command - */ - sendToActiveSession(command) { - const sessionId = this.app.state.activeSessionId; - if (!sessionId) { - this.app.notifications.error("No active session"); - return; - } - const session = this.app.state.getSession(sessionId); - if (!session || session.state !== "connected") { - this.app.notifications.error("Session is not connected"); - return; - } - - // Check for template variables - const varPattern = /\{\{([^}]+)\}\}/g; - const matches = []; - let match; - while ((match = varPattern.exec(command)) !== null) { - matches.push(match[1].trim()); - } - - // Resolve built-in variables first - const builtins = { - hostname: session.host || "unknown", - date: new Date().toISOString().split("T")[0], - time: new Date().toTimeString().split(" ")[0], - timestamp: Math.floor(Date.now() / 1000).toString(), - }; - - let resolved = command; - const userVars = []; - for (const varName of matches) { - const lower = varName.toLowerCase(); - if (builtins[lower] !== undefined) { - resolved = resolved.replace("{{" + varName + "}}", builtins[lower]); - } else if (!userVars.includes(varName)) { - userVars.push(varName); - } - } - - if (userVars.length === 0) { - this._sendCommand(sessionId, resolved); - return; - } - - // Prompt for user-defined variables - this._promptVariables(userVars, (values) => { - let final = resolved; - for (let i = 0; i < userVars.length; i++) { - final = final.split("{{" + userVars[i] + "}}").join(values[i] || ""); - } - this._sendCommand(sessionId, final); - }); - } - - /** @private */ - _sendCommand(sessionId, command) { - try { - const b64 = window.AlchementaryLink.utf8ToBase64(command + "\n"); - window.AlchementaryLink.wailsService.WriteSession(sessionId, b64); - this.app.notifications.info("Snippet sent"); - } catch (err) { - this.app.notifications.error("Failed to send snippet: " + err); - } - } - - /** @private */ - _promptVariables(varNames, onConfirm) { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - - let inputsHtml = ""; - for (const v of varNames) { - inputsHtml += ` -
- - -
- `; - } - - modal.innerHTML = ` - - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - const firstInput = modal.querySelector(".snippet-var-input"); - if (firstInput) firstInput.focus(); - }); - - const close = () => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - }; - - modal.querySelector(".modal-close").addEventListener("click", close); - modal - .querySelector(".snippet-var-cancel") - .addEventListener("click", close); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(); - }); - - modal.querySelector(".snippet-var-send").addEventListener("click", () => { - const inputs = modal.querySelectorAll(".snippet-var-input"); - const values = Array.from(inputs).map((inp) => inp.value); - close(); - onConfirm(values); - }); - - // Enter to send - modal.addEventListener("keydown", (e) => { - if (e.key === "Enter") { - e.preventDefault(); - modal.querySelector(".snippet-var-send").click(); - } - if (e.key === "Escape") close(); - }); - } - - // ── Data ───────────────────────────────────────────────────────── - - /** @private */ - async _loadSnippets() { - try { - this.snippets = (await window.AlchementaryLink.wailsService.ListSnippets()) || []; - } catch (err) { - this.snippets = []; - console.error("Failed to load snippets:", err); - } - } - - // ── Modal ──────────────────────────────────────────────────────── - - /** @private */ - _showModal() { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-lg"); - - modal.innerHTML = ` - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = () => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - }; - - modal.querySelector(".modal-close").addEventListener("click", close); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(); - }); - - // Search - const searchInput = modal.querySelector(".snippet-search"); - searchInput.addEventListener("input", () => { - this.searchQuery = searchInput.value.trim().toLowerCase(); - this._renderList(modal, close); - }); - - // New snippet button - modal.querySelector(".snippet-add-btn").addEventListener("click", () => { - this._showEditor(modal, null, close); - }); - - this._renderList(modal, close); - } - - /** @private */ - _renderList(modal, closeFn) { - const listEl = modal.querySelector(".snippet-list"); - if (!listEl) return; - listEl.innerHTML = ""; - - const filtered = this.snippets.filter((sn) => { - if (!this.searchQuery) return true; - return ( - sn.name.toLowerCase().includes(this.searchQuery) || - sn.command.toLowerCase().includes(this.searchQuery) || - (sn.description || "").toLowerCase().includes(this.searchQuery) - ); - }); - - if (filtered.length === 0) { - listEl.innerHTML = - '
No snippets found
'; - return; - } - - for (const sn of filtered) { - const item = createElement("div", "snippet-item", { - dataset: { id: sn.id }, - }); - item.style.cssText = - "padding:var(--space-2);border-radius:var(--radius-md);cursor:pointer;border:1px solid var(--border-subtle);"; - item.innerHTML = ` -
${escapeHtml(sn.name)}
-
${escapeHtml(sn.command)}
- `; - item.addEventListener("click", () => - this._showDetail(modal, sn, closeFn), - ); - item.addEventListener("mousedown", (e) => e.preventDefault()); - item.addEventListener("mouseenter", () => { - item.style.background = "var(--bg-hover)"; - }); - item.addEventListener("mouseleave", () => { - item.style.background = ""; - }); - listEl.appendChild(item); - } - } - - /** @private */ - _showDetail(modal, snippet, closeFn) { - const detailPanel = modal.querySelector(".snippet-detail-panel"); - if (!detailPanel) return; - detailPanel.innerHTML = ""; - - const nameEl = createElement("div", "", { - innerHTML: `${escapeHtml(snippet.name)}`, - }); - detailPanel.appendChild(nameEl); - - if (snippet.description) { - const descEl = createElement("div", "", { - textContent: snippet.description, - }); - descEl.style.cssText = - "font-size:var(--text-sm);color:var(--text-secondary);"; - detailPanel.appendChild(descEl); - } - - const cmdEl = createElement("pre", "", { - textContent: snippet.command, - }); - cmdEl.style.cssText = - "flex:1;font-family:var(--font-mono);font-size:var(--text-sm);background:var(--bg-base);padding:var(--space-3);border-radius:var(--radius-md);overflow:auto;color:var(--text-primary);margin:0;white-space:pre-wrap;word-break:break-all;"; - detailPanel.appendChild(cmdEl); - - // Action buttons - const actions = createElement("div", "", {}); - actions.style.cssText = "display:flex;gap:var(--space-2);"; - - const sendBtn = createElement("button", "btn btn-sm btn-primary", { - textContent: "Send to Session", - }); - sendBtn.addEventListener("click", () => { - this.sendToActiveSession(snippet.command); - }); - - const editBtn = createElement("button", "btn btn-sm btn-secondary", { - textContent: "Edit", - }); - editBtn.addEventListener("click", () => { - this._showEditor(modal, snippet, closeFn); - }); - - const deleteBtn = createElement("button", "btn btn-sm btn-danger", { - textContent: "Delete", - }); - deleteBtn.addEventListener("click", async () => { - try { - await window.AlchementaryLink.wailsService.DeleteSnippet(snippet.id); - this.snippets = this.snippets.filter((s) => s.id !== snippet.id); - this._renderList(modal, closeFn); - detailPanel.innerHTML = - '
Snippet deleted
'; - } catch (err) { - this.app.notifications.error("Failed to delete: " + err); - } - }); - - actions.appendChild(sendBtn); - actions.appendChild(editBtn); - actions.appendChild(deleteBtn); - detailPanel.appendChild(actions); - } - - /** @private */ - _showEditor(modal, snippet, closeFn) { - const detailPanel = modal.querySelector(".snippet-detail-panel"); - if (!detailPanel) return; - detailPanel.innerHTML = ""; - - const isNew = !snippet; - const title = isNew ? "New Snippet" : "Edit Snippet"; - - detailPanel.innerHTML = ` -
${title}
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- `; - - detailPanel - .querySelector(".snippet-save-btn") - .addEventListener("click", async () => { - const name = detailPanel - .querySelector(".snippet-ed-name") - .value.trim(); - const command = detailPanel.querySelector(".snippet-ed-cmd").value; - const description = detailPanel - .querySelector(".snippet-ed-desc") - .value.trim(); - const folder = detailPanel - .querySelector(".snippet-ed-folder") - .value.trim(); - - if (!name) { - this.app.notifications.error("Name is required"); - return; - } - if (!command) { - this.app.notifications.error("Command is required"); - return; - } - - try { - const data = { - id: isNew ? "" : snippet.id, - name, - command, - description, - folder, - tags: isNew ? [] : snippet.tags || [], - }; - - let saved; - if (isNew) { - saved = await window.AlchementaryLink.wailsService.CreateSnippet(data); - this.snippets.push(saved); - } else { - saved = await window.AlchementaryLink.wailsService.UpdateSnippet(data); - const idx = this.snippets.findIndex((s) => s.id === saved.id); - if (idx !== -1) this.snippets[idx] = saved; - } - - this._renderList(modal, closeFn); - this._showDetail(modal, saved, closeFn); - this.app.notifications.info( - isNew ? "Snippet created" : "Snippet updated", - ); - } catch (err) { - this.app.notifications.error("Failed to save snippet: " + err); - } - }); - - detailPanel - .querySelector(".snippet-cancel-btn") - .addEventListener("click", () => { - if (snippet) { - this._showDetail(modal, snippet, closeFn); - } else { - detailPanel.innerHTML = - '
Select a snippet or create a new one
'; - } - }); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.SnippetLibrary = SnippetLibrary; -})(); diff --git a/frontend/src/js/splitpane.js b/frontend/src/js/splitpane.js deleted file mode 100644 index dedc35b..0000000 --- a/frontend/src/js/splitpane.js +++ /dev/null @@ -1,797 +0,0 @@ -(function() { -/** - * Alchementary Link — Split Pane Manager. - * Enables splitting session panels into multiple panes, each hosting - * its own terminal session. Supports horizontal (top/bottom) and - * vertical (left/right) splits with draggable resize dividers. - */ - -var MIN_PANE_SIZE = 100; // px - -/** - * Unique pane id counter. - * @type {number} - */ -var _paneIdCounter = 0; - -function nextPaneId() { - return 'pane-' + (++_paneIdCounter); -} - -class SplitPaneManager { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - - /** - * Map of sessionId → split tree root (or null if unsplit). - * The tree is a simple object: - * Leaf: { type: 'leaf', paneId, el, sessionId } - * Branch: { type: 'branch', direction, first, second, el, divider, ratio } - * @type {Map} - */ - this.trees = new Map(); - - /** - * Connections started from placeholders but not yet attached to a pane. - * Closing a pane/tab marks its token cancelled so a late backend result - * is disconnected instead of becoming an invisible session. - * @type {Map} - */ - this.pendingConnections = new Map(); - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Splits the active session panel vertically (left | right). - */ - splitVertical() { - this._splitActive('vertical'); - } - - /** - * Splits the active session panel horizontally (top / bottom). - */ - splitHorizontal() { - this._splitActive('horizontal'); - } - - /** - * Removes all split state for a session (used on tab close). - * @param {string} sessionId - */ - async cleanup(sessionId) { - var root = this.trees.get(sessionId); - this.trees.delete(sessionId); - - var childSessionIds = new Set(); - var self = this; - function releaseNode(node) { - if (!node) return; - if (node.type === 'leaf') { - self._cancelPendingConnection(node.paneId); - if (node.sessionId && node.sessionId !== sessionId) { - childSessionIds.add(node.sessionId); - } - if (node.el) node.el._splitNode = null; - node.parent = null; - return; - } - if (node.cancelDrag) node.cancelDrag(); - releaseNode(node.first); - releaseNode(node.second); - if (node.el) node.el._splitNode = null; - node.parent = null; - } - releaseNode(root); - - // A connection may still be resolving before its leaf has a session ID. - // Its token remains captured by the promise and will disconnect any late - // result after _cancelPendingConnection marks it cancelled. - for (var [paneId, token] of this.pendingConnections) { - if (token.ownerSessionId === sessionId) { - token.cancelled = true; - this.pendingConnections.delete(paneId); - } - } - - await Promise.allSettled(Array.from(childSessionIds, function(childId) { - return self.app.closeSessionImmediate(childId, { - removeTab: false, - cleanupSplits: false - }); - })); - } - - /** - * Re-fits all terminals visible in the active session's pane tree. - */ - refitAll() { - var activeId = this.app.state.activeSessionId; - if (!activeId) return; - var tree = this.trees.get(activeId); - if (tree) this._refitTree(tree); - } - - // ── Core split logic ───────────────────────────────────────────── - - /** - * Splits the active session panel. - * @param {'horizontal'|'vertical'} direction - * @private - */ - _splitActive(direction) { - var sessionId = this.app.state.activeSessionId; - if (!sessionId) return; - - var panel = document.getElementById('panel-' + sessionId); - if (!panel) return; - - var tree = this.trees.get(sessionId); - - if (!tree) { - // First split — wrap existing content - tree = this._initTree(panel, sessionId); - } - - // Find the focused leaf (or the first leaf) - var leaf = this._findFocusedLeaf(tree) || this._firstLeaf(tree); - if (!leaf) return; - - this._splitLeaf(leaf, direction, sessionId); - } - - /** - * Creates the initial tree structure from an unsplit panel. - * Wraps the existing terminal-container in a split-pane leaf. - * @param {HTMLElement} panel - * @param {string} sessionId - * @returns {Object} root leaf node - * @private - */ - _initTree(panel, sessionId) { - var termContainer = panel.querySelector('.terminal-container'); - // For non-terminal panels (raw, github), wrap whatever content exists - var contentChildren = Array.from(panel.children); - - var paneId = nextPaneId(); - var paneEl = document.createElement('div'); - paneEl.className = 'split-pane'; - paneEl.dataset.paneId = paneId; - paneEl.style.flex = '1'; - - // Move existing content into the pane - contentChildren.forEach(function(child) { - paneEl.appendChild(child); - }); - - // Reset terminal-container from absolute to relative positioning inside a pane - if (termContainer) { - termContainer.style.position = 'relative'; - termContainer.style.inset = 'auto'; - termContainer.style.flex = '1'; - termContainer.style.width = '100%'; - termContainer.style.height = '100%'; - } - - panel.appendChild(paneEl); - - var leaf = { - type: 'leaf', - paneId: paneId, - el: paneEl, - sessionId: sessionId, - parent: null - }; - - paneEl._splitNode = leaf; - this.trees.set(sessionId, leaf); - - // Add focus tracking - this._addFocusTracking(paneEl); - - return leaf; - } - - /** - * Splits a leaf node into two panes. - * @param {Object} leaf - The leaf node to split. - * @param {'horizontal'|'vertical'} direction - * @param {string} ownerSessionId - The session that owns this panel. - * @private - */ - _splitLeaf(leaf, direction, ownerSessionId) { - var parentBranch = leaf.parent; - var panel = this._findPanel(ownerSessionId); - if (!panel) return; - - // Create a container for the two panes - var containerEl = document.createElement('div'); - containerEl.className = 'split-container split-' + direction; - containerEl.style.flex = leaf.el.style.flex || '1'; - - // The existing leaf becomes the first child - leaf.el.style.flex = '1'; - - // Create divider - var divider = document.createElement('div'); - divider.className = 'split-divider'; - - // Create new empty pane - var newPaneId = nextPaneId(); - var newPaneEl = document.createElement('div'); - newPaneEl.className = 'split-pane'; - newPaneEl.dataset.paneId = newPaneId; - newPaneEl.style.flex = '1'; - - // Add a placeholder with session picker to the new pane - this._renderPlaceholder(newPaneEl, ownerSessionId); - - // Add close buttons to both panes - this._addCloseButton(leaf.el, ownerSessionId); - this._addCloseButton(newPaneEl, ownerSessionId); - - // Add focus tracking - this._addFocusTracking(newPaneEl); - - // Build the new branch node - var newLeaf = { - type: 'leaf', - paneId: newPaneId, - el: newPaneEl, - sessionId: null, - parent: null - }; - newPaneEl._splitNode = newLeaf; - - var branch = { - type: 'branch', - direction: direction, - first: leaf, - second: newLeaf, - el: containerEl, - divider: divider, - ratio: 0.5, - parent: parentBranch - }; - - leaf.parent = branch; - newLeaf.parent = branch; - containerEl._splitNode = branch; - - // Replace the leaf element in the DOM - var leafParent = leaf.el.parentNode; - leafParent.insertBefore(containerEl, leaf.el); - containerEl.appendChild(leaf.el); - containerEl.appendChild(divider); - containerEl.appendChild(newPaneEl); - - // Update the tree structure - if (parentBranch) { - if (parentBranch.first === leaf) { - parentBranch.first = branch; - } else { - parentBranch.second = branch; - } - } else { - // This was the root leaf — the branch becomes the new root - this.trees.set(ownerSessionId, branch); - } - - // Set up divider dragging - this._setupDividerDrag(divider, branch); - - // Refit terminals after layout change - var self = this; - requestAnimationFrame(function() { - self._refitTree(self.trees.get(ownerSessionId)); - }); - } - - // ── Pane closing ───────────────────────────────────────────────── - - /** - * Closes a pane and unsplits, keeping the sibling. - * @param {string} paneId - * @param {string} ownerSessionId - * @private - */ - _closePane(paneId, ownerSessionId) { - var root = this.trees.get(ownerSessionId); - if (!root) return; - - var leaf = this._findLeafById(root, paneId); - if (!leaf || !leaf.parent) { - // Only one pane left — can't close it - return; - } - - var branch = leaf.parent; - var sibling = (branch.first === leaf) ? branch.second : branch.first; - - this._cancelPendingConnection(leaf.paneId); - - // If the closing pane has a child session, release both its frontend - // renderer and backend connection. The owner pane cannot be closed - // independently because it provides the tab's identity. - if (leaf.sessionId && leaf.sessionId !== ownerSessionId) { - this.app.closeSessionImmediate(leaf.sessionId, { - removeTab: false, - cleanupSplits: false - }); - leaf.sessionId = null; - } - - if (branch.cancelDrag) branch.cancelDrag(); - - // Replace the branch element with the sibling element in the DOM - sibling.el.style.flex = branch.el.style.flex || '1'; - branch.el.parentNode.insertBefore(sibling.el, branch.el); - branch.el.remove(); - leaf.el._splitNode = null; - branch.el._splitNode = null; - - // Remove close button from sibling if it's now the only pane - sibling.parent = branch.parent; - if (branch.parent) { - if (branch.parent.first === branch) { - branch.parent.first = sibling; - } else { - branch.parent.second = sibling; - } - } else { - // Sibling is now root - this.trees.set(ownerSessionId, sibling); - } - - // Remove close button if we're back to a single leaf - var newRoot = this.trees.get(ownerSessionId); - if (newRoot && newRoot.type === 'leaf') { - var closeBtn = newRoot.el.querySelector(':scope > .split-pane-close'); - if (closeBtn) closeBtn.remove(); - } - - // Refit terminals - var self = this; - requestAnimationFrame(function() { - self._refitTree(self.trees.get(ownerSessionId)); - }); - } - - // ── Divider drag ───────────────────────────────────────────────── - - /** - * Sets up mouse drag behaviour on a divider. - * @param {HTMLElement} divider - * @param {Object} branch - * @private - */ - _setupDividerDrag(divider, branch) { - var self = this; - var onMouseMove = null; - var onMouseUp = null; - var bodyClass = null; - - function stopDrag(refit) { - if (bodyClass) document.body.classList.remove(bodyClass); - divider.classList.remove('is-dragging'); - if (onMouseMove) document.removeEventListener('mousemove', onMouseMove); - if (onMouseUp) document.removeEventListener('mouseup', onMouseUp); - onMouseMove = null; - onMouseUp = null; - bodyClass = null; - if (refit) self._refitTree(branch); - } - - branch.cancelDrag = function() { - stopDrag(false); - }; - - divider.addEventListener('mousedown', function(e) { - e.preventDefault(); - stopDrag(false); - var isHorizontal = branch.direction === 'horizontal'; - bodyClass = isHorizontal ? 'split-resizing-row' : 'split-resizing'; - document.body.classList.add(bodyClass); - divider.classList.add('is-dragging'); - - var containerRect = branch.el.getBoundingClientRect(); - var dividerSize = isHorizontal ? 4 : 4; // divider thickness - - onMouseMove = function(e) { - var totalSize, mousePos; - if (isHorizontal) { - totalSize = containerRect.height - dividerSize; - mousePos = e.clientY - containerRect.top; - } else { - totalSize = containerRect.width - dividerSize; - mousePos = e.clientX - containerRect.left; - } - - var ratio = mousePos / (totalSize + dividerSize); - ratio = Math.max(MIN_PANE_SIZE / (totalSize + dividerSize), ratio); - ratio = Math.min(1 - MIN_PANE_SIZE / (totalSize + dividerSize), ratio); - - branch.ratio = ratio; - branch.first.el.style.flex = ratio + ''; - branch.second.el.style.flex = (1 - ratio) + ''; - - // Refit terminals in this branch - self._refitTree(branch); - }; - - onMouseUp = function() { - stopDrag(true); - }; - - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - }); - } - - // ── Placeholder / session picker ───────────────────────────────── - - /** - * Renders a placeholder with a list of available profiles to connect. - * @param {HTMLElement} paneEl - * @param {string} ownerSessionId - * @private - */ - _renderPlaceholder(paneEl, ownerSessionId) { - var placeholder = document.createElement('div'); - placeholder.className = 'split-pane-placeholder'; - - var icon = document.createElement('div'); - icon.className = 'split-pane-placeholder-icon'; - icon.innerHTML = ''; - - var text = document.createElement('div'); - text.className = 'split-pane-placeholder-text'; - text.textContent = 'Select a connection for this pane'; - - placeholder.appendChild(icon); - placeholder.appendChild(text); - - // Build profile list - var profiles = this.app.state.profiles || []; - var list = document.createElement('div'); - list.className = 'split-pane-profile-list'; - - var self = this; - var localBtn = document.createElement('button'); - localBtn.className = 'split-pane-profile-btn'; - - var localBadge = document.createElement('span'); - localBadge.className = 'protocol-badge'; - localBadge.textContent = 'LOCAL'; - localBadge.style.color = self._protocolColor('local'); - - var localLabel = document.createElement('span'); - localLabel.textContent = 'Local Machine'; - - localBtn.appendChild(localBadge); - localBtn.appendChild(localLabel); - localBtn.addEventListener('click', function() { - self._connectLocalInPane(paneEl, ownerSessionId); - }); - list.appendChild(localBtn); - - profiles.forEach(function(profile) { - var btn = document.createElement('button'); - btn.className = 'split-pane-profile-btn'; - - var badge = document.createElement('span'); - badge.className = 'protocol-badge'; - badge.textContent = (profile.protocol || 'ssh').toUpperCase(); - badge.style.color = self._protocolColor(profile.protocol); - - var label = document.createElement('span'); - label.textContent = profile.displayName || profile.host || profile.id; - - btn.appendChild(badge); - btn.appendChild(label); - - btn.addEventListener('click', function() { - self._connectInPane(paneEl, profile, ownerSessionId); - }); - - list.appendChild(btn); - }); - - placeholder.appendChild(list); - - paneEl.appendChild(placeholder); - } - - /** - * Connects a profile in a specific pane. - * @param {HTMLElement} paneEl - * @param {Object} profile - * @param {string} ownerSessionId - * @private - */ - _connectInPane(paneEl, profile, ownerSessionId) { - var self = this; - var token = this._beginPendingConnection(paneEl, ownerSessionId); - - // Remove placeholder - var placeholder = paneEl.querySelector('.split-pane-placeholder'); - if (placeholder) placeholder.remove(); - - // Connect via backend - window.AlchementaryLink.wailsService.ConnectSession(profile.id).then(async function(sessionInfo) { - if (!self._completePendingConnection(paneEl, ownerSessionId, token)) { - try { - await window.AlchementaryLink.wailsService.DisconnectSession(sessionInfo.id); - } catch (_) { - // The backend may already have completed the close. - } - return; - } - self._attachTerminalToPane(paneEl, sessionInfo, profile); - - self.app.notifications.info('Connecting to ' + (profile.displayName || profile.host) + '…'); - }).catch(function(err) { - if (!self._completePendingConnection(paneEl, ownerSessionId, token)) { - return; - } - self.app.notifications.error('Connection failed: ' + err); - // Re-render placeholder on failure - self._renderPlaceholder(paneEl, ownerSessionId); - }); - } - - /** - * Opens a local shell in a specific pane. - * @param {HTMLElement} paneEl - * @param {string} ownerSessionId - * @private - */ - _connectLocalInPane(paneEl, ownerSessionId) { - var self = this; - var token = this._beginPendingConnection(paneEl, ownerSessionId); - var placeholder = paneEl.querySelector('.split-pane-placeholder'); - if (placeholder) placeholder.remove(); - - window.AlchementaryLink.wailsService.ConnectLocal().then(async function(sessionInfo) { - if (!self._completePendingConnection(paneEl, ownerSessionId, token)) { - try { - await window.AlchementaryLink.wailsService.DisconnectSession(sessionInfo.id); - } catch (_) { - // The backend may already have completed the close. - } - return; - } - var prefs = self.app.state.preferences || {}; - self._attachTerminalToPane(paneEl, sessionInfo, { - terminalFontFamily: prefs.defaultFontFamily, - terminalFontSize: prefs.defaultFontSize, - terminalScrollback: prefs.defaultScrollback, - terminalCursorStyle: prefs.defaultCursorStyle - }); - self.app.notifications.info('Opening local terminal…'); - }).catch(function(err) { - if (!self._completePendingConnection(paneEl, ownerSessionId, token)) { - return; - } - self.app.notifications.error('Local terminal failed: ' + err); - self._renderPlaceholder(paneEl, ownerSessionId); - }); - } - - /** - * Attaches a connected terminal session to a split pane. - * @param {HTMLElement} paneEl - * @param {Object} sessionInfo - * @param {Object} terminalProfile - * @private - */ - _attachTerminalToPane(paneEl, sessionInfo, terminalProfile) { - this.app.state.addSession(sessionInfo); - - var termContainer = document.createElement('div'); - termContainer.className = 'terminal-container'; - termContainer.id = 'split-term-' + sessionInfo.id; - termContainer.style.position = 'relative'; - termContainer.style.flex = '1'; - termContainer.style.width = '100%'; - termContainer.style.height = '100%'; - paneEl.appendChild(termContainer); - - var node = paneEl._splitNode; - if (node) { - node.sessionId = sessionInfo.id; - } - - this.app.terminals.createTerminal(sessionInfo.id, termContainer.id, terminalProfile); - } - - /** - * Tracks an asynchronous connection attempt owned by a pane. - * @private - */ - _beginPendingConnection(paneEl, ownerSessionId) { - var paneId = paneEl.dataset.paneId; - this._cancelPendingConnection(paneId); - var token = { - ownerSessionId: ownerSessionId, - cancelled: false, - completed: false - }; - this.pendingConnections.set(paneId, token); - return token; - } - - /** - * Returns true only if a connection result still belongs to a live pane. - * @private - */ - _completePendingConnection(paneEl, ownerSessionId, token) { - if (token.completed) return false; - token.completed = true; - var paneId = paneEl.dataset.paneId; - if (this.pendingConnections.get(paneId) === token) { - this.pendingConnections.delete(paneId); - } - var root = this.trees.get(ownerSessionId); - var leaf = this._findLeafById(root, paneId); - return !token.cancelled && - paneEl.isConnected !== false && - leaf && - leaf.el === paneEl; - } - - /** - * Cancels a pending pane connection without waiting for the backend call. - * A late successful result will disconnect itself. - * @private - */ - _cancelPendingConnection(paneId) { - var token = this.pendingConnections.get(paneId); - if (!token) return; - token.cancelled = true; - this.pendingConnections.delete(paneId); - } - - // ── Tree traversal helpers ─────────────────────────────────────── - - /** - * Finds a leaf by paneId in the tree. - * @param {Object} node - * @param {string} paneId - * @returns {Object|null} - * @private - */ - _findLeafById(node, paneId) { - if (!node) return null; - if (node.type === 'leaf') { - return node.paneId === paneId ? node : null; - } - return this._findLeafById(node.first, paneId) || this._findLeafById(node.second, paneId); - } - - /** - * Returns the first leaf node in the tree. - * @param {Object} node - * @returns {Object|null} - * @private - */ - _firstLeaf(node) { - if (!node) return null; - if (node.type === 'leaf') return node; - return this._firstLeaf(node.first); - } - - /** - * Returns the focused leaf node (one whose pane has .is-focused). - * @param {Object} node - * @returns {Object|null} - * @private - */ - _findFocusedLeaf(node) { - if (!node) return null; - if (node.type === 'leaf') { - return node.el.classList.contains('is-focused') ? node : null; - } - return this._findFocusedLeaf(node.first) || this._findFocusedLeaf(node.second); - } - - /** - * Recursively refits all terminals in a tree node. - * @param {Object} node - * @private - */ - _refitTree(node) { - if (!node) return; - if (node.type === 'leaf') { - if (node.sessionId) { - this.app.terminals.fitSession(node.sessionId); - } - return; - } - this._refitTree(node.first); - this._refitTree(node.second); - } - - /** - * Finds the panel element for a session. - * @param {string} sessionId - * @returns {HTMLElement|null} - * @private - */ - _findPanel(sessionId) { - return document.getElementById('panel-' + sessionId); - } - - // ── UI helpers ─────────────────────────────────────────────────── - - /** - * Adds a close button to a pane element. - * @param {HTMLElement} paneEl - * @param {string} ownerSessionId - * @private - */ - _addCloseButton(paneEl, ownerSessionId) { - // Don't add if already present - if (paneEl.querySelector(':scope > .split-pane-close')) return; - // The owner session supplies the tab/panel identity. Allowing that leaf - // to close would leave an invisible backend session behind. - if (paneEl._splitNode && paneEl._splitNode.sessionId === ownerSessionId) return; - - var btn = document.createElement('button'); - btn.className = 'split-pane-close'; - btn.textContent = '✕'; - btn.title = 'Close pane'; - - var self = this; - btn.addEventListener('click', function(e) { - e.stopPropagation(); - self._closePane(paneEl.dataset.paneId, ownerSessionId); - }); - - paneEl.appendChild(btn); - } - - /** - * Adds focus tracking to a pane — marks it as focused when clicked. - * @param {HTMLElement} paneEl - * @private - */ - _addFocusTracking(paneEl) { - paneEl.addEventListener('mousedown', function() { - // Remove focus from all sibling panes - var allPanes = paneEl.closest('.session-panel'); - if (allPanes) { - allPanes.querySelectorAll('.split-pane.is-focused').forEach(function(el) { - el.classList.remove('is-focused'); - }); - } - paneEl.classList.add('is-focused'); - }); - } - - /** - * Returns a colour for a protocol badge. - * @param {string} protocol - * @returns {string} - * @private - */ - _protocolColor(protocol) { - var colors = { - ssh: '#4ecdc4', - telnet: '#ffa726', - raw: '#ab47bc', - github: '#7c8aff', - local: '#66bb6a' - }; - return colors[protocol] || '#4ecdc4'; - } -} - -window.AlchementaryLink = window.AlchementaryLink || {}; -window.AlchementaryLink.SplitPaneManager = SplitPaneManager; - -})(); diff --git a/frontend/src/js/sshkeys.js b/frontend/src/js/sshkeys.js deleted file mode 100644 index 03051e9..0000000 --- a/frontend/src/js/sshkeys.js +++ /dev/null @@ -1,337 +0,0 @@ -(function () { - /** - * Alchementary Link — SSH Key Manager. - * Panel for listing, generating, copying, and deleting SSH key pairs. - */ - - const { createElement, dismissContextMenus } = window.AlchementaryLink; - - class SSHKeyManager { - constructor(app) { - this.app = app; - this._backdrop = null; - } - - // ── Public API ─────────────────────────────────────────────────── - - open() { - if (this._backdrop) return; - this._render(); - this._loadKeys(); - } - - close() { - if (this._backdrop) { - this._backdrop.classList.remove("is-visible"); - const modal = this._backdrop.querySelector(".modal"); - if (modal) modal.classList.remove("is-visible"); - setTimeout(() => { - if (this._backdrop) { - this._backdrop.remove(); - this._backdrop = null; - } - }, 200); - } - } - - // ── Rendering ──────────────────────────────────────────────────── - - _render() { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-lg"); - - modal.innerHTML = ` - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - this._backdrop = backdrop; - - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - // Events - modal - .querySelector(".modal-close") - .addEventListener("click", () => this.close()); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) this.close(); - }); - modal - .querySelector(".sshkey-generate-btn") - .addEventListener("click", () => this._showGenerateForm()); - } - - async _loadKeys() { - const list = this._backdrop.querySelector(".sshkey-list"); - if (!list) return; - - list.innerHTML = '
Loading keys…
'; - - try { - const keys = await window.AlchementaryLink.wailsService.ListSSHKeyPairs(); - if (keys.length === 0) { - list.innerHTML = - '
No SSH keys found in ~/.ssh/
'; - return; - } - list.innerHTML = ""; - for (const key of keys) { - list.appendChild(this._renderKeyRow(key)); - } - } catch (err) { - list.innerHTML = ""; - list.appendChild( - createElement("div", "sshkey-error", { - textContent: "Failed to load keys: " + String(err), - }), - ); - } - } - - _renderKeyRow(key) { - const row = createElement("div", "sshkey-row"); - - const info = createElement("div", "sshkey-info"); - const name = createElement("div", "sshkey-name", { - textContent: key.name, - }); - const meta = createElement("div", "sshkey-meta", { - textContent: key.type.toUpperCase() + (key.bits ? ` ${key.bits}` : ""), - }); - info.appendChild(name); - info.appendChild(meta); - - const actions = createElement("div", "sshkey-actions"); - - const copyBtn = createElement("button", "btn btn-xs", { - textContent: "Copy Public Key", - }); - copyBtn.addEventListener("mousedown", (e) => e.preventDefault()); - copyBtn.addEventListener("click", () => this._copyPublicKey(key)); - - const deleteBtn = createElement("button", "btn btn-xs btn-danger", { - textContent: "Delete", - }); - deleteBtn.addEventListener("mousedown", (e) => e.preventDefault()); - deleteBtn.addEventListener("click", () => this._confirmDelete(key)); - - actions.appendChild(copyBtn); - actions.appendChild(deleteBtn); - - row.appendChild(info); - row.appendChild(actions); - return row; - } - - async _copyPublicKey(key) { - try { - const pubKey = await window.AlchementaryLink.wailsService.GetSSHPublicKey(key.name); - if (window.runtime && window.runtime.ClipboardSetText) { - window.runtime.ClipboardSetText(pubKey); - } else { - await navigator.clipboard.writeText(pubKey); - } - this.app.notifications.success("Public key copied to clipboard"); - } catch (err) { - this.app.notifications.error("Failed to copy: " + err); - } - } - - _confirmDelete(key) { - const confirmed = this._showConfirm( - "Delete Key", - `Delete key pair "${key.name}"? This cannot be undone.`, - "Delete", - ); - confirmed.then((ok) => { - if (ok) this._deleteKey(key); - }); - } - - async _deleteKey(key) { - try { - await window.AlchementaryLink.wailsService.DeleteSSHKey(key.name); - this.app.notifications.success(`Key "${key.name}" deleted`); - this._loadKeys(); - } catch (err) { - this.app.notifications.error("Delete failed: " + err); - } - } - - // ── Generate form ──────────────────────────────────────────────── - - _showGenerateForm() { - const list = this._backdrop.querySelector(".sshkey-list"); - if (!list) return; - - const form = createElement("div", "sshkey-generate-form"); - form.innerHTML = ` -
- - -
-
- - -
- -
- - -
-
- - -
- `; - - // Insert form above the key list - list.parentNode.insertBefore(form, list); - list.style.display = "none"; - - // Show bits field for RSA - const typeSelect = form.querySelector(".sshkey-type-select"); - const bitsGroup = form.querySelector(".sshkey-bits-group"); - typeSelect.addEventListener("change", () => { - bitsGroup.style.display = typeSelect.value === "rsa" ? "" : "none"; - }); - - form.querySelector(".sshkey-cancel-btn").addEventListener("click", () => { - form.remove(); - list.style.display = ""; - }); - - form - .querySelector(".sshkey-create-btn") - .addEventListener("click", async () => { - const nameInput = form.querySelector(".sshkey-name-input"); - const name = nameInput.value.trim(); - const keyType = typeSelect.value; - const bits = parseInt( - form.querySelector(".sshkey-bits-select").value, - 10, - ); - const passphrase = form.querySelector(".sshkey-pass-input").value; - - if (!name) { - this.app.notifications.error("Key name is required"); - return; - } - - try { - await window.AlchementaryLink.wailsService.GenerateSSHKey( - name, - keyType, - bits, - passphrase, - ); - this.app.notifications.success( - `Key "${name}" generated successfully`, - ); - form.remove(); - list.style.display = ""; - this._loadKeys(); - } catch (err) { - this.app.notifications.error("Generation failed: " + err); - } - }); - - form.querySelector(".sshkey-name-input").focus(); - } - - // ── Confirmation helper ────────────────────────────────────────── - - _showConfirm(title, message, confirmLabel) { - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - const header = createElement("div", "modal-header"); - header.appendChild( - createElement("h3", "modal-title", { textContent: title }), - ); - header.appendChild( - createElement("button", "btn btn-icon btn-xs modal-close", { - textContent: "x", - type: "button", - "aria-label": "Close", - }), - ); - - const body = createElement("div", "modal-body"); - const messageEl = createElement("p", "", { textContent: message }); - messageEl.style.fontSize = "var(--text-sm)"; - messageEl.style.color = "var(--text-secondary)"; - body.appendChild(messageEl); - - const footer = createElement("div", "modal-footer"); - footer.appendChild( - createElement("button", "btn btn-sm confirm-cancel", { - textContent: "Cancel", - type: "button", - }), - ); - footer.appendChild( - createElement("button", "btn btn-sm btn-danger confirm-ok", { - textContent: confirmLabel, - type: "button", - }), - ); - - modal.appendChild(header); - modal.appendChild(body); - modal.appendChild(footer); - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = (val) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(val); - }; - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-cancel") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-ok") - .addEventListener("click", () => close(true)); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(false); - }); - }); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.SSHKeyManager = SSHKeyManager; -})(); diff --git a/frontend/src/js/state.js b/frontend/src/js/state.js deleted file mode 100644 index cdf72db..0000000 --- a/frontend/src/js/state.js +++ /dev/null @@ -1,240 +0,0 @@ -(function () { - /** - * Alchementary Link — application state management. - * Provides a centralised store for profiles, sessions, and preferences, - * emitting change events so the UI stays in sync. - */ - - class State { - constructor() { - /** @type {Array} */ - this.profiles = []; - - /** @type {Map} sessionId → SessionInfo */ - this.sessions = new Map(); - - /** @type {string|null} */ - this.activeSessionId = null; - - /** @type {Object} */ - this.preferences = {}; - - /** @type {boolean} */ - this.secretStoreUnlocked = false; - - /** @type {boolean} */ - this.secretStoreInitialised = false; - - /** @type {boolean} Whether broadcast mode is active (keystrokes to all sessions). */ - this.broadcastMode = false; - - /** @type {Set|null} Session IDs to broadcast to. Null means all. */ - this.broadcastTargets = null; - - /** @type {Map>} event name → callbacks */ - this._listeners = new Map(); - } - - // ── Event emitter ──────────────────────────────────────────────── - - /** - * Subscribes to a state change event. - * @param {string} event - * @param {Function} callback - */ - on(event, callback) { - if (!this._listeners.has(event)) { - this._listeners.set(event, new Set()); - } - this._listeners.get(event).add(callback); - } - - /** - * Unsubscribes from a state change event. - * @param {string} event - * @param {Function} callback - */ - off(event, callback) { - const set = this._listeners.get(event); - if (set) { - set.delete(callback); - } - } - - /** - * Emits a state change event to all subscribers. - * @param {string} event - * @param {*} data - */ - emit(event, data) { - const set = this._listeners.get(event); - if (set) { - for (const cb of set) { - try { - cb(data); - } catch (err) { - console.error(`State listener error [${event}]:`, err); - } - } - } - } - - // ── Profiles ───────────────────────────────────────────────────── - - /** - * Replaces the entire profiles list. - * @param {Array} profiles - */ - setProfiles(profiles) { - this.profiles = profiles; - this.emit("profiles:changed", this.profiles); - } - - /** - * Adds a single profile. - * @param {Object} profile - */ - addProfile(profile) { - this.profiles.push(profile); - this.emit("profiles:changed", this.profiles); - this.emit("profile:added", profile); - } - - /** - * Updates an existing profile in place. - * @param {Object} profile - */ - updateProfile(profile) { - const idx = this.profiles.findIndex((p) => p.id === profile.id); - if (idx !== -1) { - this.profiles[idx] = profile; - } - this.emit("profiles:changed", this.profiles); - this.emit("profile:updated", profile); - } - - /** - * Removes a profile by ID. - * @param {string} id - */ - removeProfile(id) { - this.profiles = this.profiles.filter((p) => p.id !== id); - this.emit("profiles:changed", this.profiles); - this.emit("profile:removed", id); - } - - /** - * Returns a profile by ID, or undefined. - * @param {string} id - * @returns {Object|undefined} - */ - getProfile(id) { - return this.profiles.find((p) => p.id === id); - } - - // ── Sessions ───────────────────────────────────────────────────── - - /** - * Registers a new session. - * @param {Object} sessionInfo - */ - addSession(sessionInfo) { - this.sessions.set(sessionInfo.id, sessionInfo); - this.emit("sessions:changed", this.sessions); - this.emit("session:added", sessionInfo); - } - - /** - * Merges partial updates into an existing session. - * @param {string} id - * @param {Object} updates - */ - updateSession(id, updates) { - const existing = this.sessions.get(id); - if (existing) { - Object.assign(existing, updates); - this.emit("sessions:changed", this.sessions); - this.emit("session:updated", existing); - } - } - - /** - * Removes a session. - * @param {string} id - */ - removeSession(id) { - this.sessions.delete(id); - if (this.broadcastTargets) { - this.broadcastTargets.delete(id); - } - if (this.activeSessionId === id) { - // Activate the most recent remaining session, or null - const remaining = [...this.sessions.keys()]; - this.activeSessionId = - remaining.length > 0 ? remaining[remaining.length - 1] : null; - } - this.emit("sessions:changed", this.sessions); - this.emit("session:removed", id); - } - - /** - * Returns session info by ID, or undefined. - * @param {string} id - * @returns {Object|undefined} - */ - getSession(id) { - return this.sessions.get(id); - } - - /** - * Sets the currently visible/active session. - * @param {string|null} id - */ - setActiveSession(id) { - this.activeSessionId = id; - this.emit("session:activated", id); - } - - /** - * Returns an ordered array of all sessions. - * @returns {Array} - */ - getAllSessions() { - return [...this.sessions.values()]; - } - - // ── Preferences ────────────────────────────────────────────────── - - /** - * Replaces the entire preferences object. - * @param {Object} prefs - */ - setPreferences(prefs) { - this.preferences = prefs; - this.emit("preferences:changed", this.preferences); - } - - // ── Secret store ───────────────────────────────────────────────── - - /** - * Updates the secret store state. - * @param {boolean} unlocked - */ - setSecretStoreState(unlocked) { - this.secretStoreUnlocked = unlocked; - this.emit("secretStore:changed", unlocked); - } - - /** - * Marks whether the secret store has been initialised (vault exists). - * @param {boolean} initialised - */ - setSecretStoreInitialised(initialised) { - this.secretStoreInitialised = initialised; - this.emit("secretStore:init", initialised); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.State = State; -})(); diff --git a/frontend/src/js/tabs.js b/frontend/src/js/tabs.js deleted file mode 100644 index 42477fe..0000000 --- a/frontend/src/js/tabs.js +++ /dev/null @@ -1,933 +0,0 @@ -(function () { - /** - * Alchementary Link — tab management. - * Handles the tab bar: adding, removing, reordering, renaming, - * pinning tabs, and context menus. - */ - - const { - createElement, - escapeHtml, - protocolColour, - stateLabel, - dismissContextMenus, - } = window.AlchementaryLink; - - class TabManager { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - - /** @type {HTMLElement} The inner tab list where tab elements are inserted. */ - this.tabList = document.getElementById("tab-list"); - - /** @type {HTMLElement} The outer tab bar wrapper (used for drag-and-drop). */ - this.tabBar = document.getElementById("tab-bar"); - - /** @type {HTMLElement} The container for terminal/viewer panels. */ - this.panelContainer = document.getElementById("panel-container"); - - /** @type {string[]} Ordered list of session IDs. */ - this.order = []; - - this.tooltip = null; - this.tooltipTimer = null; - this.tooltipSessionId = null; - - this._setupDragAndDrop(); - this._setupScrolling(); - this._setupTooltip(); - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Adds a new tab for a session and makes it active. - * @param {Object} sessionInfo - */ - addTab(sessionInfo) { - this.order.push(sessionInfo.id); - this._renderTab(sessionInfo); - - // Create panel container for this session's terminal/viewer - const panel = createElement("div", "session-panel", { - id: `panel-${sessionInfo.id}`, - dataset: { sessionId: sessionInfo.id }, - }); - panel.style.display = "none"; - this.panelContainer.appendChild(panel); - - // Hide the empty-state BEFORE activating the panel so the - // panel-container gets the full workspace height immediately. - this._updateEmptyState(); - this._updateScrollState(); - this.setActive(sessionInfo.id); - } - - /** - * Removes a tab and its panel. - * @param {string} sessionId - */ - removeTab(sessionId) { - this._hideTabTooltip(); - const idx = this.order.indexOf(sessionId); - const wasActive = this.app.state.activeSessionId === sessionId; - if (idx !== -1) this.order.splice(idx, 1); - - // Remove tab element - const tabEl = this.tabList.querySelector( - `[data-session-id="${sessionId}"]`, - ); - if (tabEl) tabEl.remove(); - - // Remove panel - const panel = document.getElementById(`panel-${sessionId}`); - if (panel) panel.remove(); - - // Activate the nearest neighbour - if (wasActive) { - const next = - this.order.length > 0 - ? this.order[Math.min(idx, this.order.length - 1)] - : null; - if (next) { - this.setActive(next); - } else { - this.app.state.setActiveSession(null); - } - } - - this._updateEmptyState(); - this._updateScrollState(); - } - - /** - * Switches the visible tab/panel to the given session. - * @param {string} sessionId - */ - setActive(sessionId) { - this.app.state.setActiveSession(sessionId); - - // Highlight active tab and clear activity indicator - this.tabList.querySelectorAll(".tab").forEach((t) => { - t.classList.toggle("tab-active", t.dataset.sessionId === sessionId); - if (t.dataset.sessionId === sessionId) { - t.classList.remove("tab-has-activity"); - } - }); - - // Show corresponding panel, hide others - this.panelContainer.querySelectorAll(".session-panel").forEach((p) => { - p.style.display = p.dataset.sessionId === sessionId ? "flex" : "none"; - }); - - // Notify sidebar - const session = this.app.state.getSession(sessionId); - if (session) { - this.app.sidebar.highlightActive(session.profileId); - } - this.app.leviathan?.handleTabFocus?.(sessionId); - this._scrollTabIntoView(sessionId); - - // Fit and refresh the terminal for the newly active session. - // Double-RAF ensures the panel has layout dimensions before fitting. - requestAnimationFrame(() => { - this.app.terminals.fitSession(sessionId); - const entry = this.app.terminals.terminals.get(sessionId); - if (entry) { - entry.term.refresh(0, entry.term.rows - 1); - } - requestAnimationFrame(() => { - this.app.terminals.focusTerminal(sessionId); - }); - }); - } - - /** - * Closes the currently active tab. - */ - closeActive() { - if (this.app.state.activeSessionId) { - this.closeTab(this.app.state.activeSessionId); - } - } - - /** - * Reconnects a session by ID. - * @param {string} sessionId - */ - reconnectSession(sessionId) { - return this._reconnect(sessionId); - } - - /** - * Disconnects and closes a tab by session ID. - * @param {string} sessionId - */ - async closeTab(sessionId) { - this._hideTabTooltip(); - const session = this.app.state.getSession(sessionId); - - // Ask for confirmation if connected and the preference is set - if ( - session && - session.state === "connected" && - session.protocol !== "leviathan" && - session.protocol !== "github" && - this.app.state.preferences.confirmOnDisconnect - ) { - const ok = await this._confirmClose(sessionId); - if (!ok) return; - } - - await this.app.closeSessionImmediate(sessionId); - } - - /** - * Shows a confirmation modal before closing a connected session. - * @param {string} sessionId - * @returns {Promise} - * @private - */ - _confirmClose(sessionId) { - const createElement = window.AlchementaryLink.createElement; - const label = escapeHtml(this._tabLabel(sessionId)); - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - modal.innerHTML = ` - - - - `; - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = (val) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(val); - }; - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-cancel") - .addEventListener("click", () => close(false)); - modal - .querySelector(".confirm-ok") - .addEventListener("click", () => close(true)); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(false); - }); - }); - } - - /** - * Renames a tab. - * @param {string} sessionId - * @param {string} name - */ - renameTab(sessionId, name) { - this.app.state.updateSession(sessionId, { tabName: name }); - const tabEl = this.tabList.querySelector( - `[data-session-id="${sessionId}"]`, - ); - if (tabEl) { - tabEl.querySelector(".tab-name").textContent = name; - } - } - - /** - * Pins a tab (moves it to the front). - * @param {string} sessionId - */ - pinTab(sessionId) { - this.app.state.updateSession(sessionId, { pinned: true }); - this._reorderDOM(); - this._updateTabStyles(sessionId); - } - - /** - * Unpins a tab. - * @param {string} sessionId - */ - unpinTab(sessionId) { - this.app.state.updateSession(sessionId, { pinned: false }); - this._reorderDOM(); - this._updateTabStyles(sessionId); - } - - /** - * Moves a tab one position to the left. - * @param {string} sessionId - */ - moveLeft(sessionId) { - const idx = this.order.indexOf(sessionId); - if (idx > 0) { - [this.order[idx - 1], this.order[idx]] = [ - this.order[idx], - this.order[idx - 1], - ]; - this._reorderDOM(); - } - } - - /** - * Moves a tab one position to the right. - * @param {string} sessionId - */ - moveRight(sessionId) { - const idx = this.order.indexOf(sessionId); - if (idx >= 0 && idx < this.order.length - 1) { - [this.order[idx], this.order[idx + 1]] = [ - this.order[idx + 1], - this.order[idx], - ]; - this._reorderDOM(); - } - } - - /** - * Opens a new session with the same profile as the given tab. - * @param {string} sessionId - */ - async duplicateTab(sessionId) { - const session = this.app.state.getSession(sessionId); - if (session && session.profileId) { - await this.app.connectProfile(session.profileId); - } - } - - /** - * Updates the visual state of a tab when session state changes. - * @param {string} sessionId - * @param {string} state - connecting | connected | disconnected | error - * @param {string} [error] - */ - updateSessionState(sessionId, state, error) { - this.app.state.updateSession(sessionId, { state, error: error || "" }); - - const tabEl = this.tabList.querySelector( - `[data-session-id="${sessionId}"]`, - ); - if (!tabEl) return; - - const dot = tabEl.querySelector(".tab-status"); - if (dot) { - dot.className = `tab-status status-${state}`; - dot.title = stateLabel(state); - } - - if (state === "error" && error) { - this.app.notifications.error(`${this._tabLabel(sessionId)}: ${error}`); - } - } - - /** - * Handles a session closed event from the backend. - * @param {string} sessionId - */ - handleSessionClosed(sessionId) { - this.updateSessionState(sessionId, "disconnected"); - } - - /** - * Shows a right-click context menu for a tab. - * @param {string} sessionId - * @param {number} x - * @param {number} y - */ - showContextMenu(sessionId, x, y) { - dismissContextMenus(); - const session = this.app.state.getSession(sessionId); - if (!session) return; - - const menu = createElement("div", "context-menu"); - menu.style.left = `${x}px`; - menu.style.top = `${y}px`; - - const isPinned = session.pinned; - - const items = [ - { label: "Rename…", action: () => this._promptRename(sessionId) }, - { label: "Duplicate", action: () => this.duplicateTab(sessionId) }, - { divider: true }, - { - label: isPinned ? "Unpin" : "Pin", - action: () => - isPinned ? this.unpinTab(sessionId) : this.pinTab(sessionId), - }, - { label: "Move Left", action: () => this.moveLeft(sessionId) }, - { label: "Move Right", action: () => this.moveRight(sessionId) }, - { divider: true }, - { - label: "Reconnect", - action: () => this._reconnect(sessionId), - disabled: - session.state === "connected" || session.state === "connecting", - }, - { divider: true }, - { - label: "Close", - className: "context-menu-danger", - action: () => this.closeTab(sessionId), - }, - ]; - - for (const entry of items) { - if (entry.divider) { - menu.appendChild(createElement("div", "context-menu-divider")); - continue; - } - const btn = createElement( - "button", - `context-menu-item ${entry.className || ""}`, - { - textContent: entry.label, - }, - ); - if (entry.disabled) { - btn.disabled = true; - btn.classList.add("context-menu-disabled"); - } - btn.addEventListener("click", () => { - dismissContextMenus(); - entry.action(); - }); - menu.appendChild(btn); - } - - document.body.appendChild(menu); - - // Keep within viewport - const rect = menu.getBoundingClientRect(); - if (rect.right > window.innerWidth) - menu.style.left = `${window.innerWidth - rect.width - 8}px`; - if (rect.bottom > window.innerHeight) - menu.style.top = `${window.innerHeight - rect.height - 8}px`; - } - - /** - * Returns the current tab ordering. - * @returns {string[]} - */ - getTabOrder() { - return [...this.order]; - } - - /** - * Marks a background tab as having new activity. - * Does nothing if the session is currently active. - * @param {string} sessionId - */ - markActivity(sessionId) { - if (sessionId === this.app.state.activeSessionId) return; - const tabEl = this.tabList.querySelector( - `[data-session-id="${sessionId}"]`, - ); - if (tabEl && !tabEl.classList.contains("tab-has-activity")) { - tabEl.classList.add("tab-has-activity"); - } - } - - /** - * Returns the session ID of the next tab (wrapping round). - * @returns {string|null} - */ - getNextTab() { - if (this.order.length === 0) return null; - const idx = this.order.indexOf(this.app.state.activeSessionId); - return this.order[(idx + 1) % this.order.length]; - } - - /** - * Returns the session ID of the previous tab (wrapping round). - * @returns {string|null} - */ - getPrevTab() { - if (this.order.length === 0) return null; - const idx = this.order.indexOf(this.app.state.activeSessionId); - return this.order[(idx - 1 + this.order.length) % this.order.length]; - } - - // ── Rendering ──────────────────────────────────────────────────── - - /** - * Creates and appends a tab element. - * @param {Object} sessionInfo - * @private - */ - _renderTab(sessionInfo) { - const tab = createElement("div", "tab", { - dataset: { sessionId: sessionInfo.id }, - draggable: "true", - }); - - // Close button - const closeBtn = createElement("button", "tab-close", { - textContent: "×", - "aria-label": "Close tab", - }); - closeBtn.addEventListener("click", (e) => { - e.stopPropagation(); - this._hideTabTooltip(); - this.closeTab(sessionInfo.id); - }); - tab.appendChild(closeBtn); - - // Protocol badge - const protoLabel = - sessionInfo.protocol === "github" - ? "GitHub" - : sessionInfo.protocol === "local" - ? "Local" - : sessionInfo.protocol.toUpperCase(); - const badge = createElement( - "span", - `tab-badge badge-${sessionInfo.protocol}`, - { - textContent: protoLabel, - }, - ); - tab.appendChild(badge); - - // Name - const name = createElement("span", "tab-name", { - textContent: sessionInfo.tabName || sessionInfo.protocol.toUpperCase(), - }); - tab.appendChild(name); - - // Status dot - const status = createElement( - "span", - `tab-status status-${sessionInfo.state || "connecting"}`, - { - title: stateLabel(sessionInfo.state), - }, - ); - tab.appendChild(status); - - // Activity indicator (hidden by default, shown when background tab has output) - const activity = createElement("span", "tab-activity"); - tab.appendChild(activity); - - // Pin indicator - if (sessionInfo.pinned) { - tab.classList.add("tab-pinned"); - } - - // SFTP toggle button (SSH sessions only) - if (sessionInfo.protocol === "ssh") { - const sftpBtn = createElement("button", "tab-sftp", { - textContent: "📁", - title: "Toggle SFTP file browser", - "aria-label": "Toggle SFTP", - }); - sftpBtn.addEventListener("click", (e) => { - e.stopPropagation(); - this.app.sftp.toggle(sessionInfo.id); - }); - tab.appendChild(sftpBtn); - } - - // Click → activate - tab.addEventListener("click", () => this.setActive(sessionInfo.id)); - - // Right-click → context menu - tab.addEventListener("contextmenu", (e) => { - e.preventDefault(); - this.showContextMenu(sessionInfo.id, e.clientX, e.clientY); - }); - - this.tabList.appendChild(tab); - } - - // ── Internal helpers ───────────────────────────────────────────── - - /** - * Reorders the tab-bar DOM elements to match `this.order`, - * with pinned tabs always first. - * @private - */ - _reorderDOM() { - // Sort: pinned first, then by order index - const sorted = [...this.order].sort((a, b) => { - const sa = this.app.state.getSession(a); - const sb = this.app.state.getSession(b); - const pinnedA = sa?.pinned ? 0 : 1; - const pinnedB = sb?.pinned ? 0 : 1; - if (pinnedA !== pinnedB) return pinnedA - pinnedB; - return this.order.indexOf(a) - this.order.indexOf(b); - }); - - for (const id of sorted) { - const el = this.tabList.querySelector(`[data-session-id="${id}"]`); - if (el) this.tabList.appendChild(el); - } - this._updateScrollState(); - } - - /** - * Updates the visual pin state on a tab element. - * @param {string} sessionId - * @private - */ - _updateTabStyles(sessionId) { - const session = this.app.state.getSession(sessionId); - const tabEl = this.tabList.querySelector( - `[data-session-id="${sessionId}"]`, - ); - if (tabEl && session) { - tabEl.classList.toggle("tab-pinned", !!session.pinned); - } - } - - /** - * Makes the tab strip usable when the tab list overflows horizontally. - * @private - */ - _setupScrolling() { - if (!this.tabList) return; - this.tabList.addEventListener( - "wheel", - (event) => { - if (this.tabList.scrollWidth <= this.tabList.clientWidth) return; - const delta = Math.abs(event.deltaX) > Math.abs(event.deltaY) - ? event.deltaX - : event.deltaY; - if (!delta) return; - event.preventDefault(); - this.tabList.scrollLeft += delta; - this._updateScrollState(); - }, - { passive: false }, - ); - this.tabList.addEventListener("scroll", () => this._updateScrollState(), { - passive: true, - }); - window.addEventListener("resize", () => this._updateScrollState()); - } - - /** - * Keeps the active tab visible inside the scrollable tab list. - * @param {string} sessionId - * @private - */ - _scrollTabIntoView(sessionId) { - const tab = this.tabList?.querySelector(`[data-session-id="${sessionId}"]`); - if (!tab) return; - requestAnimationFrame(() => { - tab.scrollIntoView({ - behavior: "smooth", - block: "nearest", - inline: "nearest", - }); - this._updateScrollState(); - }); - } - - /** - * Reflects whether tab overflow exists at either edge. - * @private - */ - _updateScrollState() { - if (!this.tabList || !this.tabBar) return; - const max = this.tabList.scrollWidth - this.tabList.clientWidth; - this.tabBar.classList.toggle("has-tab-overflow", max > 1); - this.tabBar.classList.toggle("has-overflow-left", this.tabList.scrollLeft > 1); - this.tabBar.classList.toggle( - "has-overflow-right", - max > 1 && this.tabList.scrollLeft < max - 1, - ); - } - - /** - * Shows a custom hover hint for tabs with full names and connection start. - * @private - */ - _setupTooltip() { - if (!this.tabList) return; - this.tooltip = createElement("div", "tooltip tab-tooltip", { - role: "tooltip", - }); - document.body.appendChild(this.tooltip); - - this.tabList.addEventListener("pointerover", (event) => { - const tab = event.target.closest(".tab"); - if (!tab || !this.tabList.contains(tab)) return; - if (tab.contains(event.relatedTarget)) return; - this._clearTabTooltipTimer(); - this.tooltipTimer = setTimeout(() => { - this.tooltipTimer = null; - this._showTabTooltip(tab.dataset.sessionId, tab); - }, 250); - }); - - this.tabList.addEventListener("pointerdown", () => this._hideTabTooltip()); - - this.tabList.addEventListener("pointermove", (event) => { - const tab = event.target.closest(".tab"); - if (!tab || !this.tooltip?.classList.contains("is-visible")) return; - this._positionTabTooltip(tab); - }); - - this.tabList.addEventListener("pointerout", (event) => { - const tab = event.target.closest(".tab"); - if (!tab || tab.contains(event.relatedTarget)) return; - this._hideTabTooltip(); - }); - - this.tabList.addEventListener("scroll", () => this._hideTabTooltip(), { - passive: true, - }); - window.addEventListener("blur", () => this._hideTabTooltip()); - document.addEventListener("visibilitychange", () => { - if (document.hidden) this._hideTabTooltip(); - }); - } - - /** - * Renders and positions the tab hover tooltip. - * @param {string} sessionId - * @param {HTMLElement} tab - * @private - */ - _showTabTooltip(sessionId, tab) { - const session = this.app.state.getSession(sessionId); - if (!session || !this.tooltip || !tab?.isConnected || !this.tabList.contains(tab)) { - this._hideTabTooltip(); - return; - } - this.tooltip.replaceChildren(); - this.tooltip.appendChild( - createElement("div", "tab-tooltip-title", { - textContent: this._tabLabel(sessionId), - }), - ); - if ( - session.protocol !== "leviathan" && - session.state === "connected" && - session.connectedAt - ) { - const connected = this._formatConnectedSince(session.connectedAt); - if (connected) { - this.tooltip.appendChild( - createElement("div", "tab-tooltip-meta", { - textContent: `Connected since: ${connected}`, - }), - ); - } - } - this._positionTabTooltip(tab); - this.tooltipSessionId = sessionId; - this.tooltip.classList.add("is-visible"); - } - - /** - * Positions the tab tooltip under the hovered tab. - * @param {HTMLElement} tab - * @private - */ - _positionTabTooltip(tab) { - if (!this.tooltip) return; - if (!tab?.isConnected || !this.tabList.contains(tab)) { - this._hideTabTooltip(); - return; - } - const rect = tab.getBoundingClientRect(); - const tooltipRect = this.tooltip.getBoundingClientRect(); - const left = Math.min( - Math.max(8, rect.left + rect.width / 2 - tooltipRect.width / 2), - window.innerWidth - tooltipRect.width - 8, - ); - const top = Math.min(rect.bottom + 8, window.innerHeight - tooltipRect.height - 8); - this.tooltip.style.left = `${left}px`; - this.tooltip.style.top = `${top}px`; - } - - /** - * Hides the tab hover tooltip. - * @private - */ - _hideTabTooltip() { - this._clearTabTooltipTimer(); - this.tooltipSessionId = null; - if (!this.tooltip) return; - this.tooltip.classList.remove("is-visible"); - this.tooltip.replaceChildren(); - } - - /** - * Cancels any pending tab tooltip show timer. - * @private - */ - _clearTabTooltipTimer() { - if (!this.tooltipTimer) return; - clearTimeout(this.tooltipTimer); - this.tooltipTimer = null; - } - - /** - * Formats a session connection timestamp for the tab tooltip. - * @param {string} value - * @returns {string} - * @private - */ - _formatConnectedSince(value) { - const date = new Date(value); - if (Number.isNaN(date.getTime()) || date.getFullYear() <= 1) return ""; - try { - return date.toLocaleString([], { - dateStyle: "medium", - timeStyle: "short", - }); - } catch (_) { - return date.toLocaleString(); - } - } - - /** - * Shows/hides the empty-state panel based on whether tabs exist. - * @private - */ - _updateEmptyState() { - const empty = document.getElementById("empty-state"); - if (empty) { - empty.style.display = this.order.length === 0 ? "flex" : "none"; - } - } - - /** - * Prompts the user to rename a tab. - * @param {string} sessionId - * @private - */ - _promptRename(sessionId) { - const session = this.app.state.getSession(sessionId); - const current = session?.tabName || ""; - // Use the app's custom prompt modal (window.prompt is unavailable in WKWebView) - this.app._inputPrompt("Rename Tab", "Tab name", current).then((name) => { - if (name !== null && name.trim()) { - this.renameTab(sessionId, name.trim()); - } - }); - } - - /** - * Attempts to reconnect a disconnected session. - * @param {string} sessionId - * @private - */ - async _reconnect(sessionId) { - const session = this.app.state.getSession(sessionId); - if (!session || !session.profileId) return; - - // Fully close the old session before opening a fresh connection. - await this.app.closeSessionImmediate(sessionId); - await this.app.connectProfile(session.profileId); - } - - /** - * Returns the display label for a session's tab. - * @param {string} sessionId - * @returns {string} - * @private - */ - _tabLabel(sessionId) { - const session = this.app.state.getSession(sessionId); - return session?.tabName || "Session"; - } - - // ── Drag and drop ──────────────────────────────────────────────── - - /** - * Sets up drag-and-drop reordering on the tab bar. - * @private - */ - _setupDragAndDrop() { - if (!this.tabList) return; - - let draggedId = null; - - this.tabList.addEventListener("dragstart", (e) => { - const tab = e.target.closest(".tab"); - if (!tab) return; - draggedId = tab.dataset.sessionId; - tab.classList.add("tab-dragging"); - e.dataTransfer.effectAllowed = "move"; - }); - - this.tabList.addEventListener("dragover", (e) => { - e.preventDefault(); - e.dataTransfer.dropEffect = "move"; - - const target = e.target.closest(".tab"); - if (!target || target.dataset.sessionId === draggedId) return; - - const rect = target.getBoundingClientRect(); - const midX = rect.left + rect.width / 2; - target.classList.toggle("tab-drop-before", e.clientX < midX); - target.classList.toggle("tab-drop-after", e.clientX >= midX); - }); - - this.tabList.addEventListener("dragleave", (e) => { - const target = e.target.closest(".tab"); - if (target) { - target.classList.remove("tab-drop-before", "tab-drop-after"); - } - }); - - this.tabList.addEventListener("drop", (e) => { - e.preventDefault(); - const target = e.target.closest(".tab"); - if (!target || !draggedId) return; - - target.classList.remove("tab-drop-before", "tab-drop-after"); - - const targetId = target.dataset.sessionId; - if (targetId === draggedId) return; - - // Reorder - const fromIdx = this.order.indexOf(draggedId); - const toIdx = this.order.indexOf(targetId); - if (fromIdx === -1 || toIdx === -1) return; - - this.order.splice(fromIdx, 1); - this.order.splice(toIdx, 0, draggedId); - this._reorderDOM(); - }); - - this.tabList.addEventListener("dragend", () => { - this.tabList.querySelectorAll(".tab").forEach((t) => { - t.classList.remove( - "tab-dragging", - "tab-drop-before", - "tab-drop-after", - ); - }); - draggedId = null; - }); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.TabManager = TabManager; -})(); diff --git a/frontend/src/js/terminal.js b/frontend/src/js/terminal.js deleted file mode 100644 index 257e35f..0000000 --- a/frontend/src/js/terminal.js +++ /dev/null @@ -1,1613 +0,0 @@ -(function () { - /** - * Alchementary Link — xterm.js terminal manager. - * Creates, manages, and destroys xterm.js instances for SSH and Telnet sessions. - * Handles data routing, resizing, and search. - */ - - /* global Terminal, FitAddon, SearchAddon, WebLinksAddon, Unicode11Addon */ - - const UPLOAD_CHUNK_BYTES = 768 * 1024; - - /** - * Encode a string as UTF-8 bytes then base64-encode them. - * Unlike bare btoa(), this handles characters above U+007F correctly - * (e.g. degree symbol °, emoji) and won't throw InvalidCharacterError. - */ - function utf8ToBase64(str) { - const bytes = new TextEncoder().encode(str); - let binary = ""; - for (let i = 0; i < bytes.length; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); - } - - class TerminalManager { - /** - * @param {App} app - */ - constructor(app) { - this.app = app; - - /** - * Map of sessionId → { term, fitAddon, searchAddon, observer }. - * @type {Map} - */ - this.terminals = new Map(); - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Creates and opens an xterm.js terminal inside the given container. - * Wires up data input → backend and container resizing. - * - * @param {string} sessionId - * @param {string} containerId - DOM id of the panel element. - * @param {Object} profile - The connection profile (used for font/theme prefs). - * @returns {Terminal} The xterm.js Terminal instance. - */ - createTerminal(sessionId, containerId, profile) { - const themeName = - (this.app.state.preferences || {}).terminalTheme || "charcoal"; - const term = new Terminal({ - theme: window.AlchementaryLink.getTerminalTheme(themeName), - fontFamily: - profile.terminalFontFamily || - 'JetBrains Mono, Menlo, Monaco, Consolas, "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", monospace', - fontSize: profile.terminalFontSize || 14, - scrollback: profile.terminalScrollback || 10000, - cursorStyle: profile.terminalCursorStyle || "block", - cursorBlink: true, - allowProposedApi: true, - convertEol: false, - windowsMode: false, - scrollOnUserInput: true, - altClickMovesCursor: true, - }); - - // Load addons - const fitAddon = new FitAddon.FitAddon(); - const searchAddon = new SearchAddon.SearchAddon(); - let webLinksAddon = null; - let unicode11Addon = null; - - term.loadAddon(fitAddon); - term.loadAddon(searchAddon); - - try { - unicode11Addon = new Unicode11Addon.Unicode11Addon(); - term.loadAddon(unicode11Addon); - term.unicode.activeVersion = "11"; - } catch (_) { - // Unicode 11 addon is optional; fall back to default unicode handling. - } - - // WebLinksAddon may not always be available - try { - webLinksAddon = new WebLinksAddon.WebLinksAddon((_event, uri) => { - window.runtime.BrowserOpenURL(uri); - }); - term.loadAddon(webLinksAddon); - } catch (_) { - // Not critical — links just won't be clickable - } - - // Open the terminal in the DOM container - const container = document.getElementById(containerId); - term.open(container); - - let suppressPasteEvent = false; - const attachPasteGuard = () => { - const inputEl = term.textarea || container.querySelector("textarea"); - if (!inputEl) return; - inputEl.addEventListener("keydown", (e) => { - const mod = e.ctrlKey || e.metaKey; - if (!mod || e.key !== "v") return; - // Block the native paste when we handle Cmd/Ctrl+V ourselves. - if (!suppressPasteEvent) return; - e.preventDefault(); - e.stopPropagation(); - }); - inputEl.addEventListener("paste", (e) => { - if (!suppressPasteEvent) return; - e.preventDefault(); - e.stopPropagation(); - suppressPasteEvent = false; - }); - }; - attachPasteGuard(); - - // Right-click context menu on the terminal viewport - const termEl = container.querySelector(".xterm"); - if (termEl) { - termEl.addEventListener("contextmenu", (e) => { - e.preventDefault(); - this._showTerminalContextMenu(sessionId, term, e.clientX, e.clientY); - }); - } - - // Alternate-screen scroll passthrough for screen/tmux/byobu. - // When the terminal is in the alternate buffer (used by multiplexers), - // convert mouse-wheel events into arrow-key sequences so the remote - // application handles scrollback instead of xterm.js (which has no - // scrollback content in alternate mode). - const viewportEl = container.querySelector(".xterm-viewport"); - if (viewportEl) { - viewportEl.addEventListener( - "wheel", - (e) => { - if (term.buffer.active !== term.buffer.alternate) return; - // Only intercept when mouse tracking is NOT active (if mouse mode - // is on, xterm.js already forwards events correctly). - if ( - term.modes && - term.modes.mouseTrackingMode && - term.modes.mouseTrackingMode !== "none" - ) - return; - - e.preventDefault(); - e.stopPropagation(); - const lines = Math.max(1, Math.round(Math.abs(e.deltaY) / 40)); - const seq = e.deltaY < 0 ? "\x1b[A" : "\x1b[B"; - const data = seq.repeat(lines); - const b64 = utf8ToBase64(data); - try { - window.AlchementaryLink.wailsService.WriteSession(sessionId, b64); - } catch (_) { - /* non-critical */ - } - }, - { passive: false }, - ); - } - - // Drag-and-drop file handling - container.addEventListener("dragover", (e) => { - if ( - e.dataTransfer && - Array.from(e.dataTransfer.types || []).includes("Files") - ) { - e.preventDefault(); - e.dataTransfer.dropEffect = "copy"; - } - }); - container.addEventListener("drop", (e) => { - const files = e.dataTransfer ? e.dataTransfer.files : null; - if (!files || files.length === 0) return; - e.preventDefault(); - e.stopPropagation(); - this._handleFileDrop(sessionId, term, files); - }); - - let fitRaf = 0; - let forceNextFit = false; - let lastFitWidth = 0; - let lastFitHeight = 0; - - const cancelScheduledFit = () => { - if (!fitRaf) return; - cancelAnimationFrame(fitRaf); - fitRaf = 0; - }; - - const scheduleFit = (force = false) => { - forceNextFit = forceNextFit || force; - if (fitRaf) return; - - fitRaf = requestAnimationFrame(() => { - fitRaf = 0; - - if (!container.isConnected) { - forceNextFit = false; - return; - } - - const rect = container.getBoundingClientRect(); - const width = Math.round(rect.width); - const height = Math.round(rect.height); - if (width <= 0 || height <= 0) { - return; - } - - const dimensionsChanged = - width !== lastFitWidth || height !== lastFitHeight; - if (!forceNextFit && !dimensionsChanged) { - return; - } - - lastFitWidth = width; - lastFitHeight = height; - forceNextFit = false; - - try { - fitAddon.fit(); - // Forced fits do not emit onResize when xterm's geometry is - // unchanged. Always publish the fitted size so a new local PTY - // cannot remain at its 80x24 fallback dimensions. - this._sendTerminalSize(sessionId, term); - } catch (_) { - /* noop */ - } - }); - }; - - // Force a synchronous layout reflow so the container reports its - // true dimensions, then fit the terminal to fill the space. - // eslint-disable-next-line no-unused-expressions - container.offsetHeight; - scheduleFit(true); - // Belt-and-braces: retry once after a paint in case the initial - // reflow dimensions were still stale. - requestAnimationFrame(() => scheduleFit(true)); - - const refreshTerminal = () => { - try { - term.refresh(0, term.rows - 1); - } catch (_) { - /* noop */ - } - }; - - // Forward user keystrokes to the backend - term.onData((data) => { - const entry = this.terminals.get(sessionId); - if (entry?.localExited) { - if (data === "\r" || data === "\n") { - this._restartLocalTerminal(sessionId, entry); - } - return; - } - - try { - const b64 = utf8ToBase64(data); - window.AlchementaryLink.wailsService.WriteSession(sessionId, b64); - - // Broadcast to other connected sessions if enabled - if (this.app.state.broadcastMode) { - const targets = this.app.state.broadcastTargets; - for (const [otherId] of this.terminals) { - if (otherId === sessionId) continue; - // If targets are set, only broadcast to selected sessions - if (targets && !targets.has(otherId)) continue; - const otherSession = this.app.state.getSession(otherId); - if (otherSession && otherSession.state === "connected") { - try { - window.AlchementaryLink.wailsService.WriteSession(otherId, b64); - } catch (_) { - /* non-critical */ - } - } - } - } - } catch (err) { - console.error("WriteSession error:", err); - } - }); - - // Clipboard: copy selection on Cmd/Ctrl+C, paste on Cmd/Ctrl+V - term.attachCustomKeyEventHandler((e) => { - const mod = e.ctrlKey || e.metaKey; - - // Cmd/Ctrl+C — copy selected text (let xterm handle it normally if no selection) - if (mod && e.key === "c" && e.type === "keydown") { - const selection = term.getSelection(); - if (selection) { - if (window.runtime && window.runtime.ClipboardSetText) { - window.runtime.ClipboardSetText(selection); - } else { - navigator.clipboard.writeText(selection).catch(function () {}); - } - return false; // Prevent xterm from sending Ctrl+C - } - // No selection — let Ctrl+C pass through as SIGINT - return true; - } - - // Cmd/Ctrl+V — paste from clipboard - if (mod && e.key === "v" && e.type === "keydown") { - const prefs = this.app.state.preferences || {}; - if (prefs.useNativePaste === true) { - suppressPasteEvent = false; - return true; - } - suppressPasteEvent = true; - e.preventDefault(); - e.stopPropagation(); - setTimeout(() => { - suppressPasteEvent = false; - }, 250); - if (window.runtime && window.runtime.ClipboardGetText) { - window.runtime - .ClipboardGetText() - .then(function (text) { - if (text) term.paste(text); - }) - .catch(function () {}); - } else { - navigator.clipboard - .readText() - .then(function (text) { - if (text) term.paste(text); - }) - .catch(function () {}); - } - return false; - } - - return true; - }); - - // Notify backend when terminal dimensions change - term.onResize(({ cols, rows }) => { - this._sendTerminalSize(sessionId, { cols, rows }); - }); - - // Re-fit when the container itself resizes. ResizeObserver can fire - // repeatedly while xterm mutates its DOM, so schedule at most one fit - // per frame and skip work when the container dimensions are unchanged. - const observer = new ResizeObserver(() => { - scheduleFit(); - }); - observer.observe(container); - - this.terminals.set(sessionId, { - term, - fitAddon, - searchAddon, - webLinksAddon, - observer, - scheduleFit, - cancelScheduledFit, - refreshTerminal, - localExited: false, - localRestarting: false, - decoder: - typeof TextDecoder !== "undefined" ? new TextDecoder("utf-8") : null, - }); - - return term; - } - - /** - * Writes incoming backend data (base64-encoded) to the appropriate terminal. - * @param {string} sessionId - * @param {string} base64Data - */ - handleData(sessionId, base64Data) { - const entry = this.terminals.get(sessionId); - if (!entry) return; - - try { - const raw = atob(base64Data); - if (entry.decoder) { - const bytes = new Uint8Array(raw.length); - for (let i = 0; i < raw.length; i++) { - bytes[i] = raw.charCodeAt(i); - } - const text = entry.decoder.decode(bytes, { stream: true }); - entry.term.write(text); - } else { - entry.term.write(raw); - } - } catch (err) { - console.error("Failed to decode session data:", err); - } - } - - /** - * Tears down a terminal instance and frees resources. - * @param {string} sessionId - */ - destroyTerminal(sessionId) { - const entry = this.terminals.get(sessionId); - if (!entry) return; - - entry.observer.disconnect(); - entry.cancelScheduledFit(); - entry.term.dispose(); - this.terminals.delete(sessionId); - } - - /** - * Shows the terminal for a given session, hiding all others. - * @param {string} sessionId - */ - showTerminal(sessionId) { - for (const [id, entry] of this.terminals) { - const panel = document.getElementById(`panel-${id}`); - if (panel) { - panel.style.display = id === sessionId ? "flex" : "none"; - } - } - - // Re-fit the newly visible terminal - const entry = this.terminals.get(sessionId); - if (entry) { - entry.scheduleFit(true); - } - } - - /** - * Re-fits the currently active terminal to its container. - */ - fitActive() { - const id = this.app.state.activeSessionId; - if (id) this.fitSession(id); - } - - /** - * Re-fits a specific session's terminal to its container. - * @param {string} sessionId - */ - fitSession(sessionId) { - const entry = this.terminals.get(sessionId); - if (entry) { - entry.scheduleFit(true); - } - } - - /** - * Shows or hides the disconnected overlay for a session panel. - * @param {string} sessionId - * @param {boolean} isDisconnected - * @param {string} [error] - */ - setDisconnectedOverlay(sessionId, isDisconnected, error) { - const panel = document.getElementById(`panel-${sessionId}`); - if (!panel) return; - - const overlay = this._ensureDisconnectOverlay(panel, sessionId); - if (!overlay) return; - - const message = overlay.querySelector(".terminal-disconnect-message"); - if (message) { - message.textContent = error ? error : "Connection lost."; - } - - panel.classList.toggle("is-disconnected", isDisconnected); - overlay.classList.toggle("is-visible", isDisconnected); - } - - /** - * Marks a local shell as exited without obscuring its terminal buffer. - * Enter is intercepted by the terminal input handler and restarts it. - * @param {string} sessionId - * @param {boolean} isExited - */ - setLocalExited(sessionId, isExited) { - const entry = this.terminals.get(sessionId); - if (!entry) return; - - if (isExited && !entry.localExited) { - entry.term.write("\r\n[Exited - press enter to restart]\r\n"); - } - entry.localExited = isExited; - entry.localRestarting = false; - if (isExited) entry.term.focus(); - } - - /** - * Searches within a terminal using the search addon. - * @param {string} sessionId - * @param {string} query - * @param {Object} [options] - * @param {boolean} [options.regex=false] - * @param {boolean} [options.caseSensitive=false] - * @param {boolean} [options.wholeWord=false] - * @returns {boolean} Whether a match was found. - */ - search(sessionId, query, options = {}) { - const entry = this.terminals.get(sessionId); - if (!entry) return false; - - if (!query) { - entry.searchAddon.clearDecorations(); - return false; - } - - return entry.searchAddon.findNext(query, { - regex: options.regex || false, - caseSensitive: options.caseSensitive || false, - wholeWord: options.wholeWord || false, - decorations: { - matchBackground: "#ffa72633", - activeMatchBackground: "#4ecdc466", - matchBorder: "#ffa726", - activeMatchBorder: "#4ecdc4", - }, - }); - } - - /** - * Finds the previous search result. - * @param {string} sessionId - * @param {string} query - * @param {Object} [options] - * @returns {boolean} - */ - searchPrevious(sessionId, query, options = {}) { - const entry = this.terminals.get(sessionId); - if (!entry || !query) return false; - return entry.searchAddon.findPrevious(query, { - regex: options.regex || false, - caseSensitive: options.caseSensitive || false, - wholeWord: options.wholeWord || false, - }); - } - - /** - * Clears the terminal buffer and viewport. - * @param {string} sessionId - */ - clearTerminal(sessionId) { - const entry = this.terminals.get(sessionId); - if (entry) entry.term.clear(); - } - - /** - * Fully resets the terminal (clears buffer, restores default state). - * @param {string} sessionId - */ - resetTerminal(sessionId) { - const entry = this.terminals.get(sessionId); - if (entry) entry.term.reset(); - } - - /** - * Focuses the terminal so it can receive keyboard input. - * @param {string} sessionId - */ - focusTerminal(sessionId) { - const entry = this.terminals.get(sessionId); - if (entry) entry.term.focus(); - } - - /** - * Applies a named colour theme to all active terminal instances. - * @param {string} themeName - */ - applyTheme(themeName) { - const themeObj = window.AlchementaryLink.getTerminalTheme(themeName); - for (const [, entry] of this.terminals) { - entry.term.options.theme = themeObj; - } - } - - // ── Private helpers ────────────────────────────────────────────── - - _sendTerminalSize(sessionId, dimensions) { - const cols = Number(dimensions?.cols || 0); - const rows = Number(dimensions?.rows || 0); - if (cols <= 0 || rows <= 0) return; - - Promise.resolve( - window.AlchementaryLink.wailsService.ResizeSession(sessionId, cols, rows), - ).catch((err) => console.error("ResizeSession error:", err)); - } - - async _restartLocalTerminal(sessionId, entry) { - if (entry.localRestarting) return; - entry.localRestarting = true; - - try { - await window.AlchementaryLink.wailsService.RestartLocalSession( - sessionId, - entry.term.cols, - entry.term.rows, - ); - } catch (err) { - entry.localRestarting = false; - entry.term.write( - `\r\n[Restart failed: ${String(err)} - press enter to retry]\r\n`, - ); - } - } - - /** - * Ensures the disconnect overlay exists for a session panel. - * @param {HTMLElement} panel - * @param {string} sessionId - * @returns {HTMLElement|null} - * @private - */ - _ensureDisconnectOverlay(panel, sessionId) { - let overlay = panel.querySelector(".terminal-disconnect-overlay"); - if (overlay) return overlay; - - const { createElement } = window.AlchementaryLink; - if (!createElement) return null; - - overlay = createElement("div", "terminal-disconnect-overlay"); - const content = createElement("div", "terminal-disconnect-content"); - const title = createElement("div", "terminal-disconnect-title", { - textContent: "DISCONNECTED", - }); - const message = createElement("div", "terminal-disconnect-message", { - textContent: "Connection lost.", - }); - const button = createElement("button", "btn btn-sm btn-primary", { - textContent: "Reconnect", - }); - button.addEventListener("click", () => { - this.app.tabs.reconnectSession(sessionId); - }); - - content.appendChild(title); - content.appendChild(message); - content.appendChild(button); - overlay.appendChild(content); - panel.appendChild(overlay); - - return overlay; - } - - /** - * Shows a right-click context menu for a terminal. - * @param {string} sessionId - * @param {Terminal} term - * @param {number} x - * @param {number} y - * @private - */ - _showTerminalContextMenu(sessionId, term, x, y) { - const { createElement, dismissContextMenus } = window.AlchementaryLink; - dismissContextMenus(); - - const isMac = navigator.platform.indexOf("Mac") !== -1; - const modKey = isMac ? "⌘" : "Ctrl+"; - const hasSelection = term.hasSelection(); - const menu = createElement("div", "context-menu"); - menu.style.left = `${x}px`; - menu.style.top = `${y}px`; - - const items = [ - { - label: "Copy", - shortcut: modKey + "C", - disabled: !hasSelection, - action: () => { - const text = term.getSelection(); - if (text) { - if (window.runtime && window.runtime.ClipboardSetText) { - window.runtime.ClipboardSetText(text); - } else { - navigator.clipboard.writeText(text).catch(function () {}); - } - this.app.notifications.info("Copied to clipboard"); - } - }, - }, - { - label: "Paste", - shortcut: modKey + "V", - action: () => { - if (window.runtime && window.runtime.ClipboardGetText) { - window.runtime - .ClipboardGetText() - .then((text) => { - if (text) term.paste(text); - }) - .catch(function () {}); - } else { - navigator.clipboard - .readText() - .then((text) => { - if (text) term.paste(text); - }) - .catch(function () {}); - } - }, - }, - { divider: true }, - { - label: "Select All", - shortcut: modKey + "A", - action: () => term.selectAll(), - }, - { - label: "Find…", - shortcut: modKey + "F", - action: () => this.app._openTerminalSearch(), - }, - { - label: "Send Snippet…", - action: () => this.app.snippets.open(), - }, - { - label: "Port Forwards…", - action: () => this._showPortForwardPanel(sessionId), - }, - { divider: true }, - { - label: "Split Right", - shortcut: modKey + "⇧D", - action: () => this.app.splitPane("vertical"), - }, - { - label: "Split Down", - shortcut: modKey + "⇧E", - action: () => this.app.splitPane("horizontal"), - }, - { divider: true }, - { - label: "Export Output", - action: () => this._exportTerminalOutput(sessionId, term), - }, - { - label: "Clear Terminal", - shortcut: modKey + "K", - action: () => term.clear(), - }, - { - label: "Reset Terminal", - action: () => term.reset(), - }, - ]; - - for (const entry of items) { - if (entry.divider) { - menu.appendChild(createElement("div", "context-menu-divider")); - continue; - } - const btn = createElement("button", "context-menu-item"); - const labelSpan = createElement("span", "context-menu-label", { - textContent: entry.label, - }); - btn.appendChild(labelSpan); - if (entry.shortcut) { - const shortcutSpan = createElement("span", "context-menu-shortcut", { - textContent: entry.shortcut, - }); - btn.appendChild(shortcutSpan); - } - if (entry.disabled) { - btn.disabled = true; - btn.classList.add("context-menu-disabled"); - } - btn.addEventListener("click", () => { - dismissContextMenus(); - entry.action(); - }); - // Prevent mousedown from causing text deselection in WKWebView - btn.addEventListener("mousedown", (e) => e.preventDefault()); - menu.appendChild(btn); - } - - document.body.appendChild(menu); - - // Keep within viewport - const rect = menu.getBoundingClientRect(); - if (rect.right > window.innerWidth) - menu.style.left = `${window.innerWidth - rect.width - 8}px`; - if (rect.bottom > window.innerHeight) - menu.style.top = `${window.innerHeight - rect.height - 8}px`; - } - - _handleFileDrop(sessionId, term, fileList) { - const session = this.app.state.getSession(sessionId); - if (!session) return; - - const files = Array.from(fileList || []); - if (files.length === 0) return; - - const canUpload = session.protocol === "ssh"; - this._promptFileDropAction(files, canUpload).then((choice) => { - if (!choice) return; - if (choice === "paste") { - this._pasteFileContents(files, term); - return; - } - if (choice === "upload") { - if (!canUpload) { - this.app.notifications.error( - "Upload is only available for SSH sessions.", - ); - return; - } - this._uploadDroppedFiles(sessionId, files); - } - }); - } - - _promptFileDropAction(files, canUpload) { - const { createElement, escapeHtml, formatBytes } = window.AlchementaryLink; - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - const count = files.length; - const first = files[0]; - const sizeLabel = - first && typeof first.size === "number" - ? formatBytes(first.size) - : ""; - const title = - count === 1 ? (first ? first.name : "File") : count + " files"; - - modal.innerHTML = ` - - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const uploadBtn = modal.querySelector(".action-upload"); - if (!canUpload) { - uploadBtn.disabled = true; - uploadBtn.title = "Upload is only available for SSH sessions."; - } - - const close = (value) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(value); - }; - - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(null)); - modal - .querySelector(".prompt-cancel") - .addEventListener("click", () => close(null)); - modal - .querySelector(".action-paste") - .addEventListener("click", () => close("paste")); - uploadBtn.addEventListener("click", () => close("upload")); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(null); - }); - }); - } - - _pasteFileContents(files, term) { - const readFile = (file) => - new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result || ""); - reader.onerror = () => reject(new Error("read failed")); - reader.readAsText(file); - }); - - const run = async () => { - try { - let combined = ""; - for (let i = 0; i < files.length; i += 1) { - const text = await readFile(files[i]); - if (text) { - if (combined) combined += "\n"; - combined += text; - } - } - if (combined) term.paste(combined); - } catch (_) { - this.app.notifications.error("Failed to read file contents."); - } - }; - - run(); - } - - _uploadDroppedFiles(sessionId, files) { - const run = async () => { - const remoteDir = await this._promptUploadDirectory(sessionId); - if (!remoteDir) return; - - if (files.length > 1) { - this.app.notifications.info( - "Uploading " + files.length + " files...", - ); - } - - for (let i = 0; i < files.length; i += 1) { - const file = files[i]; - try { - await this._uploadDroppedFileStream(sessionId, remoteDir, file); - } catch (err) { - const msg = err && err.message ? err.message : String(err); - this.app.notifications.error( - "Upload failed for " + file.name + ": " + msg, - ); - return; - } - } - - if (files.length === 1) { - this.app.notifications.success("Uploaded " + files[0].name); - } else { - this.app.notifications.success("Uploaded " + files.length + " files"); - } - }; - - run().catch((err) => { - const msg = err && err.message ? err.message : String(err); - this.app.notifications.error("Upload failed: " + msg); - }); - } - - async _uploadDroppedFileStream(sessionId, remoteDir, file) { - const totalBytes = typeof file.size === "number" ? file.size : 0; - let uploadId = ""; - try { - uploadId = await window.AlchementaryLink.wailsService.SFTPUploadStart( - sessionId, - remoteDir, - file.name, - totalBytes, - ); - - for (let offset = 0; offset < totalBytes; offset += UPLOAD_CHUNK_BYTES) { - const end = Math.min(offset + UPLOAD_CHUNK_BYTES, totalBytes); - const buffer = await this._readFileSlice(file, offset, end); - const base64Data = this._arrayBufferToBase64(buffer); - await window.AlchementaryLink.wailsService.SFTPUploadChunk(uploadId, base64Data); - } - - await window.AlchementaryLink.wailsService.SFTPUploadFinish(uploadId); - } catch (err) { - if (uploadId) { - try { - await window.AlchementaryLink.wailsService.SFTPUploadCancel(uploadId); - } catch (_) { - /* noop */ - } - } - throw err; - } - } - - _readFileSlice(file, start, end) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(new Error("read failed")); - reader.readAsArrayBuffer(file.slice(start, end)); - }); - } - - _readFileBuffer(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(new Error("read failed")); - reader.readAsArrayBuffer(file); - }); - } - - async _promptUploadDirectory(sessionId) { - return this._openUploadDirectoryPicker(sessionId); - } - - _openUploadDirectoryPicker(sessionId) { - const { createElement, escapeHtml } = window.AlchementaryLink; - return new Promise((resolve) => { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-lg"); - modal.innerHTML = ` - - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const listEl = modal.querySelector(".sftp-picker-list"); - const breadcrumbEl = modal.querySelector('[data-role="breadcrumb"]'); - const pathLabel = modal.querySelector(".sftp-picker-path"); - const upBtn = modal.querySelector(".sftp-picker-up"); - const refreshBtn = modal.querySelector(".sftp-picker-refresh"); - const useBtn = modal.querySelector(".prompt-ok"); - const filterInput = modal.querySelector(".sftp-picker-filter"); - const statusLabel = modal.querySelector(".sftp-picker-status"); - - const state = { - cwd: "", - selected: "", - selectedWritable: true, - cwdWritable: true, - entries: [], - visibleEntries: [], - loading: false, - filter: "", - cursorIndex: -1, - }; - - const close = (value) => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - resolve(value); - }; - - modal - .querySelector(".modal-close") - .addEventListener("click", () => close(null)); - modal - .querySelector(".prompt-cancel") - .addEventListener("click", () => close(null)); - useBtn.addEventListener("click", () => - close(state.selected || state.cwd), - ); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(null); - }); - - const renderBreadcrumb = () => { - breadcrumbEl.innerHTML = ""; - const segments = this._pathSegments(state.cwd); - for (let i = 0; i < segments.length; i += 1) { - if (i > 0) { - breadcrumbEl.appendChild( - createElement("span", "sftp-breadcrumb-sep", { - textContent: ">", - }), - ); - } - const seg = segments[i]; - const btn = createElement("button", "sftp-breadcrumb-segment", { - textContent: seg.label, - title: seg.path, - }); - btn.addEventListener("click", () => loadDir(seg.path)); - breadcrumbEl.appendChild(btn); - } - }; - - const updateStatus = () => { - if (state.selected) { - statusLabel.textContent = state.selectedWritable - ? "" - : "No write permission"; - } else { - statusLabel.textContent = state.cwdWritable - ? "" - : "Current folder is read only"; - } - }; - - const setSelection = (entry, index) => { - listEl.querySelectorAll(".sftp-file-row").forEach((el) => { - el.classList.remove("is-selected"); - }); - const row = listEl.querySelector( - `.sftp-file-row[data-index="${index}"]`, - ); - if (row) row.classList.add("is-selected"); - state.selected = entry.path; - state.selectedWritable = entry.canWrite !== false; - state.cursorIndex = index; - pathLabel.textContent = entry.path; - useBtn.disabled = !state.selectedWritable; - useBtn.title = state.selectedWritable ? "" : "No write permission"; - updateStatus(); - }; - - const renderList = () => { - listEl.innerHTML = ""; - const basePath = state.selected || state.cwd || ""; - const suffix = state.selected - ? "" - : state.cwdWritable - ? "" - : " (read only)"; - pathLabel.textContent = basePath + suffix; - - if (state.cwd && state.cwd !== "/") { - const parentRow = this._buildPickerRow({ - name: "..", - isDir: true, - path: this._parentPath(state.cwd), - }); - parentRow.classList.add("is-parent"); - parentRow.addEventListener("dblclick", () => - loadDir(parentRow.dataset.path), - ); - listEl.appendChild(parentRow); - } - - const filtered = state.filter - ? state.entries.filter((entry) => - entry.name.toLowerCase().includes(state.filter), - ) - : state.entries; - state.visibleEntries = filtered; - - if (!filtered.length) { - const empty = createElement("div", "sftp-state"); - empty.innerHTML = - '
D
No folders found
'; - listEl.appendChild(empty); - state.cursorIndex = -1; - updateStatus(); - return; - } - - filtered.forEach((entry, index) => { - const row = this._buildPickerRow(entry, index); - if (entry.canRead === false) { - row.classList.add("is-unreadable"); - } - if (entry.canWrite === false) { - row.classList.add("is-readonly"); - } - row.addEventListener("click", () => { - if (entry.canRead === false) return; - setSelection(entry, index); - }); - row.addEventListener("dblclick", () => { - if (entry.canRead === false) return; - loadDir(entry.path); - }); - listEl.appendChild(row); - }); - updateStatus(); - }; - - const renderLoading = () => { - listEl.innerHTML = ""; - const s = createElement("div", "sftp-state"); - s.innerHTML = - '
Loading...
'; - listEl.appendChild(s); - }; - - const renderError = (msg) => { - listEl.innerHTML = ""; - const s = createElement("div", "sftp-state"); - s.innerHTML = - '
!
' + - '
' + - escapeHtml(msg) + - "
" + - ''; - const retryBtn = s.querySelector(".sftp-state-retry"); - if (retryBtn) - retryBtn.addEventListener("click", () => loadDir(state.cwd)); - listEl.appendChild(s); - }; - - const loadDir = async (path) => { - if (state.loading) return; - state.loading = true; - renderLoading(); - try { - const targetPath = - path || (await window.AlchementaryLink.wailsService.SFTPPwd(sessionId)); - const files = await window.AlchementaryLink.wailsService.SFTPList( - sessionId, - targetPath, - ); - const stat = await window.AlchementaryLink.wailsService.SFTPStat( - sessionId, - targetPath, - ); - state.cwd = targetPath; - state.selected = ""; - state.selectedWritable = true; - state.cwdWritable = stat && this._modeCanWrite(stat.mode); - state.entries = (files || []) - .filter((e) => e.isDir) - .sort((a, b) => - a.name.localeCompare(b.name, undefined, { - sensitivity: "base", - }), - ); - state.entries = state.entries.map((entry) => { - const canRead = this._modeCanRead(entry.mode); - const canWrite = this._modeCanWrite(entry.mode); - return Object.assign({}, entry, { canRead, canWrite }); - }); - useBtn.disabled = !state.cwdWritable; - useBtn.title = state.cwdWritable ? "" : "No write permission"; - renderBreadcrumb(); - renderList(); - } catch (err) { - renderError(err.message || String(err)); - } finally { - state.loading = false; - } - }; - - upBtn.addEventListener("click", () => { - if (state.cwd) loadDir(this._parentPath(state.cwd)); - }); - refreshBtn.addEventListener("click", () => loadDir(state.cwd)); - - filterInput.addEventListener("input", () => { - state.filter = filterInput.value.trim().toLowerCase(); - renderList(); - }); - - const moveSelection = (delta) => { - if (!state.visibleEntries.length) return; - let idx = state.cursorIndex; - const max = state.visibleEntries.length - 1; - let attempts = 0; - do { - idx = idx + delta; - if (idx < 0) idx = max; - if (idx > max) idx = 0; - const entry = state.visibleEntries[idx]; - if (entry && entry.canRead !== false) { - setSelection(entry, idx); - return; - } - attempts += 1; - } while (attempts <= max); - }; - - modal.addEventListener("keydown", (e) => { - const isInput = e.target === filterInput; - if (isInput && e.key !== "ArrowDown" && e.key !== "ArrowUp") return; - - if (e.key === "ArrowDown") { - e.preventDefault(); - moveSelection(1); - } else if (e.key === "ArrowUp") { - e.preventDefault(); - moveSelection(-1); - } else if (e.key === "Enter") { - if (state.selected && state.selectedWritable) { - e.preventDefault(); - close(state.selected); - } else if (!state.selected && state.cwd && state.cwdWritable) { - e.preventDefault(); - close(state.cwd); - } - } else if (e.key === "Escape") { - e.preventDefault(); - close(null); - } - }); - - const initial = - this.app.sftp && typeof this.app.sftp.getCwd === "function" - ? this.app.sftp.getCwd(sessionId) - : ""; - loadDir(initial || ""); - }); - } - - _buildPickerRow(entry, index) { - const { createElement } = window.AlchementaryLink; - const dataset = { path: entry.path || "" }; - if (typeof index === "number") { - dataset.index = String(index); - } - const row = createElement("div", "sftp-file-row", { dataset }); - const icon = createElement("span", "sftp-file-icon is-dir", { - textContent: "DIR", - }); - const name = createElement("span", "sftp-file-name", { - textContent: entry.name || entry.path || "", - title: entry.name || entry.path || "", - }); - let metaLabel = ""; - if (entry.canRead === false) { - metaLabel = "NO READ"; - } else if (entry.canWrite === false) { - metaLabel = "READ ONLY"; - } - const meta = createElement("span", "sftp-file-meta", { - textContent: metaLabel, - }); - row.appendChild(icon); - row.appendChild(name); - row.appendChild(meta); - return row; - } - - _modeCanRead(mode) { - if (!mode || typeof mode !== "string") return true; - return mode.indexOf("r") !== -1; - } - - _modeCanWrite(mode) { - if (!mode || typeof mode !== "string") return true; - return mode.indexOf("w") !== -1; - } - - _pathSegments(p) { - const parts = (p || "").split("/").filter(Boolean); - const segs = [{ label: "/", path: "/" }]; - let acc = ""; - for (let i = 0; i < parts.length; i += 1) { - acc += "/" + parts[i]; - segs.push({ label: parts[i], path: acc }); - } - return segs; - } - - _parentPath(p) { - if (!p || p === "/") return "/"; - const trimmed = p.replace(/\/+$/, ""); - const idx = trimmed.lastIndexOf("/"); - return idx <= 0 ? "/" : trimmed.substring(0, idx); - } - - _arrayBufferToBase64(buffer) { - if (!buffer) return ""; - const bytes = new Uint8Array(buffer); - let binary = ""; - const chunkSize = 0x8000; - for (let i = 0; i < bytes.length; i += chunkSize) { - const chunk = bytes.subarray(i, i + chunkSize); - binary += String.fromCharCode.apply(null, chunk); - } - return btoa(binary); - } - - /** - * Exports the terminal scrollback buffer as plain text. - * @param {string} sessionId - * @param {Terminal} term - * @private - */ - _exportTerminalOutput(sessionId, term) { - const { createElement } = window.AlchementaryLink; - const buffer = term.buffer.active; - const lines = []; - for (let i = 0; i < buffer.length; i++) { - const line = buffer.getLine(i); - if (line) lines.push(line.translateToString(true)); - } - const text = lines.join("\n").trimEnd(); - - // Use Wails save dialog if available - if (window.runtime && window.runtime.SaveFileDialog) { - // Copy to clipboard as a fallback - } - - // Copy to clipboard - if (window.runtime && window.runtime.ClipboardSetText) { - window.runtime.ClipboardSetText(text); - } else { - navigator.clipboard.writeText(text).catch(function () {}); - } - - // Also offer download via blob - const blob = new Blob([text], { type: "text/plain" }); - const url = URL.createObjectURL(blob); - const a = document.createElement("a"); - a.href = url; - a.download = "terminal-output-" + sessionId.substring(0, 8) + ".txt"; - document.body.appendChild(a); - a.click(); - document.body.removeChild(a); - URL.revokeObjectURL(url); - - this.app.notifications.success( - "Terminal output exported & copied to clipboard", - ); - } - - /** - * Shows the port forwarding management panel for an SSH session. - * @param {string} sessionId - * @private - */ - _showPortForwardPanel(sessionId) { - const { createElement, escapeHtml } = window.AlchementaryLink; - const session = this.app.state.getSession(sessionId); - if (!session || session.protocol !== "ssh") { - this.app.notifications.error( - "Port forwarding is only available for SSH sessions", - ); - return; - } - - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-lg"); - - modal.innerHTML = ` - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const close = () => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - }; - - modal.querySelector(".modal-close").addEventListener("click", close); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) close(); - }); - - const listEl = modal.querySelector(".portfwd-list"); - const self = this; - - const renderForwards = async () => { - try { - const forwards = - (await window.AlchementaryLink.wailsService.ListPortForwards(sessionId)) || []; - listEl.innerHTML = ""; - if (forwards.length === 0) { - listEl.innerHTML = - '
No active port forwards
'; - return; - } - for (const fwd of forwards) { - const row = createElement("div", "", {}); - row.style.cssText = - "display:flex;align-items:center;gap:var(--space-2);padding:var(--space-1) 0;border-bottom:1px solid var(--border-subtle);"; - const dirLabel = fwd.direction === "local" ? "L" : "R"; - const dirColor = - fwd.direction === "local" ? "var(--accent)" : "var(--warning)"; - row.innerHTML = ` - ${dirLabel} - - ${escapeHtml(fwd.bindHost || "127.0.0.1")}:${fwd.bindPort} → ${escapeHtml(fwd.destHost || "localhost")}:${fwd.destPort} - - ${fwd.label ? `${escapeHtml(fwd.label)}` : ""} - `; - const stopBtn = createElement("button", "btn btn-xs btn-danger", { - textContent: "Stop", - }); - stopBtn.addEventListener("click", async () => { - try { - await window.AlchementaryLink.wailsService.StopPortForward(sessionId, fwd.id); - self.app.notifications.info("Forward stopped"); - renderForwards(); - } catch (err) { - self.app.notifications.error( - "Failed to stop: " + (err.message || err), - ); - } - }); - row.appendChild(stopBtn); - listEl.appendChild(row); - } - } catch (err) { - listEl.innerHTML = - '
Failed to load forwards
'; - } - }; - - renderForwards(); - - // Add forward button - modal.querySelector(".pf-add-btn").addEventListener("click", async () => { - const direction = modal.querySelector(".pf-direction").value; - const bindHost = - modal.querySelector(".pf-bind-host").value.trim() || "127.0.0.1"; - const bindPort = parseInt( - modal.querySelector(".pf-bind-port").value, - 10, - ); - const destHost = - modal.querySelector(".pf-dest-host").value.trim() || "localhost"; - const destPort = parseInt( - modal.querySelector(".pf-dest-port").value, - 10, - ); - const label = modal.querySelector(".pf-label").value.trim(); - - if (!bindPort || !destPort) { - self.app.notifications.error( - "Bind port and destination port are required", - ); - return; - } - - try { - await window.AlchementaryLink.wailsService.StartPortForward(sessionId, { - id: "", - direction: direction, - bindHost: bindHost, - bindPort: bindPort, - destHost: destHost, - destPort: destPort, - enabled: true, - label: label, - }); - self.app.notifications.success("Forward started"); - modal.querySelector(".pf-bind-port").value = ""; - modal.querySelector(".pf-dest-port").value = ""; - modal.querySelector(".pf-label").value = ""; - renderForwards(); - } catch (err) { - self.app.notifications.error( - "Failed to start forward: " + (err.message || err), - ); - } - }); - } - - /** - * Builds the xterm.js colour theme. - * @returns {Object} - * @private - */ - _buildTheme() { - const themeName = - (this.app.state.preferences || {}).terminalTheme || "charcoal"; - return window.AlchementaryLink.getTerminalTheme(themeName); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.TerminalManager = TerminalManager; -})(); diff --git a/frontend/src/js/themes.js b/frontend/src/js/themes.js deleted file mode 100644 index eff2d27..0000000 --- a/frontend/src/js/themes.js +++ /dev/null @@ -1,347 +0,0 @@ -(function () { - /** - * Alchementary Link — terminal colour themes. - * Provides a collection of preset xterm.js colour schemes and a manager - * for applying them to terminal instances. - */ - - /** - * Preset terminal colour themes. - * Each entry contains the full xterm.js theme object. - */ - const THEMES = { - charcoal: { - label: "Charcoal (Default)", - theme: { - background: "#1c1c1c", - foreground: "#e4e4e4", - cursor: "#4ecdc4", - cursorAccent: "#1c1c1c", - selectionBackground: "rgba(78, 205, 196, 0.3)", - selectionForeground: "#ffffff", - black: "#1c1c1c", - red: "#ef5350", - green: "#66bb6a", - yellow: "#ffa726", - blue: "#42a5f5", - magenta: "#ab47bc", - cyan: "#4ecdc4", - white: "#e4e4e4", - brightBlack: "#555555", - brightRed: "#ff7043", - brightGreen: "#81c784", - brightYellow: "#ffcc02", - brightBlue: "#64b5f6", - brightMagenta: "#ce93d8", - brightCyan: "#80deea", - brightWhite: "#ffffff", - }, - }, - - "solarized-dark": { - label: "Solarized Dark", - theme: { - background: "#002b36", - foreground: "#839496", - cursor: "#93a1a1", - cursorAccent: "#002b36", - selectionBackground: "rgba(147, 161, 161, 0.3)", - selectionForeground: "#fdf6e3", - black: "#073642", - red: "#dc322f", - green: "#859900", - yellow: "#b58900", - blue: "#268bd2", - magenta: "#d33682", - cyan: "#2aa198", - white: "#eee8d5", - brightBlack: "#586e75", - brightRed: "#cb4b16", - brightGreen: "#586e75", - brightYellow: "#657b83", - brightBlue: "#839496", - brightMagenta: "#6c71c4", - brightCyan: "#93a1a1", - brightWhite: "#fdf6e3", - }, - }, - - monokai: { - label: "Monokai", - theme: { - background: "#272822", - foreground: "#f8f8f2", - cursor: "#f8f8f0", - cursorAccent: "#272822", - selectionBackground: "rgba(73, 72, 62, 0.6)", - selectionForeground: "#f8f8f2", - black: "#272822", - red: "#f92672", - green: "#a6e22e", - yellow: "#f4bf75", - blue: "#66d9ef", - magenta: "#ae81ff", - cyan: "#a1efe4", - white: "#f8f8f2", - brightBlack: "#75715e", - brightRed: "#f92672", - brightGreen: "#a6e22e", - brightYellow: "#f4bf75", - brightBlue: "#66d9ef", - brightMagenta: "#ae81ff", - brightCyan: "#a1efe4", - brightWhite: "#f9f8f5", - }, - }, - - nord: { - label: "Nord", - theme: { - background: "#2e3440", - foreground: "#d8dee9", - cursor: "#d8dee9", - cursorAccent: "#2e3440", - selectionBackground: "rgba(136, 192, 208, 0.3)", - selectionForeground: "#eceff4", - black: "#3b4252", - red: "#bf616a", - green: "#a3be8c", - yellow: "#ebcb8b", - blue: "#81a1c1", - magenta: "#b48ead", - cyan: "#88c0d0", - white: "#e5e9f0", - brightBlack: "#4c566a", - brightRed: "#bf616a", - brightGreen: "#a3be8c", - brightYellow: "#ebcb8b", - brightBlue: "#81a1c1", - brightMagenta: "#b48ead", - brightCyan: "#8fbcbb", - brightWhite: "#eceff4", - }, - }, - - dracula: { - label: "Dracula", - theme: { - background: "#282a36", - foreground: "#f8f8f2", - cursor: "#f8f8f2", - cursorAccent: "#282a36", - selectionBackground: "rgba(68, 71, 90, 0.6)", - selectionForeground: "#f8f8f2", - black: "#21222c", - red: "#ff5555", - green: "#50fa7b", - yellow: "#f1fa8c", - blue: "#bd93f9", - magenta: "#ff79c6", - cyan: "#8be9fd", - white: "#f8f8f2", - brightBlack: "#6272a4", - brightRed: "#ff6e6e", - brightGreen: "#69ff94", - brightYellow: "#ffffa5", - brightBlue: "#d6acff", - brightMagenta: "#ff92df", - brightCyan: "#a4ffff", - brightWhite: "#ffffff", - }, - }, - - "one-dark": { - label: "One Dark", - theme: { - background: "#282c34", - foreground: "#abb2bf", - cursor: "#528bff", - cursorAccent: "#282c34", - selectionBackground: "rgba(82, 139, 255, 0.3)", - selectionForeground: "#abb2bf", - black: "#282c34", - red: "#e06c75", - green: "#98c379", - yellow: "#e5c07b", - blue: "#61afef", - magenta: "#c678dd", - cyan: "#56b6c2", - white: "#abb2bf", - brightBlack: "#5c6370", - brightRed: "#e06c75", - brightGreen: "#98c379", - brightYellow: "#e5c07b", - brightBlue: "#61afef", - brightMagenta: "#c678dd", - brightCyan: "#56b6c2", - brightWhite: "#ffffff", - }, - }, - - "gruvbox-dark": { - label: "Gruvbox Dark", - theme: { - background: "#282828", - foreground: "#ebdbb2", - cursor: "#ebdbb2", - cursorAccent: "#282828", - selectionBackground: "rgba(214, 93, 14, 0.3)", - selectionForeground: "#ebdbb2", - black: "#282828", - red: "#cc241d", - green: "#98971a", - yellow: "#d79921", - blue: "#458588", - magenta: "#b16286", - cyan: "#689d6a", - white: "#a89984", - brightBlack: "#928374", - brightRed: "#fb4934", - brightGreen: "#b8bb26", - brightYellow: "#fabd2f", - brightBlue: "#83a598", - brightMagenta: "#d3869b", - brightCyan: "#8ec07c", - brightWhite: "#ebdbb2", - }, - }, - - "tokyo-night": { - label: "Tokyo Night", - theme: { - background: "#1a1b26", - foreground: "#a9b1d6", - cursor: "#c0caf5", - cursorAccent: "#1a1b26", - selectionBackground: "rgba(41, 46, 66, 0.9)", - selectionForeground: "#c0caf5", - black: "#15161e", - red: "#f7768e", - green: "#9ece6a", - yellow: "#e0af68", - blue: "#7aa2f7", - magenta: "#bb9af7", - cyan: "#7dcfff", - white: "#a9b1d6", - brightBlack: "#414868", - brightRed: "#f7768e", - brightGreen: "#9ece6a", - brightYellow: "#e0af68", - brightBlue: "#7aa2f7", - brightMagenta: "#bb9af7", - brightCyan: "#7dcfff", - brightWhite: "#c0caf5", - }, - }, - - "catppuccin-mocha": { - label: "Catppuccin Mocha", - theme: { - background: "#1e1e2e", - foreground: "#cdd6f4", - cursor: "#f5e0dc", - cursorAccent: "#1e1e2e", - selectionBackground: "rgba(88, 91, 112, 0.5)", - selectionForeground: "#cdd6f4", - black: "#45475a", - red: "#f38ba8", - green: "#a6e3a1", - yellow: "#f9e2af", - blue: "#89b4fa", - magenta: "#f5c2e7", - cyan: "#94e2d5", - white: "#bac2de", - brightBlack: "#585b70", - brightRed: "#f38ba8", - brightGreen: "#a6e3a1", - brightYellow: "#f9e2af", - brightBlue: "#89b4fa", - brightMagenta: "#f5c2e7", - brightCyan: "#94e2d5", - brightWhite: "#a6adc8", - }, - }, - - "rose-pine": { - label: "Rosé Pine", - theme: { - background: "#191724", - foreground: "#e0def4", - cursor: "#524f67", - cursorAccent: "#e0def4", - selectionBackground: "rgba(110, 106, 134, 0.4)", - selectionForeground: "#e0def4", - black: "#26233a", - red: "#eb6f92", - green: "#31748f", - yellow: "#f6c177", - blue: "#9ccfd8", - magenta: "#c4a7e7", - cyan: "#ebbcba", - white: "#e0def4", - brightBlack: "#6e6a86", - brightRed: "#eb6f92", - brightGreen: "#31748f", - brightYellow: "#f6c177", - brightBlue: "#9ccfd8", - brightMagenta: "#c4a7e7", - brightCyan: "#ebbcba", - brightWhite: "#e0def4", - }, - }, - - "solarized-light": { - label: "Solarized Light", - theme: { - background: "#fdf6e3", - foreground: "#657b83", - cursor: "#586e75", - cursorAccent: "#fdf6e3", - selectionBackground: "rgba(7, 54, 66, 0.2)", - selectionForeground: "#073642", - black: "#073642", - red: "#dc322f", - green: "#859900", - yellow: "#b58900", - blue: "#268bd2", - magenta: "#d33682", - cyan: "#2aa198", - white: "#eee8d5", - brightBlack: "#002b36", - brightRed: "#cb4b16", - brightGreen: "#586e75", - brightYellow: "#657b83", - brightBlue: "#839496", - brightMagenta: "#6c71c4", - brightCyan: "#93a1a1", - brightWhite: "#fdf6e3", - }, - }, - }; - - /** - * Returns the theme object for the given theme name. - * Falls back to 'charcoal' if not found. - * @param {string} name - * @returns {Object} xterm.js theme object - */ - function getTerminalTheme(name) { - const entry = THEMES[name || "charcoal"]; - return entry ? entry.theme : THEMES.charcoal.theme; - } - - /** - * Returns all theme names and labels for building a selector. - * @returns {Array<{value: string, label: string}>} - */ - function listTerminalThemes() { - return Object.entries(THEMES).map(([value, entry]) => ({ - value, - label: entry.label, - })); - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.getTerminalTheme = getTerminalTheme; - window.AlchementaryLink.listTerminalThemes = listTerminalThemes; -})(); diff --git a/frontend/src/js/triggers.js b/frontend/src/js/triggers.js deleted file mode 100644 index 46eed8e..0000000 --- a/frontend/src/js/triggers.js +++ /dev/null @@ -1,381 +0,0 @@ -(function () { - /** - * Alchementary Link — Trigger Rules Engine. - * Monitors terminal output and fires actions (highlight, notify, sound) - * when patterns match. Rules are stored in localStorage. - */ - - const { createElement } = window.AlchementaryLink; - - const STORAGE_KEY = "link:trigger-rules"; - - class TriggerEngine { - constructor(app) { - this.app = app; - this._rules = this._load(); - this._backdrop = null; - // Buffer per session to accumulate partial lines - this._buffers = new Map(); - } - - // ── Public API ─────────────────────────────────────────────────── - - /** - * Checks incoming terminal data against all enabled rules. - * Called from the session:data event handler. - * @param {string} sessionId - * @param {string} text - decoded terminal text - */ - check(sessionId, text) { - if (this._rules.length === 0) return; - - // Accumulate text and check line by line - const buf = (this._buffers.get(sessionId) || "") + text; - const lines = buf.split("\n"); - // Keep the last incomplete line in the buffer - this._buffers.set(sessionId, lines.pop()); - - for (const line of lines) { - this._checkLine(sessionId, line); - } - } - - /** - * Opens the trigger rules management panel. - */ - open() { - if (this._backdrop) return; - this._render(); - } - - /** - * Closes the trigger rules panel. - */ - close() { - if (this._backdrop) { - this._backdrop.classList.remove("is-visible"); - const modal = this._backdrop.querySelector(".modal"); - if (modal) modal.classList.remove("is-visible"); - setTimeout(() => { - if (this._backdrop) { - this._backdrop.remove(); - this._backdrop = null; - } - }, 200); - } - } - - /** - * Cleans up session buffer on disconnect. - * @param {string} sessionId - */ - cleanup(sessionId) { - this._buffers.delete(sessionId); - } - - // ── Rule matching ──────────────────────────────────────────────── - - _checkLine(sessionId, line) { - // Strip ANSI escape sequences for matching - const clean = line.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ""); - - for (const rule of this._rules) { - if (!rule.enabled) continue; - - let matched = false; - if (rule.isRegex) { - try { - const re = new RegExp(rule.pattern, rule.caseSensitive ? "" : "i"); - matched = re.test(clean); - } catch (_) { - // Invalid regex — skip - } - } else { - const haystack = rule.caseSensitive ? clean : clean.toLowerCase(); - const needle = rule.caseSensitive - ? rule.pattern - : rule.pattern.toLowerCase(); - matched = haystack.includes(needle); - } - - if (!matched) continue; - - // Fire actions - if (rule.action === "notify" || rule.action === "both") { - const session = this.app.state.getSession(sessionId); - const label = session - ? session.tabName || session.profileName - : sessionId; - this.app.notifications.info( - `[${label}] Trigger: ${rule.name || rule.pattern}`, - ); - } - - if (rule.action === "highlight" || rule.action === "both") { - // Flash the tab briefly - const tabEl = document.querySelector( - `.tab[data-session-id="${sessionId}"]`, - ); - if (tabEl) { - tabEl.classList.add("tab-trigger-flash"); - setTimeout(() => tabEl.classList.remove("tab-trigger-flash"), 2000); - } - } - - if (rule.action === "sound") { - this._playBeep(); - } - } - } - - _playBeep() { - try { - const ctx = new (window.AudioContext || window.webkitAudioContext)(); - const osc = ctx.createOscillator(); - const gain = ctx.createGain(); - osc.connect(gain); - gain.connect(ctx.destination); - osc.frequency.value = 880; - gain.gain.value = 0.1; - osc.start(); - osc.stop(ctx.currentTime + 0.15); - } catch (_) { - // Audio not available — silent - } - } - - // ── Storage ────────────────────────────────────────────────────── - - _load() { - try { - const raw = localStorage.getItem(STORAGE_KEY); - return raw ? JSON.parse(raw) : []; - } catch (_) { - return []; - } - } - - _save() { - try { - localStorage.setItem(STORAGE_KEY, JSON.stringify(this._rules)); - } catch (_) { - // localStorage full or unavailable - } - } - - // ── UI ─────────────────────────────────────────────────────────── - - _render() { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-lg"); - - modal.innerHTML = ` - - - `; - - backdrop.appendChild(modal); - document.body.appendChild(backdrop); - this._backdrop = backdrop; - - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - modal - .querySelector(".modal-close") - .addEventListener("click", () => this.close()); - backdrop.addEventListener("click", (e) => { - if (e.target === backdrop) this.close(); - }); - modal - .querySelector(".trigger-add-btn") - .addEventListener("click", () => this._addRule()); - - this._renderList(); - } - - _renderList() { - const list = this._backdrop.querySelector(".trigger-list"); - if (!list) return; - list.innerHTML = ""; - - if (this._rules.length === 0) { - list.innerHTML = - '
No trigger rules defined
'; - return; - } - - for (let i = 0; i < this._rules.length; i++) { - list.appendChild(this._renderRuleRow(this._rules[i], i)); - } - } - - _renderRuleRow(rule, index) { - const row = createElement("div", "trigger-row"); - - const toggle = createElement("input", "trigger-toggle"); - toggle.type = "checkbox"; - toggle.checked = rule.enabled; - toggle.addEventListener("change", () => { - this._rules[index].enabled = toggle.checked; - this._save(); - }); - - const info = createElement("div", "trigger-info"); - const name = createElement("div", "trigger-name", { - textContent: rule.name || rule.pattern, - }); - const meta = createElement("div", "trigger-meta", { - textContent: `${rule.isRegex ? "Regex" : "Text"} · ${rule.action} · ${rule.caseSensitive ? "Case-sensitive" : "Case-insensitive"}`, - }); - info.appendChild(name); - info.appendChild(meta); - - const pattern = createElement("code", "trigger-pattern", { - textContent: rule.pattern, - }); - - const actions = createElement("div", "trigger-actions"); - const editBtn = createElement("button", "btn btn-xs", { - textContent: "Edit", - }); - editBtn.addEventListener("mousedown", (e) => e.preventDefault()); - editBtn.addEventListener("click", () => this._editRule(index)); - - const delBtn = createElement("button", "btn btn-xs btn-danger", { - textContent: "✕", - }); - delBtn.addEventListener("mousedown", (e) => e.preventDefault()); - delBtn.addEventListener("click", () => { - this._rules.splice(index, 1); - this._save(); - this._renderList(); - }); - - actions.appendChild(editBtn); - actions.appendChild(delBtn); - - row.appendChild(toggle); - row.appendChild(info); - row.appendChild(pattern); - row.appendChild(actions); - return row; - } - - _addRule() { - this._showRuleForm(null, (rule) => { - this._rules.push(rule); - this._save(); - this._renderList(); - }); - } - - _editRule(index) { - this._showRuleForm(this._rules[index], (rule) => { - this._rules[index] = rule; - this._save(); - this._renderList(); - }); - } - - _showRuleForm(existing, onSave) { - const list = this._backdrop.querySelector(".trigger-list"); - const toolbar = this._backdrop.querySelector(".trigger-toolbar"); - if (!list) return; - - // Hide list and toolbar - list.style.display = "none"; - if (toolbar) toolbar.style.display = "none"; - - const form = createElement("div", "trigger-form"); - form.innerHTML = ` -
- - -
-
- - -
-
-
- - -
-
- - -
-
-
- -
-
- - -
- `; - - list.parentNode.insertBefore(form, list); - - form - .querySelector(".trigger-cancel-btn") - .addEventListener("click", () => { - form.remove(); - list.style.display = ""; - if (toolbar) toolbar.style.display = ""; - }); - - form.querySelector(".trigger-save-btn").addEventListener("click", () => { - const pattern = form - .querySelector(".trigger-pattern-input") - .value.trim(); - if (!pattern) { - this.app.notifications.error("Pattern is required"); - return; - } - const rule = { - name: form.querySelector(".trigger-name-input").value.trim(), - pattern: pattern, - isRegex: form.querySelector(".trigger-type-select").value === "regex", - action: form.querySelector(".trigger-action-select").value, - caseSensitive: form.querySelector(".trigger-case-check").checked, - enabled: existing ? existing.enabled : true, - }; - form.remove(); - list.style.display = ""; - if (toolbar) toolbar.style.display = ""; - onSave(rule); - }); - - form.querySelector(".trigger-pattern-input").focus(); - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - window.AlchementaryLink.TriggerEngine = TriggerEngine; -})(); diff --git a/frontend/src/js/utils.js b/frontend/src/js/utils.js deleted file mode 100644 index 338c26e..0000000 --- a/frontend/src/js/utils.js +++ /dev/null @@ -1,402 +0,0 @@ -(function () { - /** - * Alchementary Link — utility functions. - * General-purpose helpers used throughout the application. - */ - - /** - * Shorthand for document.querySelector. - * @param {string} selector - CSS selector. - * @returns {Element|null} - */ - function $(selector) { - return document.querySelector(selector); - } - - /** - * Shorthand for document.querySelectorAll. - * @param {string} selector - CSS selector. - * @returns {NodeListOf} - */ - function $$(selector) { - return document.querySelectorAll(selector); - } - - /** - * Creates a DOM element with optional class name and attributes. - * @param {string} tag - HTML tag name. - * @param {string} [className] - Space-separated class names. - * @param {Object} [attrs] - Attribute key/value pairs. - * @returns {HTMLElement} - */ - function createElement(tag, className, attrs = {}) { - const el = document.createElement(tag); - if (className) { - el.className = className; - } - for (const [key, value] of Object.entries(attrs)) { - if (key === "textContent") { - el.textContent = value; - } else if (key === "innerHTML") { - el.innerHTML = value; - } else if (key === "dataset") { - for (const [dKey, dVal] of Object.entries(value)) { - el.dataset[dKey] = dVal; - } - } else if (key.startsWith("on") && typeof value === "function") { - el.addEventListener(key.slice(2).toLowerCase(), value); - } else { - el.setAttribute(key, value); - } - } - return el; - } - - /** - * Creates a standard modal skeleton without HTML string interpolation. - * @param {string} title - * @param {string} [sizeClass] - * @returns {{backdrop: HTMLElement, modal: HTMLElement, body: HTMLElement, footer: HTMLElement, closeButton: HTMLElement, show: Function, remove: Function}} - */ - function createModalShell(title, sizeClass = "modal-sm") { - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", `modal ${sizeClass}`); - const header = createElement("div", "modal-header"); - const titleEl = createElement("h3", "modal-title", { textContent: title }); - const closeButton = createElement("button", "btn btn-icon btn-xs modal-close", { - textContent: "x", - "aria-label": "Close", - }); - const body = createElement("div", "modal-body"); - const footer = createElement("div", "modal-footer"); - - header.appendChild(titleEl); - header.appendChild(closeButton); - modal.appendChild(header); - modal.appendChild(body); - modal.appendChild(footer); - backdrop.appendChild(modal); - - return { - backdrop, - modal, - body, - footer, - closeButton, - show() { - document.body.appendChild(backdrop); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - }, - remove() { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - setTimeout(() => backdrop.remove(), 200); - }, - }; - } - - /** - * Escapes HTML special characters to prevent XSS. - * @param {string} str - Raw string. - * @returns {string} Escaped string safe for innerHTML. - */ - function escapeHtml(str) { - if (!str) return ""; - const map = { - "&": "&", - "<": "<", - ">": ">", - '"': """, - "'": "'", - }; - return str.replace(/[&<>"']/g, (ch) => map[ch]); - } - - /** - * Formats a Date (or ISO string) into a concise timestamp. - * @param {Date|string} date - * @returns {string} e.g. "14:32:07" or "2 Jan 14:32". - */ - function formatTimestamp(date) { - if (!date) return ""; - const d = date instanceof Date ? date : new Date(date); - if (isNaN(d.getTime())) return ""; - - const now = new Date(); - const isToday = d.toDateString() === now.toDateString(); - const hours = String(d.getHours()).padStart(2, "0"); - const mins = String(d.getMinutes()).padStart(2, "0"); - const secs = String(d.getSeconds()).padStart(2, "0"); - - if (isToday) { - return `${hours}:${mins}:${secs}`; - } - const months = [ - "Jan", - "Feb", - "Mar", - "Apr", - "May", - "Jun", - "Jul", - "Aug", - "Sep", - "Oct", - "Nov", - "Dec", - ]; - return `${d.getDate()} ${months[d.getMonth()]} ${hours}:${mins}`; - } - - /** - * Returns a debounced version of the given function. - * @param {Function} fn - * @param {number} ms - Delay in milliseconds. - * @returns {Function} - */ - function debounce(fn, ms) { - let timer = null; - return function (...args) { - clearTimeout(timer); - timer = setTimeout(() => fn.apply(this, args), ms); - }; - } - - /** - * Performs a simple fuzzy match — checks whether every character in - * the query appears in order within the text (case-insensitive). - * @param {string} text - * @param {string} query - * @returns {{ match: boolean, score: number }} - */ - function fuzzyMatch(text, query) { - if (!query) return { match: true, score: 1 }; - const lowerText = text.toLowerCase(); - const lowerQuery = query.toLowerCase(); - - let textIdx = 0; - let score = 0; - let consecutiveBonus = 0; - - for (let qi = 0; qi < lowerQuery.length; qi++) { - const ch = lowerQuery[qi]; - const foundAt = lowerText.indexOf(ch, textIdx); - if (foundAt === -1) { - return { match: false, score: 0 }; - } - // Reward consecutive character matches and early matches - if (foundAt === textIdx) { - consecutiveBonus += 1; - } else { - consecutiveBonus = 0; - } - score += 1 + consecutiveBonus + (foundAt === 0 ? 2 : 0); - textIdx = foundAt + 1; - } - - return { match: true, score }; - } - - /** - * Base64-encodes a string. - * @param {string} str - * @returns {string} - */ - function base64Encode(str) { - return btoa(str); - } - - /** - * UTF-8-encodes a string and then base64-encodes the bytes. - * Unlike bare btoa(), this handles characters above U+007F correctly. - * @param {string} str - * @returns {string} - */ - function utf8ToBase64(str) { - const bytes = new TextEncoder().encode(str); - let binary = ""; - for (let i = 0; i < bytes.length; i++) { - binary += String.fromCharCode(bytes[i]); - } - return btoa(binary); - } - - /** - * Base64-decodes a string. - * @param {string} str - * @returns {string} - */ - function base64Decode(str) { - return atob(str); - } - - /** - * Generates a new UUID v4 using the browser crypto API. - * @returns {string} - */ - function generateId() { - return crypto.randomUUID(); - } - - /** - * Formats a byte count into a human-friendly string. - * @param {number} bytes - * @returns {string} e.g. "1.23 KB" - */ - function formatBytes(bytes) { - if (bytes === 0) return "0 B"; - const units = ["B", "KB", "MB", "GB", "TB"]; - const k = 1024; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - const value = bytes / Math.pow(k, i); - return `${value.toFixed(i === 0 ? 0 : 2)} ${units[i]}`; - } - - /** - * Truncates a string to the given length, appending an ellipsis if needed. - * @param {string} str - * @param {number} len - * @returns {string} - */ - function truncate(str, len) { - if (!str || str.length <= len) return str || ""; - return str.slice(0, len) + "…"; - } - - /** - * Removes any open context menus from the DOM. - */ - function dismissContextMenus() { - document.querySelectorAll(".context-menu").forEach((m) => m.remove()); - } - - /** - * Returns the protocol badge colour class. - * @param {string} protocol - 'ssh', 'telnet', 'raw', or 'github'. - * @returns {string} - */ - function protocolColour(protocol) { - switch (protocol) { - case "ssh": - return "proto-ssh"; - case "telnet": - return "proto-telnet"; - case "raw": - return "proto-raw"; - case "github": - return "proto-github"; - case "vnc": - return "proto-vnc"; - case "irc": - return "proto-irc"; - case "local": - return "proto-local"; - default: - return ""; - } - } - - /** - * Returns a human-friendly relative time string for an ISO date. - * @param {string} isoDate - ISO 8601 date string. - * @returns {string} e.g. "3 hours ago", "2 days ago". - */ - function relativeTime(isoDate) { - if (!isoDate) return ""; - const d = new Date(isoDate); - if (isNaN(d.getTime())) return ""; - - const now = Date.now(); - const diff = now - d.getTime(); - const secs = Math.floor(diff / 1000); - if (secs < 60) return "just now"; - const mins = Math.floor(secs / 60); - if (mins < 60) return mins === 1 ? "1 minute ago" : `${mins} minutes ago`; - const hrs = Math.floor(mins / 60); - if (hrs < 24) return hrs === 1 ? "1 hour ago" : `${hrs} hours ago`; - const days = Math.floor(hrs / 24); - if (days < 30) return days === 1 ? "1 day ago" : `${days} days ago`; - const months = Math.floor(days / 30); - if (months < 12) - return months === 1 ? "1 month ago" : `${months} months ago`; - const years = Math.floor(months / 12); - return years === 1 ? "1 year ago" : `${years} years ago`; - } - - /** - * Returns a human-friendly label for a session state. - * @param {string} state - * @returns {string} - */ - function stateLabel(state) { - switch (state) { - case "connecting": - return "Connecting"; - case "connected": - return "Connected"; - case "disconnected": - return "Disconnected"; - case "reconnecting": - return "Reconnecting"; - case "error": - return "Error"; - default: - return state || "Unknown"; - } - } - - window.AlchementaryLink = window.AlchementaryLink || {}; - Object.assign(window.AlchementaryLink, { - $, - $$, - createElement, - createModalShell, - escapeHtml, - formatTimestamp, - debounce, - fuzzyMatch, - base64Encode, - utf8ToBase64, - base64Decode, - generateId, - formatBytes, - truncate, - dismissContextMenus, - protocolColour, - stateLabel, - relativeTime, - }); - - /* ── Disable macOS/WebKit auto-capitalisation on all text inputs ───────── */ - function disableAutoCapitalise(el) { - el.setAttribute("autocapitalize", "none"); - el.setAttribute("autocorrect", "off"); - el.setAttribute("spellcheck", "false"); - } - - // Apply to existing inputs - document.querySelectorAll("input, textarea").forEach(disableAutoCapitalise); - - // Observe for dynamically added inputs - var inputObserver = new MutationObserver(function (mutations) { - for (var i = 0; i < mutations.length; i++) { - var nodes = mutations[i].addedNodes; - for (var j = 0; j < nodes.length; j++) { - var node = nodes[j]; - if (node.nodeType !== 1) continue; - if (node.tagName === "INPUT" || node.tagName === "TEXTAREA") { - disableAutoCapitalise(node); - } - if (node.querySelectorAll) { - node - .querySelectorAll("input, textarea") - .forEach(disableAutoCapitalise); - } - } - } - }); - inputObserver.observe(document.body, { childList: true, subtree: true }); -})(); diff --git a/frontend/src/js/vnc.js b/frontend/src/js/vnc.js deleted file mode 100644 index 418f38f..0000000 --- a/frontend/src/js/vnc.js +++ /dev/null @@ -1,331 +0,0 @@ -(function () { - /** - * Alchementary Link — VNC viewer. - * Renders a noVNC canvas inside a session panel, connecting to the local - * WebSocket proxy started by the Go backend. - */ - - const { createElement, $ } = window.AlchementaryLink; - - class VNCViewer { - constructor() { - /** @type {Map} sessionID → viewer */ - this._viewers = new Map(); - } - - /** - * Creates a VNC viewer panel for a session inside the given container. - * @param {string} sessionID - * @param {HTMLElement} container - The .session-panel element. - * @param {object} profile - The profile object with VNC config. - */ - createViewer(sessionID, container, profile) { - if (this._viewers.has(sessionID)) return; - if (!container) return; - - const viewer = new VNCViewerInstance(sessionID, container, profile); - this._viewers.set(sessionID, viewer); - } - - /** - * Connects the viewer to the WebSocket URL from the backend. - * @param {string} sessionID - * @param {string} wsURL - */ - connect(sessionID, wsURL) { - const viewer = this._viewers.get(sessionID); - if (viewer) { - viewer.connect(wsURL); - } - } - - /** - * Destroys a viewer for the given session. - * @param {string} sessionID - */ - destroyViewer(sessionID) { - const viewer = this._viewers.get(sessionID); - if (viewer) { - viewer.destroy(); - this._viewers.delete(sessionID); - } - } - - /** - * Resizes the VNC display for the given session. - * @param {string} sessionID - */ - resize(sessionID) { - const viewer = this._viewers.get(sessionID); - if (viewer) { - viewer.resize(); - } - } - } - - class VNCViewerInstance { - /** - * @param {string} sessionID - * @param {HTMLElement} container - * @param {object} profile - */ - constructor(sessionID, container, profile) { - this.sessionID = sessionID; - this.container = container; - this.profile = profile; - this.rfb = null; - this._rfbListeners = []; - this._credentialDialog = null; - this._destroyed = false; - - this._buildUI(); - } - - _buildUI() { - this.container.innerHTML = ""; - this.container.classList.add("vnc-viewer-panel"); - - // Toolbar - this.toolbar = createElement("div", "vnc-toolbar"); - - const ctrlAltDel = createElement("button", "btn btn-sm btn-vnc-action", { - textContent: "Ctrl+Alt+Del", - title: "Send Ctrl+Alt+Del", - }); - ctrlAltDel.addEventListener("click", () => this._sendCtrlAltDel()); - - const clipboardBtn = createElement( - "button", - "btn btn-sm btn-vnc-action", - { - textContent: "Clipboard", - title: "Sync clipboard", - }, - ); - clipboardBtn.addEventListener("click", () => this._syncClipboard()); - - const fullscreenBtn = createElement( - "button", - "btn btn-sm btn-vnc-action", - { - textContent: "Fullscreen", - title: "Toggle fullscreen", - }, - ); - fullscreenBtn.addEventListener("click", () => this._toggleFullscreen()); - - this.statusEl = createElement("span", "vnc-status", { - textContent: "Connecting…", - }); - - this.toolbar.appendChild(this.statusEl); - this.toolbar.appendChild(ctrlAltDel); - this.toolbar.appendChild(clipboardBtn); - this.toolbar.appendChild(fullscreenBtn); - - // VNC display container - this.displayEl = createElement("div", "vnc-display"); - - this.container.appendChild(this.toolbar); - this.container.appendChild(this.displayEl); - } - - /** - * Connects to the VNC WebSocket proxy. - * @param {string} wsURL - WebSocket URL (ws://127.0.0.1:) - */ - connect(wsURL) { - if (this._destroyed) return; - if (!window.noVNC || !window.noVNC.RFB) { - this.statusEl.textContent = "Error: noVNC not loaded"; - this.statusEl.classList.add("vnc-status-error"); - return; - } - - try { - const vncConfig = this.profile.vnc || {}; - - this.rfb = new window.noVNC.RFB(this.displayEl, wsURL, { - shared: vncConfig.sharedSession !== false, - credentials: {}, - }); - - this.rfb.viewOnly = !!vncConfig.viewOnly; - this.rfb.scaleViewport = vncConfig.scaleToFit !== false; - this.rfb.resizeSession = false; - this.rfb.clipViewport = false; - this.rfb.qualityLevel = vncConfig.quality || 6; - this.rfb.compressionLevel = 2; - - // Event handlers - this._addRFBListener("connect", () => { - this.statusEl.textContent = "Connected"; - this.statusEl.classList.remove("vnc-status-error"); - this.statusEl.classList.add("vnc-status-connected"); - }); - - this._addRFBListener("disconnect", (ev) => { - const clean = ev.detail.clean; - this.statusEl.textContent = clean - ? "Disconnected" - : "Connection lost"; - this.statusEl.classList.remove("vnc-status-connected"); - if (!clean) { - this.statusEl.classList.add("vnc-status-error"); - } - }); - - this._addRFBListener("credentialsrequired", () => { - this.statusEl.textContent = "Credentials required"; - // TODO: prompt for VNC password via modal - this._promptCredentials(); - }); - - this._addRFBListener("desktopname", (ev) => { - this.statusEl.textContent = "Connected — " + ev.detail.name; - }); - } catch (err) { - this.statusEl.textContent = "Error: " + err.message; - this.statusEl.classList.add("vnc-status-error"); - } - } - - _sendCtrlAltDel() { - if (this.rfb) { - this.rfb.sendCtrlAltDel(); - } - } - - _addRFBListener(name, handler) { - this.rfb.addEventListener(name, handler); - this._rfbListeners.push({ name, handler }); - } - - async _syncClipboard() { - if (!this.rfb) return; - try { - const text = await navigator.clipboard.readText(); - if (text) { - this.rfb.clipboardPasteFrom(text); - } - } catch (_) { - // Clipboard API may not be available in WKWebView - } - } - - _toggleFullscreen() { - if (!document.fullscreenElement) { - this.container.requestFullscreen().catch(() => {}); - } else { - document.exitFullscreen().catch(() => {}); - } - } - - _promptCredentials() { - if (this._destroyed) return; - this._closeCredentialDialog(); - - // Build a simple modal for VNC password - const backdrop = createElement("div", "modal-backdrop"); - const modal = createElement("div", "modal modal-sm"); - modal.innerHTML = ` - - - - `; - - document.body.appendChild(backdrop); - document.body.appendChild(modal); - requestAnimationFrame(() => { - backdrop.classList.add("is-visible"); - modal.classList.add("is-visible"); - }); - - const input = modal.querySelector("#vnc-password-input"); - const submit = modal.querySelector("#vnc-password-submit"); - const cancel = modal.querySelector("#vnc-password-cancel"); - const dialog = { - backdrop, - modal, - removeTimer: null, - focusTimer: null, - }; - this._credentialDialog = dialog; - - const close = () => { - backdrop.classList.remove("is-visible"); - modal.classList.remove("is-visible"); - dialog.removeTimer = setTimeout(() => { - if (this._credentialDialog === dialog) { - this._closeCredentialDialog(); - } - }, 200); - }; - - submit.addEventListener("click", () => { - const pw = input.value; - close(); - if (this.rfb && pw) { - this.rfb.sendCredentials({ password: pw }); - } - }); - - cancel.addEventListener("click", () => { - close(); - }); - - input.addEventListener("keydown", (e) => { - if (e.key === "Enter") submit.click(); - if (e.key === "Escape") cancel.click(); - }); - - dialog.focusTimer = setTimeout(() => input.focus(), 50); - } - - _closeCredentialDialog() { - const dialog = this._credentialDialog; - if (!dialog) return; - if (dialog.removeTimer) clearTimeout(dialog.removeTimer); - if (dialog.focusTimer) clearTimeout(dialog.focusTimer); - dialog.backdrop.remove(); - dialog.modal.remove(); - this._credentialDialog = null; - } - - resize() { - // noVNC handles resizing internally via ResizeObserver on its canvas, - // but we can trigger a re-scale if needed. - if (this.rfb && this.rfb.scaleViewport) { - this.rfb.scaleViewport = true; - } - } - - destroy() { - this._destroyed = true; - this._closeCredentialDialog(); - if (this.rfb) { - for (const { name, handler } of this._rfbListeners) { - this.rfb.removeEventListener(name, handler); - } - this._rfbListeners = []; - this.rfb.disconnect(); - this.rfb = null; - } - this.container.innerHTML = ""; - this.container.classList.remove("vnc-viewer-panel"); - } - } - - // Export to global namespace. - window.AlchementaryLink.VNCViewer = new VNCViewer(); -})(); diff --git a/frontend/src/js/wails-service.js b/frontend/src/js/wails-service.js deleted file mode 100644 index f5bddb6..0000000 --- a/frontend/src/js/wails-service.js +++ /dev/null @@ -1,28 +0,0 @@ -(function () { - const root = (window.AlchementaryLink = window.AlchementaryLink || {}); - - const directService = () => window.go?.api?.Service || null; - const unavailable = (name) => - Promise.reject(new Error(`Wails service method is not available: ${String(name)}`)); - - root.wailsService = new Proxy( - {}, - { - get(_target, prop) { - if (prop === "then") return undefined; - const service = directService(); - const value = service?.[prop]; - if (typeof value === "function") return value.bind(service); - if (value !== undefined) return value; - return () => unavailable(prop); - }, - has(_target, prop) { - return prop in (directService() || {}); - }, - }, - ); - - root.wailsServiceReady = Promise.resolve(root.wailsService); - root.hasWailsService = () => - Boolean(root.wailsService && window.runtime && directService()); -})(); diff --git a/frontend/wailsjs/go/api/Service.d.ts b/frontend/wailsjs/go/api/Service.d.ts deleted file mode 100755 index 3f4904b..0000000 --- a/frontend/wailsjs/go/api/Service.d.ts +++ /dev/null @@ -1,261 +0,0 @@ -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT -import {models} from '../models'; -import {api} from '../models'; -import {context} from '../models'; - -export function BrowseForShell():Promise; - -export function CancelReconnect(arg1:string):Promise; - -export function CheckForUpdates():Promise>; - -export function CleanupRecordings(arg1:number,arg2:number):Promise>; - -export function ClearSessionHistory(arg1:string):Promise; - -export function CloseAllSessions():Promise; - -export function ConnectLocal():Promise; - -export function ConnectQuick(arg1:models.Profile):Promise; - -export function ConnectSession(arg1:string):Promise; - -export function CreateProfile(arg1:models.Profile):Promise; - -export function CreateSnippet(arg1:models.Snippet):Promise; - -export function DeleteKnownHost(arg1:string):Promise; - -export function DeleteProfile(arg1:string):Promise; - -export function DeleteProfileSecrets(arg1:string):Promise; - -export function DeleteRecording(arg1:string):Promise; - -export function DeleteSSHKey(arg1:string):Promise; - -export function DeleteSnippet(arg1:string):Promise; - -export function DeleteTOTPSecret(arg1:string):Promise; - -export function DisconnectSession(arg1:string):Promise; - -export function DownloadGitHubRepoArchive(arg1:string,arg2:string,arg3:string):Promise; - -export function DownloadGitHubRepoArchiveToPath(arg1:string,arg2:string,arg3:string,arg4:string):Promise; - -export function DuplicateProfile(arg1:string):Promise; - -export function ExportProfiles(arg1:boolean):Promise; - -export function ExportProfilesEncrypted(arg1:Array,arg2:string):Promise; - -export function FileExists(arg1:string):Promise; - -export function FileIsPrivateKey(arg1:string):Promise; - -export function GenerateSSHKey(arg1:string,arg2:string,arg3:number,arg4:string):Promise; - -export function GetAppInfo():Promise>; - -export function GetDefaultShell():Promise; - -export function GetPreferences():Promise; - -export function GetProfile(arg1:string):Promise; - -export function GetRecording(arg1:string):Promise>; - -export function GetRecordingStorageInfo():Promise>; - -export function GetSSHPublicKey(arg1:string):Promise; - -export function GetSessionCwd(arg1:string):Promise; - -export function GetSessionHistory(arg1:string):Promise>>; - -export function GetTOTPCode(arg1:string):Promise; - -export function GetVNCWebSocketURL(arg1:string):Promise; - -export function HasTOTPSecret(arg1:string):Promise; - -export function IRCHandleInput(arg1:string,arg2:string,arg3:string):Promise; - -export function IRCJoin(arg1:string,arg2:string):Promise; - -export function IRCListRooms(arg1:string):Promise; - -export function IRCPart(arg1:string,arg2:string):Promise; - -export function IRCRooms(arg1:string):Promise>>; - -export function IRCUsers(arg1:string,arg2:string):Promise>; - -export function ImportProfiles(arg1:string,arg2:string):Promise; - -export function ImportProfilesEncrypted(arg1:string,arg2:string):Promise; - -export function ImportSSHConfig(arg1:string,arg2:string):Promise; - -export function InitialiseSecretStore(arg1:string):Promise; - -export function IsRecording(arg1:string):Promise; - -export function IsSecretStoreInitialised():Promise; - -export function IsSecretStoreUnlocked():Promise; - -export function LeviathanExportJSON(arg1:string,arg2:Record):Promise; - -export function LeviathanExportRange(arg1:string,arg2:number,arg3:number):Promise; - -export function LeviathanLoadFile(arg1:string,arg2:boolean):Promise>; - -export function LeviathanOpenCompare():Promise>; - -export function LeviathanOpenFile():Promise>; - -export function LeviathanRedo():Promise; - -export function LeviathanRevert():Promise; - -export function LeviathanRunTool(arg1:string,arg2:Record):Promise>; - -export function LeviathanSaveWorking():Promise; - -export function LeviathanSaveWorkingToPath(arg1:string):Promise; - -export function LeviathanSetByte(arg1:number,arg2:string):Promise>; - -export function LeviathanState():Promise; - -export function LeviathanUndo():Promise; - -export function ListAuditEntries(arg1:number):Promise>; - -export function ListFavourites():Promise>; - -export function ListKnownHosts():Promise>; - -export function ListPortForwards(arg1:string):Promise>; - -export function ListProfiles():Promise>; - -export function ListProfilesByFolder(arg1:string):Promise>; - -export function ListProfilesByProtocol(arg1:string):Promise>; - -export function ListProfilesByTag(arg1:string):Promise>; - -export function ListRecordings():Promise>>; - -export function ListSSHKeyPairs():Promise>; - -export function ListSSHKeys():Promise>; - -export function ListSessions():Promise>; - -export function ListSnippets():Promise>; - -export function LockSecretStore():Promise; - -export function NewProfile(arg1:string):Promise; - -export function PinSession(arg1:string,arg2:boolean):Promise; - -export function PingSession(arg1:string):Promise; - -export function ReconnectSession(arg1:string):Promise; - -export function RefreshGitHubRepos(arg1:string):Promise>; - -export function RegisterSecretResponseListener():Promise; - -export function RenameSession(arg1:string,arg2:string):Promise; - -export function ResizeSession(arg1:string,arg2:number,arg3:number):Promise; - -export function RespondSecret(arg1:string,arg2:string,arg3:boolean):Promise; - -export function RestartLocalSession(arg1:string,arg2:number,arg3:number):Promise; - -export function SFTPDownload(arg1:string,arg2:string):Promise; - -export function SFTPDownloadToPath(arg1:string,arg2:string,arg3:string):Promise; - -export function SFTPList(arg1:string,arg2:string):Promise>>; - -export function SFTPMkdir(arg1:string,arg2:string):Promise; - -export function SFTPPwd(arg1:string):Promise; - -export function SFTPRemove(arg1:string,arg2:string):Promise; - -export function SFTPRename(arg1:string,arg2:string,arg3:string):Promise; - -export function SFTPStat(arg1:string,arg2:string):Promise>; - -export function SFTPUpload(arg1:string,arg2:string):Promise; - -export function SFTPUploadCancel(arg1:string):Promise; - -export function SFTPUploadChunk(arg1:string,arg2:string):Promise; - -export function SFTPUploadData(arg1:string,arg2:string,arg3:string,arg4:string):Promise; - -export function SFTPUploadFinish(arg1:string):Promise; - -export function SFTPUploadPath(arg1:string,arg2:string,arg3:string):Promise; - -export function SFTPUploadStart(arg1:string,arg2:string,arg3:string,arg4:number):Promise; - -export function SaveFile(arg1:string,arg2:string):Promise; - -export function SavePreferences(arg1:models.AppPreferences):Promise; - -export function SaveSessionCapture(arg1:string,arg2:string):Promise; - -export function SearchProfiles(arg1:string):Promise>; - -export function SelectFile(arg1:string,arg2:string):Promise; - -export function SendBreak(arg1:string):Promise; - -export function SetContext(arg1:context.Context):Promise; - -export function SetEventSink(arg1:any):Promise; - -export function SetFavourite(arg1:string,arg2:boolean):Promise; - -export function SetProfileSecret(arg1:string,arg2:string,arg3:string):Promise; - -export function SetTOTPSecret(arg1:string,arg2:string):Promise; - -export function Shutdown():Promise; - -export function StartPortForward(arg1:string,arg2:models.PortForward):Promise; - -export function StartRecording(arg1:string,arg2:number,arg3:number):Promise; - -export function StopPortForward(arg1:string,arg2:string):Promise; - -export function StopRecording(arg1:string):Promise; - -export function TestConnection(arg1:string,arg2:number):Promise>; - -export function UnlockSecretStore(arg1:string):Promise; - -export function UpdateProfile(arg1:models.Profile):Promise; - -export function UpdateSnippet(arg1:models.Snippet):Promise; - -export function WriteSession(arg1:string,arg2:string):Promise; - -export function WriteSessionEscaped(arg1:string,arg2:string):Promise; - -export function WriteSessionHex(arg1:string,arg2:string):Promise; - -export function WriteSessionText(arg1:string,arg2:string):Promise; diff --git a/frontend/wailsjs/go/api/Service.js b/frontend/wailsjs/go/api/Service.js deleted file mode 100755 index fb48e57..0000000 --- a/frontend/wailsjs/go/api/Service.js +++ /dev/null @@ -1,515 +0,0 @@ -// @ts-check -// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL -// This file is automatically generated. DO NOT EDIT - -export function BrowseForShell() { - return window['go']['api']['Service']['BrowseForShell'](); -} - -export function CancelReconnect(arg1) { - return window['go']['api']['Service']['CancelReconnect'](arg1); -} - -export function CheckForUpdates() { - return window['go']['api']['Service']['CheckForUpdates'](); -} - -export function CleanupRecordings(arg1, arg2) { - return window['go']['api']['Service']['CleanupRecordings'](arg1, arg2); -} - -export function ClearSessionHistory(arg1) { - return window['go']['api']['Service']['ClearSessionHistory'](arg1); -} - -export function CloseAllSessions() { - return window['go']['api']['Service']['CloseAllSessions'](); -} - -export function ConnectLocal() { - return window['go']['api']['Service']['ConnectLocal'](); -} - -export function ConnectQuick(arg1) { - return window['go']['api']['Service']['ConnectQuick'](arg1); -} - -export function ConnectSession(arg1) { - return window['go']['api']['Service']['ConnectSession'](arg1); -} - -export function CreateProfile(arg1) { - return window['go']['api']['Service']['CreateProfile'](arg1); -} - -export function CreateSnippet(arg1) { - return window['go']['api']['Service']['CreateSnippet'](arg1); -} - -export function DeleteKnownHost(arg1) { - return window['go']['api']['Service']['DeleteKnownHost'](arg1); -} - -export function DeleteProfile(arg1) { - return window['go']['api']['Service']['DeleteProfile'](arg1); -} - -export function DeleteProfileSecrets(arg1) { - return window['go']['api']['Service']['DeleteProfileSecrets'](arg1); -} - -export function DeleteRecording(arg1) { - return window['go']['api']['Service']['DeleteRecording'](arg1); -} - -export function DeleteSSHKey(arg1) { - return window['go']['api']['Service']['DeleteSSHKey'](arg1); -} - -export function DeleteSnippet(arg1) { - return window['go']['api']['Service']['DeleteSnippet'](arg1); -} - -export function DeleteTOTPSecret(arg1) { - return window['go']['api']['Service']['DeleteTOTPSecret'](arg1); -} - -export function DisconnectSession(arg1) { - return window['go']['api']['Service']['DisconnectSession'](arg1); -} - -export function DownloadGitHubRepoArchive(arg1, arg2, arg3) { - return window['go']['api']['Service']['DownloadGitHubRepoArchive'](arg1, arg2, arg3); -} - -export function DownloadGitHubRepoArchiveToPath(arg1, arg2, arg3, arg4) { - return window['go']['api']['Service']['DownloadGitHubRepoArchiveToPath'](arg1, arg2, arg3, arg4); -} - -export function DuplicateProfile(arg1) { - return window['go']['api']['Service']['DuplicateProfile'](arg1); -} - -export function ExportProfiles(arg1) { - return window['go']['api']['Service']['ExportProfiles'](arg1); -} - -export function ExportProfilesEncrypted(arg1, arg2) { - return window['go']['api']['Service']['ExportProfilesEncrypted'](arg1, arg2); -} - -export function FileExists(arg1) { - return window['go']['api']['Service']['FileExists'](arg1); -} - -export function FileIsPrivateKey(arg1) { - return window['go']['api']['Service']['FileIsPrivateKey'](arg1); -} - -export function GenerateSSHKey(arg1, arg2, arg3, arg4) { - return window['go']['api']['Service']['GenerateSSHKey'](arg1, arg2, arg3, arg4); -} - -export function GetAppInfo() { - return window['go']['api']['Service']['GetAppInfo'](); -} - -export function GetDefaultShell() { - return window['go']['api']['Service']['GetDefaultShell'](); -} - -export function GetPreferences() { - return window['go']['api']['Service']['GetPreferences'](); -} - -export function GetProfile(arg1) { - return window['go']['api']['Service']['GetProfile'](arg1); -} - -export function GetRecording(arg1) { - return window['go']['api']['Service']['GetRecording'](arg1); -} - -export function GetRecordingStorageInfo() { - return window['go']['api']['Service']['GetRecordingStorageInfo'](); -} - -export function GetSSHPublicKey(arg1) { - return window['go']['api']['Service']['GetSSHPublicKey'](arg1); -} - -export function GetSessionCwd(arg1) { - return window['go']['api']['Service']['GetSessionCwd'](arg1); -} - -export function GetSessionHistory(arg1) { - return window['go']['api']['Service']['GetSessionHistory'](arg1); -} - -export function GetTOTPCode(arg1) { - return window['go']['api']['Service']['GetTOTPCode'](arg1); -} - -export function GetVNCWebSocketURL(arg1) { - return window['go']['api']['Service']['GetVNCWebSocketURL'](arg1); -} - -export function HasTOTPSecret(arg1) { - return window['go']['api']['Service']['HasTOTPSecret'](arg1); -} - -export function IRCHandleInput(arg1, arg2, arg3) { - return window['go']['api']['Service']['IRCHandleInput'](arg1, arg2, arg3); -} - -export function IRCJoin(arg1, arg2) { - return window['go']['api']['Service']['IRCJoin'](arg1, arg2); -} - -export function IRCListRooms(arg1) { - return window['go']['api']['Service']['IRCListRooms'](arg1); -} - -export function IRCPart(arg1, arg2) { - return window['go']['api']['Service']['IRCPart'](arg1, arg2); -} - -export function IRCRooms(arg1) { - return window['go']['api']['Service']['IRCRooms'](arg1); -} - -export function IRCUsers(arg1, arg2) { - return window['go']['api']['Service']['IRCUsers'](arg1, arg2); -} - -export function ImportProfiles(arg1, arg2) { - return window['go']['api']['Service']['ImportProfiles'](arg1, arg2); -} - -export function ImportProfilesEncrypted(arg1, arg2) { - return window['go']['api']['Service']['ImportProfilesEncrypted'](arg1, arg2); -} - -export function ImportSSHConfig(arg1, arg2) { - return window['go']['api']['Service']['ImportSSHConfig'](arg1, arg2); -} - -export function InitialiseSecretStore(arg1) { - return window['go']['api']['Service']['InitialiseSecretStore'](arg1); -} - -export function IsRecording(arg1) { - return window['go']['api']['Service']['IsRecording'](arg1); -} - -export function IsSecretStoreInitialised() { - return window['go']['api']['Service']['IsSecretStoreInitialised'](); -} - -export function IsSecretStoreUnlocked() { - return window['go']['api']['Service']['IsSecretStoreUnlocked'](); -} - -export function LeviathanExportJSON(arg1, arg2) { - return window['go']['api']['Service']['LeviathanExportJSON'](arg1, arg2); -} - -export function LeviathanExportRange(arg1, arg2, arg3) { - return window['go']['api']['Service']['LeviathanExportRange'](arg1, arg2, arg3); -} - -export function LeviathanLoadFile(arg1, arg2) { - return window['go']['api']['Service']['LeviathanLoadFile'](arg1, arg2); -} - -export function LeviathanOpenCompare() { - return window['go']['api']['Service']['LeviathanOpenCompare'](); -} - -export function LeviathanOpenFile() { - return window['go']['api']['Service']['LeviathanOpenFile'](); -} - -export function LeviathanRedo() { - return window['go']['api']['Service']['LeviathanRedo'](); -} - -export function LeviathanRevert() { - return window['go']['api']['Service']['LeviathanRevert'](); -} - -export function LeviathanRunTool(arg1, arg2) { - return window['go']['api']['Service']['LeviathanRunTool'](arg1, arg2); -} - -export function LeviathanSaveWorking() { - return window['go']['api']['Service']['LeviathanSaveWorking'](); -} - -export function LeviathanSaveWorkingToPath(arg1) { - return window['go']['api']['Service']['LeviathanSaveWorkingToPath'](arg1); -} - -export function LeviathanSetByte(arg1, arg2) { - return window['go']['api']['Service']['LeviathanSetByte'](arg1, arg2); -} - -export function LeviathanState() { - return window['go']['api']['Service']['LeviathanState'](); -} - -export function LeviathanUndo() { - return window['go']['api']['Service']['LeviathanUndo'](); -} - -export function ListAuditEntries(arg1) { - return window['go']['api']['Service']['ListAuditEntries'](arg1); -} - -export function ListFavourites() { - return window['go']['api']['Service']['ListFavourites'](); -} - -export function ListKnownHosts() { - return window['go']['api']['Service']['ListKnownHosts'](); -} - -export function ListPortForwards(arg1) { - return window['go']['api']['Service']['ListPortForwards'](arg1); -} - -export function ListProfiles() { - return window['go']['api']['Service']['ListProfiles'](); -} - -export function ListProfilesByFolder(arg1) { - return window['go']['api']['Service']['ListProfilesByFolder'](arg1); -} - -export function ListProfilesByProtocol(arg1) { - return window['go']['api']['Service']['ListProfilesByProtocol'](arg1); -} - -export function ListProfilesByTag(arg1) { - return window['go']['api']['Service']['ListProfilesByTag'](arg1); -} - -export function ListRecordings() { - return window['go']['api']['Service']['ListRecordings'](); -} - -export function ListSSHKeyPairs() { - return window['go']['api']['Service']['ListSSHKeyPairs'](); -} - -export function ListSSHKeys() { - return window['go']['api']['Service']['ListSSHKeys'](); -} - -export function ListSessions() { - return window['go']['api']['Service']['ListSessions'](); -} - -export function ListSnippets() { - return window['go']['api']['Service']['ListSnippets'](); -} - -export function LockSecretStore() { - return window['go']['api']['Service']['LockSecretStore'](); -} - -export function NewProfile(arg1) { - return window['go']['api']['Service']['NewProfile'](arg1); -} - -export function PinSession(arg1, arg2) { - return window['go']['api']['Service']['PinSession'](arg1, arg2); -} - -export function PingSession(arg1) { - return window['go']['api']['Service']['PingSession'](arg1); -} - -export function ReconnectSession(arg1) { - return window['go']['api']['Service']['ReconnectSession'](arg1); -} - -export function RefreshGitHubRepos(arg1) { - return window['go']['api']['Service']['RefreshGitHubRepos'](arg1); -} - -export function RegisterSecretResponseListener() { - return window['go']['api']['Service']['RegisterSecretResponseListener'](); -} - -export function RenameSession(arg1, arg2) { - return window['go']['api']['Service']['RenameSession'](arg1, arg2); -} - -export function ResizeSession(arg1, arg2, arg3) { - return window['go']['api']['Service']['ResizeSession'](arg1, arg2, arg3); -} - -export function RespondSecret(arg1, arg2, arg3) { - return window['go']['api']['Service']['RespondSecret'](arg1, arg2, arg3); -} - -export function RestartLocalSession(arg1, arg2, arg3) { - return window['go']['api']['Service']['RestartLocalSession'](arg1, arg2, arg3); -} - -export function SFTPDownload(arg1, arg2) { - return window['go']['api']['Service']['SFTPDownload'](arg1, arg2); -} - -export function SFTPDownloadToPath(arg1, arg2, arg3) { - return window['go']['api']['Service']['SFTPDownloadToPath'](arg1, arg2, arg3); -} - -export function SFTPList(arg1, arg2) { - return window['go']['api']['Service']['SFTPList'](arg1, arg2); -} - -export function SFTPMkdir(arg1, arg2) { - return window['go']['api']['Service']['SFTPMkdir'](arg1, arg2); -} - -export function SFTPPwd(arg1) { - return window['go']['api']['Service']['SFTPPwd'](arg1); -} - -export function SFTPRemove(arg1, arg2) { - return window['go']['api']['Service']['SFTPRemove'](arg1, arg2); -} - -export function SFTPRename(arg1, arg2, arg3) { - return window['go']['api']['Service']['SFTPRename'](arg1, arg2, arg3); -} - -export function SFTPStat(arg1, arg2) { - return window['go']['api']['Service']['SFTPStat'](arg1, arg2); -} - -export function SFTPUpload(arg1, arg2) { - return window['go']['api']['Service']['SFTPUpload'](arg1, arg2); -} - -export function SFTPUploadCancel(arg1) { - return window['go']['api']['Service']['SFTPUploadCancel'](arg1); -} - -export function SFTPUploadChunk(arg1, arg2) { - return window['go']['api']['Service']['SFTPUploadChunk'](arg1, arg2); -} - -export function SFTPUploadData(arg1, arg2, arg3, arg4) { - return window['go']['api']['Service']['SFTPUploadData'](arg1, arg2, arg3, arg4); -} - -export function SFTPUploadFinish(arg1) { - return window['go']['api']['Service']['SFTPUploadFinish'](arg1); -} - -export function SFTPUploadPath(arg1, arg2, arg3) { - return window['go']['api']['Service']['SFTPUploadPath'](arg1, arg2, arg3); -} - -export function SFTPUploadStart(arg1, arg2, arg3, arg4) { - return window['go']['api']['Service']['SFTPUploadStart'](arg1, arg2, arg3, arg4); -} - -export function SaveFile(arg1, arg2) { - return window['go']['api']['Service']['SaveFile'](arg1, arg2); -} - -export function SavePreferences(arg1) { - return window['go']['api']['Service']['SavePreferences'](arg1); -} - -export function SaveSessionCapture(arg1, arg2) { - return window['go']['api']['Service']['SaveSessionCapture'](arg1, arg2); -} - -export function SearchProfiles(arg1) { - return window['go']['api']['Service']['SearchProfiles'](arg1); -} - -export function SelectFile(arg1, arg2) { - return window['go']['api']['Service']['SelectFile'](arg1, arg2); -} - -export function SendBreak(arg1) { - return window['go']['api']['Service']['SendBreak'](arg1); -} - -export function SetContext(arg1) { - return window['go']['api']['Service']['SetContext'](arg1); -} - -export function SetEventSink(arg1) { - return window['go']['api']['Service']['SetEventSink'](arg1); -} - -export function SetFavourite(arg1, arg2) { - return window['go']['api']['Service']['SetFavourite'](arg1, arg2); -} - -export function SetProfileSecret(arg1, arg2, arg3) { - return window['go']['api']['Service']['SetProfileSecret'](arg1, arg2, arg3); -} - -export function SetTOTPSecret(arg1, arg2) { - return window['go']['api']['Service']['SetTOTPSecret'](arg1, arg2); -} - -export function Shutdown() { - return window['go']['api']['Service']['Shutdown'](); -} - -export function StartPortForward(arg1, arg2) { - return window['go']['api']['Service']['StartPortForward'](arg1, arg2); -} - -export function StartRecording(arg1, arg2, arg3) { - return window['go']['api']['Service']['StartRecording'](arg1, arg2, arg3); -} - -export function StopPortForward(arg1, arg2) { - return window['go']['api']['Service']['StopPortForward'](arg1, arg2); -} - -export function StopRecording(arg1) { - return window['go']['api']['Service']['StopRecording'](arg1); -} - -export function TestConnection(arg1, arg2) { - return window['go']['api']['Service']['TestConnection'](arg1, arg2); -} - -export function UnlockSecretStore(arg1) { - return window['go']['api']['Service']['UnlockSecretStore'](arg1); -} - -export function UpdateProfile(arg1) { - return window['go']['api']['Service']['UpdateProfile'](arg1); -} - -export function UpdateSnippet(arg1) { - return window['go']['api']['Service']['UpdateSnippet'](arg1); -} - -export function WriteSession(arg1, arg2) { - return window['go']['api']['Service']['WriteSession'](arg1, arg2); -} - -export function WriteSessionEscaped(arg1, arg2) { - return window['go']['api']['Service']['WriteSessionEscaped'](arg1, arg2); -} - -export function WriteSessionHex(arg1, arg2) { - return window['go']['api']['Service']['WriteSessionHex'](arg1, arg2); -} - -export function WriteSessionText(arg1, arg2) { - return window['go']['api']['Service']['WriteSessionText'](arg1, arg2); -} diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts deleted file mode 100755 index b66a789..0000000 --- a/frontend/wailsjs/go/models.ts +++ /dev/null @@ -1,691 +0,0 @@ -export namespace api { - - export class SSHKeyInfo { - name: string; - type: string; - bits: string; - publicKey: string; - privateKeyPath: string; - - static createFrom(source: any = {}) { - return new SSHKeyInfo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.type = source["type"]; - this.bits = source["bits"]; - this.publicKey = source["publicKey"]; - this.privateKeyPath = source["privateKeyPath"]; - } - } - export class leviathanDocState { - name: string; - size: number; - dirty: boolean; - undoLen: number; - redoLen: number; - compare?: leviathanDocState; - previewHex: any[]; - - static createFrom(source: any = {}) { - return new leviathanDocState(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.name = source["name"]; - this.size = source["size"]; - this.dirty = source["dirty"]; - this.undoLen = source["undoLen"]; - this.redoLen = source["redoLen"]; - this.compare = this.convertValues(source["compare"], leviathanDocState); - this.previewHex = source["previewHex"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - -} - -export namespace models { - - export class AppPreferences { - theme: string; - terminalTheme: string; - defaultProtocol: string; - confirmOnClose: boolean; - confirmOnDisconnect: boolean; - showStatusBar: boolean; - sidebarWidth: number; - defaultFontFamily: string; - defaultFontSize: number; - defaultScrollback: number; - defaultCursorStyle: string; - useNativePaste: boolean; - localShell: string; - leviathanEnabled: boolean; - - static createFrom(source: any = {}) { - return new AppPreferences(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.theme = source["theme"]; - this.terminalTheme = source["terminalTheme"]; - this.defaultProtocol = source["defaultProtocol"]; - this.confirmOnClose = source["confirmOnClose"]; - this.confirmOnDisconnect = source["confirmOnDisconnect"]; - this.showStatusBar = source["showStatusBar"]; - this.sidebarWidth = source["sidebarWidth"]; - this.defaultFontFamily = source["defaultFontFamily"]; - this.defaultFontSize = source["defaultFontSize"]; - this.defaultScrollback = source["defaultScrollback"]; - this.defaultCursorStyle = source["defaultCursorStyle"]; - this.useNativePaste = source["useNativePaste"]; - this.localShell = source["localShell"]; - this.leviathanEnabled = source["leviathanEnabled"]; - } - } - export class AuditEntry { - id: string; - timestamp: string; - action: string; - profileId?: string; - sessionId?: string; - details: string; - - static createFrom(source: any = {}) { - return new AuditEntry(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.timestamp = source["timestamp"]; - this.action = source["action"]; - this.profileId = source["profileId"]; - this.sessionId = source["sessionId"]; - this.details = source["details"]; - } - } - export class GitHubConfig { - baseURL: string; - secretMode: string; - refreshIntervalSecs: number; - - static createFrom(source: any = {}) { - return new GitHubConfig(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.baseURL = source["baseURL"]; - this.secretMode = source["secretMode"]; - this.refreshIntervalSecs = source["refreshIntervalSecs"]; - } - } - export class GitHubRepo { - fullName: string; - name: string; - owner: string; - description: string; - htmlURL: string; - defaultBranch: string; - latestCommitter: string; - latestCommitAt: string; - private: boolean; - fork: boolean; - archived: boolean; - language: string; - stars: number; - pushedAt: string; - updatedAt: string; - - static createFrom(source: any = {}) { - return new GitHubRepo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.fullName = source["fullName"]; - this.name = source["name"]; - this.owner = source["owner"]; - this.description = source["description"]; - this.htmlURL = source["htmlURL"]; - this.defaultBranch = source["defaultBranch"]; - this.latestCommitter = source["latestCommitter"]; - this.latestCommitAt = source["latestCommitAt"]; - this.private = source["private"]; - this.fork = source["fork"]; - this.archived = source["archived"]; - this.language = source["language"]; - this.stars = source["stars"]; - this.pushedAt = source["pushedAt"]; - this.updatedAt = source["updatedAt"]; - } - } - export class IRCConfig { - nick: string; - realName: string; - useTLS: boolean; - serverPasswordRequired: boolean; - serverPasswordMode: string; - nickServIdentify: boolean; - nickServAccount: string; - nickServMode: string; - autoJoinRooms: string[]; - sshTunnel: boolean; - sshHost: string; - sshPort: number; - sshUsername: string; - sshAuthMethod: string; - sshSecretMode: string; - sshPrivateKeyPath: string; - sshAgentAuth: boolean; - autoReconnect: boolean; - maxReconnectTries: number; - - static createFrom(source: any = {}) { - return new IRCConfig(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.nick = source["nick"]; - this.realName = source["realName"]; - this.useTLS = source["useTLS"]; - this.serverPasswordRequired = source["serverPasswordRequired"]; - this.serverPasswordMode = source["serverPasswordMode"]; - this.nickServIdentify = source["nickServIdentify"]; - this.nickServAccount = source["nickServAccount"]; - this.nickServMode = source["nickServMode"]; - this.autoJoinRooms = source["autoJoinRooms"]; - this.sshTunnel = source["sshTunnel"]; - this.sshHost = source["sshHost"]; - this.sshPort = source["sshPort"]; - this.sshUsername = source["sshUsername"]; - this.sshAuthMethod = source["sshAuthMethod"]; - this.sshSecretMode = source["sshSecretMode"]; - this.sshPrivateKeyPath = source["sshPrivateKeyPath"]; - this.sshAgentAuth = source["sshAgentAuth"]; - this.autoReconnect = source["autoReconnect"]; - this.maxReconnectTries = source["maxReconnectTries"]; - } - } - export class JumpHost { - host: string; - port: number; - username: string; - authMethod: string; - secretMode: string; - privateKeyPath: string; - - static createFrom(source: any = {}) { - return new JumpHost(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.host = source["host"]; - this.port = source["port"]; - this.username = source["username"]; - this.authMethod = source["authMethod"]; - this.secretMode = source["secretMode"]; - this.privateKeyPath = source["privateKeyPath"]; - } - } - export class KnownHost { - id: string; - host: string; - port: number; - keyType: string; - fingerprint: string; - publicKey: string; - firstSeen: string; - lastSeen: string; - trusted: boolean; - - static createFrom(source: any = {}) { - return new KnownHost(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.host = source["host"]; - this.port = source["port"]; - this.keyType = source["keyType"]; - this.fingerprint = source["fingerprint"]; - this.publicKey = source["publicKey"]; - this.firstSeen = source["firstSeen"]; - this.lastSeen = source["lastSeen"]; - this.trusted = source["trusted"]; - } - } - export class LoginScriptStep { - type: string; - value: string; - timeout: number; - - static createFrom(source: any = {}) { - return new LoginScriptStep(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.type = source["type"]; - this.value = source["value"]; - this.timeout = source["timeout"]; - } - } - export class PayloadPreset { - id: string; - name: string; - payload: string; - format: string; - - static createFrom(source: any = {}) { - return new PayloadPreset(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.name = source["name"]; - this.payload = source["payload"]; - this.format = source["format"]; - } - } - export class PortForward { - id: string; - direction: string; - bindHost: string; - bindPort: number; - destHost: string; - destPort: number; - enabled: boolean; - label: string; - - static createFrom(source: any = {}) { - return new PortForward(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.direction = source["direction"]; - this.bindHost = source["bindHost"]; - this.bindPort = source["bindPort"]; - this.destHost = source["destHost"]; - this.destPort = source["destPort"]; - this.enabled = source["enabled"]; - this.label = source["label"]; - } - } - export class VNCConfig { - vncHost: string; - vncPort: number; - secretMode: string; - viewOnly: boolean; - scaleToFit: boolean; - quality: number; - sharedSession: boolean; - sshTunnel: boolean; - sshHost: string; - sshPort: number; - sshUsername: string; - sshAuthMethod: string; - sshSecretMode: string; - sshPrivateKeyPath: string; - sshAgentAuth: boolean; - - static createFrom(source: any = {}) { - return new VNCConfig(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.vncHost = source["vncHost"]; - this.vncPort = source["vncPort"]; - this.secretMode = source["secretMode"]; - this.viewOnly = source["viewOnly"]; - this.scaleToFit = source["scaleToFit"]; - this.quality = source["quality"]; - this.sharedSession = source["sharedSession"]; - this.sshTunnel = source["sshTunnel"]; - this.sshHost = source["sshHost"]; - this.sshPort = source["sshPort"]; - this.sshUsername = source["sshUsername"]; - this.sshAuthMethod = source["sshAuthMethod"]; - this.sshSecretMode = source["sshSecretMode"]; - this.sshPrivateKeyPath = source["sshPrivateKeyPath"]; - this.sshAgentAuth = source["sshAgentAuth"]; - } - } - export class RAWConfig { - autoReconnect: boolean; - lineEnding: string; - encoding: string; - showTimestamps: boolean; - showDirection: boolean; - payloadPresets: PayloadPreset[]; - - static createFrom(source: any = {}) { - return new RAWConfig(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.autoReconnect = source["autoReconnect"]; - this.lineEnding = source["lineEnding"]; - this.encoding = source["encoding"]; - this.showTimestamps = source["showTimestamps"]; - this.showDirection = source["showDirection"]; - this.payloadPresets = this.convertValues(source["payloadPresets"], PayloadPreset); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class TelnetConfig { - localEcho: boolean; - lineEnding: string; - terminalType: string; - encoding: string; - negotiationDebug: boolean; - lineMode: boolean; - autoReconnect: boolean; - maxReconnectTries: number; - loginScripts: LoginScriptStep[]; - - static createFrom(source: any = {}) { - return new TelnetConfig(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.localEcho = source["localEcho"]; - this.lineEnding = source["lineEnding"]; - this.terminalType = source["terminalType"]; - this.encoding = source["encoding"]; - this.negotiationDebug = source["negotiationDebug"]; - this.lineMode = source["lineMode"]; - this.autoReconnect = source["autoReconnect"]; - this.maxReconnectTries = source["maxReconnectTries"]; - this.loginScripts = this.convertValues(source["loginScripts"], LoginScriptStep); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class SSHConfig { - authMethods: string[]; - secretMode: string; - privateKeyPath: string; - keySecretMode: string; - hostKeyPolicy: string; - terminalType: string; - keepaliveInterval: number; - keepaliveCountMax: number; - compression: boolean; - agentAuth: boolean; - agentForwarding: boolean; - autoReconnect: boolean; - maxReconnectTries: number; - loginScripts: LoginScriptStep[]; - totpSecret: string; - proxyType: string; - proxyHost: string; - proxyPort: number; - proxyUsername: string; - proxyPassword: string; - jumpHosts: JumpHost[]; - portForwards: PortForward[]; - startupCommand: string; - envVars: Record; - - static createFrom(source: any = {}) { - return new SSHConfig(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.authMethods = source["authMethods"]; - this.secretMode = source["secretMode"]; - this.privateKeyPath = source["privateKeyPath"]; - this.keySecretMode = source["keySecretMode"]; - this.hostKeyPolicy = source["hostKeyPolicy"]; - this.terminalType = source["terminalType"]; - this.keepaliveInterval = source["keepaliveInterval"]; - this.keepaliveCountMax = source["keepaliveCountMax"]; - this.compression = source["compression"]; - this.agentAuth = source["agentAuth"]; - this.agentForwarding = source["agentForwarding"]; - this.autoReconnect = source["autoReconnect"]; - this.maxReconnectTries = source["maxReconnectTries"]; - this.loginScripts = this.convertValues(source["loginScripts"], LoginScriptStep); - this.totpSecret = source["totpSecret"]; - this.proxyType = source["proxyType"]; - this.proxyHost = source["proxyHost"]; - this.proxyPort = source["proxyPort"]; - this.proxyUsername = source["proxyUsername"]; - this.proxyPassword = source["proxyPassword"]; - this.jumpHosts = this.convertValues(source["jumpHosts"], JumpHost); - this.portForwards = this.convertValues(source["portForwards"], PortForward); - this.startupCommand = source["startupCommand"]; - this.envVars = source["envVars"]; - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - export class Profile { - id: string; - displayName: string; - description: string; - protocol: string; - host: string; - port: number; - username: string; - tags: string[]; - folder: string; - environment: string; - notes: string; - favourite: boolean; - lastConnected?: string; - connectTimeout: number; - readTimeout: number; - createdAt: string; - updatedAt: string; - terminalFontFamily: string; - terminalFontSize: number; - terminalScrollback: number; - terminalCursorStyle: string; - ssh?: SSHConfig; - telnet?: TelnetConfig; - raw?: RAWConfig; - github?: GitHubConfig; - vnc?: VNCConfig; - irc?: IRCConfig; - - static createFrom(source: any = {}) { - return new Profile(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.displayName = source["displayName"]; - this.description = source["description"]; - this.protocol = source["protocol"]; - this.host = source["host"]; - this.port = source["port"]; - this.username = source["username"]; - this.tags = source["tags"]; - this.folder = source["folder"]; - this.environment = source["environment"]; - this.notes = source["notes"]; - this.favourite = source["favourite"]; - this.lastConnected = source["lastConnected"]; - this.connectTimeout = source["connectTimeout"]; - this.readTimeout = source["readTimeout"]; - this.createdAt = source["createdAt"]; - this.updatedAt = source["updatedAt"]; - this.terminalFontFamily = source["terminalFontFamily"]; - this.terminalFontSize = source["terminalFontSize"]; - this.terminalScrollback = source["terminalScrollback"]; - this.terminalCursorStyle = source["terminalCursorStyle"]; - this.ssh = this.convertValues(source["ssh"], SSHConfig); - this.telnet = this.convertValues(source["telnet"], TelnetConfig); - this.raw = this.convertValues(source["raw"], RAWConfig); - this.github = this.convertValues(source["github"], GitHubConfig); - this.vnc = this.convertValues(source["vnc"], VNCConfig); - this.irc = this.convertValues(source["irc"], IRCConfig); - } - - convertValues(a: any, classs: any, asMap: boolean = false): any { - if (!a) { - return a; - } - if (a.slice && a.map) { - return (a as any[]).map(elem => this.convertValues(elem, classs)); - } else if ("object" === typeof a) { - if (asMap) { - for (const key of Object.keys(a)) { - a[key] = new classs(a[key]); - } - return a; - } - return new classs(a); - } - return a; - } - } - - - export class SessionInfo { - id: string; - profileId: string; - profileName: string; - protocol: string; - host: string; - port: number; - state: string; - tabName: string; - pinned: boolean; - connectedAt: string; - error?: string; - - static createFrom(source: any = {}) { - return new SessionInfo(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.profileId = source["profileId"]; - this.profileName = source["profileName"]; - this.protocol = source["protocol"]; - this.host = source["host"]; - this.port = source["port"]; - this.state = source["state"]; - this.tabName = source["tabName"]; - this.pinned = source["pinned"]; - this.connectedAt = source["connectedAt"]; - this.error = source["error"]; - } - } - export class Snippet { - id: string; - name: string; - command: string; - description: string; - folder: string; - tags: string[]; - createdAt: string; - updatedAt: string; - - static createFrom(source: any = {}) { - return new Snippet(source); - } - - constructor(source: any = {}) { - if ('string' === typeof source) source = JSON.parse(source); - this.id = source["id"]; - this.name = source["name"]; - this.command = source["command"]; - this.description = source["description"]; - this.folder = source["folder"]; - this.tags = source["tags"]; - this.createdAt = source["createdAt"]; - this.updatedAt = source["updatedAt"]; - } - } - - -} - diff --git a/frontend/wailsjs/runtime/package.json b/frontend/wailsjs/runtime/package.json deleted file mode 100644 index 1e7c8a5..0000000 --- a/frontend/wailsjs/runtime/package.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "@wailsapp/runtime", - "version": "2.0.0", - "description": "Wails Javascript runtime library", - "main": "runtime.js", - "types": "runtime.d.ts", - "scripts": { - }, - "repository": { - "type": "git", - "url": "git+https://github.com/wailsapp/wails.git" - }, - "keywords": [ - "Wails", - "Javascript", - "Go" - ], - "author": "Lea Anthony ", - "license": "MIT", - "bugs": { - "url": "https://github.com/wailsapp/wails/issues" - }, - "homepage": "https://github.com/wailsapp/wails#readme" -} diff --git a/frontend/wailsjs/runtime/runtime.d.ts b/frontend/wailsjs/runtime/runtime.d.ts deleted file mode 100644 index 3bbea84..0000000 --- a/frontend/wailsjs/runtime/runtime.d.ts +++ /dev/null @@ -1,330 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export interface Position { - x: number; - y: number; -} - -export interface Size { - w: number; - h: number; -} - -export interface Screen { - isCurrent: boolean; - isPrimary: boolean; - width : number - height : number -} - -// Environment information such as platform, buildtype, ... -export interface EnvironmentInfo { - buildType: string; - platform: string; - arch: string; -} - -// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit) -// emits the given event. Optional data may be passed with the event. -// This will trigger any event listeners. -export function EventsEmit(eventName: string, ...data: any): void; - -// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name. -export function EventsOn(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple) -// sets up a listener for the given event name, but will only trigger a given number times. -export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void; - -// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce) -// sets up a listener for the given event name, but will only trigger once. -export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void; - -// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff) -// unregisters the listener for the given event name. -export function EventsOff(eventName: string, ...additionalEventNames: string[]): void; - -// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall) -// unregisters all listeners. -export function EventsOffAll(): void; - -// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint) -// logs the given message as a raw message -export function LogPrint(message: string): void; - -// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace) -// logs the given message at the `trace` log level. -export function LogTrace(message: string): void; - -// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug) -// logs the given message at the `debug` log level. -export function LogDebug(message: string): void; - -// [LogError](https://wails.io/docs/reference/runtime/log#logerror) -// logs the given message at the `error` log level. -export function LogError(message: string): void; - -// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal) -// logs the given message at the `fatal` log level. -// The application will quit after calling this method. -export function LogFatal(message: string): void; - -// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo) -// logs the given message at the `info` log level. -export function LogInfo(message: string): void; - -// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning) -// logs the given message at the `warning` log level. -export function LogWarning(message: string): void; - -// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload) -// Forces a reload by the main application as well as connected browsers. -export function WindowReload(): void; - -// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp) -// Reloads the application frontend. -export function WindowReloadApp(): void; - -// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop) -// Sets the window AlwaysOnTop or not on top. -export function WindowSetAlwaysOnTop(b: boolean): void; - -// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme) -// *Windows only* -// Sets window theme to system default (dark/light). -export function WindowSetSystemDefaultTheme(): void; - -// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme) -// *Windows only* -// Sets window to light theme. -export function WindowSetLightTheme(): void; - -// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme) -// *Windows only* -// Sets window to dark theme. -export function WindowSetDarkTheme(): void; - -// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter) -// Centers the window on the monitor the window is currently on. -export function WindowCenter(): void; - -// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle) -// Sets the text in the window title bar. -export function WindowSetTitle(title: string): void; - -// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen) -// Makes the window full screen. -export function WindowFullscreen(): void; - -// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen) -// Restores the previous window dimensions and position prior to full screen. -export function WindowUnfullscreen(): void; - -// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen) -// Returns the state of the window, i.e. whether the window is in full screen mode or not. -export function WindowIsFullscreen(): Promise; - -// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize) -// Sets the width and height of the window. -export function WindowSetSize(width: number, height: number): void; - -// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize) -// Gets the width and height of the window. -export function WindowGetSize(): Promise; - -// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize) -// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMaxSize(width: number, height: number): void; - -// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize) -// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions. -// Setting a size of 0,0 will disable this constraint. -export function WindowSetMinSize(width: number, height: number): void; - -// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition) -// Sets the window position relative to the monitor the window is currently on. -export function WindowSetPosition(x: number, y: number): void; - -// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition) -// Gets the window position relative to the monitor the window is currently on. -export function WindowGetPosition(): Promise; - -// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide) -// Hides the window. -export function WindowHide(): void; - -// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow) -// Shows the window, if it is currently hidden. -export function WindowShow(): void; - -// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise) -// Maximises the window to fill the screen. -export function WindowMaximise(): void; - -// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise) -// Toggles between Maximised and UnMaximised. -export function WindowToggleMaximise(): void; - -// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise) -// Restores the window to the dimensions and position prior to maximising. -export function WindowUnmaximise(): void; - -// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised) -// Returns the state of the window, i.e. whether the window is maximised or not. -export function WindowIsMaximised(): Promise; - -// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise) -// Minimises the window. -export function WindowMinimise(): void; - -// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise) -// Restores the window to the dimensions and position prior to minimising. -export function WindowUnminimise(): void; - -// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised) -// Returns the state of the window, i.e. whether the window is minimised or not. -export function WindowIsMinimised(): Promise; - -// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal) -// Returns the state of the window, i.e. whether the window is normal or not. -export function WindowIsNormal(): Promise; - -// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour) -// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels. -export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void; - -// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall) -// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system. -export function ScreenGetAll(): Promise; - -// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl) -// Opens the given URL in the system browser. -export function BrowserOpenURL(url: string): void; - -// [Environment](https://wails.io/docs/reference/runtime/intro#environment) -// Returns information about the environment -export function Environment(): Promise; - -// [Quit](https://wails.io/docs/reference/runtime/intro#quit) -// Quits the application. -export function Quit(): void; - -// [Hide](https://wails.io/docs/reference/runtime/intro#hide) -// Hides the application. -export function Hide(): void; - -// [Show](https://wails.io/docs/reference/runtime/intro#show) -// Shows the application. -export function Show(): void; - -// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext) -// Returns the current text stored on clipboard -export function ClipboardGetText(): Promise; - -// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext) -// Sets a text on the clipboard -export function ClipboardSetText(text: string): Promise; - -// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop) -// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. -export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void - -// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff) -// OnFileDropOff removes the drag and drop listeners and handlers. -export function OnFileDropOff() :void - -// Check if the file path resolver is available -export function CanResolveFilePaths(): boolean; - -// Resolves file paths for an array of files -export function ResolveFilePaths(files: File[]): void - -// Notification types -export interface NotificationOptions { - id: string; - title: string; - subtitle?: string; // macOS and Linux only - body?: string; - categoryId?: string; - data?: { [key: string]: any }; -} - -export interface NotificationAction { - id?: string; - title?: string; - destructive?: boolean; // macOS-specific -} - -export interface NotificationCategory { - id?: string; - actions?: NotificationAction[]; - hasReplyField?: boolean; - replyPlaceholder?: string; - replyButtonTitle?: string; -} - -// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications) -// Initializes the notification service for the application. -// This must be called before sending any notifications. -export function InitializeNotifications(): Promise; - -// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications) -// Cleans up notification resources and releases any held connections. -export function CleanupNotifications(): Promise; - -// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable) -// Checks if notifications are available on the current platform. -export function IsNotificationAvailable(): Promise; - -// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization) -// Requests notification authorization from the user (macOS only). -export function RequestNotificationAuthorization(): Promise; - -// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization) -// Checks the current notification authorization status (macOS only). -export function CheckNotificationAuthorization(): Promise; - -// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification) -// Sends a basic notification with the given options. -export function SendNotification(options: NotificationOptions): Promise; - -// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions) -// Sends a notification with action buttons. Requires a registered category. -export function SendNotificationWithActions(options: NotificationOptions): Promise; - -// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory) -// Registers a notification category that can be used with SendNotificationWithActions. -export function RegisterNotificationCategory(category: NotificationCategory): Promise; - -// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory) -// Removes a previously registered notification category. -export function RemoveNotificationCategory(categoryId: string): Promise; - -// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications) -// Removes all pending notifications from the notification center. -export function RemoveAllPendingNotifications(): Promise; - -// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification) -// Removes a specific pending notification by its identifier. -export function RemovePendingNotification(identifier: string): Promise; - -// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications) -// Removes all delivered notifications from the notification center. -export function RemoveAllDeliveredNotifications(): Promise; - -// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification) -// Removes a specific delivered notification by its identifier. -export function RemoveDeliveredNotification(identifier: string): Promise; - -// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification) -// Removes a notification by its identifier (cross-platform convenience function). -export function RemoveNotification(identifier: string): Promise; \ No newline at end of file diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js deleted file mode 100644 index 556621e..0000000 --- a/frontend/wailsjs/runtime/runtime.js +++ /dev/null @@ -1,298 +0,0 @@ -/* - _ __ _ __ -| | / /___ _(_) /____ -| | /| / / __ `/ / / ___/ -| |/ |/ / /_/ / / (__ ) -|__/|__/\__,_/_/_/____/ -The electron alternative for Go -(c) Lea Anthony 2019-present -*/ - -export function LogPrint(message) { - window.runtime.LogPrint(message); -} - -export function LogTrace(message) { - window.runtime.LogTrace(message); -} - -export function LogDebug(message) { - window.runtime.LogDebug(message); -} - -export function LogInfo(message) { - window.runtime.LogInfo(message); -} - -export function LogWarning(message) { - window.runtime.LogWarning(message); -} - -export function LogError(message) { - window.runtime.LogError(message); -} - -export function LogFatal(message) { - window.runtime.LogFatal(message); -} - -export function EventsOnMultiple(eventName, callback, maxCallbacks) { - return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks); -} - -export function EventsOn(eventName, callback) { - return EventsOnMultiple(eventName, callback, -1); -} - -export function EventsOff(eventName, ...additionalEventNames) { - return window.runtime.EventsOff(eventName, ...additionalEventNames); -} - -export function EventsOffAll() { - return window.runtime.EventsOffAll(); -} - -export function EventsOnce(eventName, callback) { - return EventsOnMultiple(eventName, callback, 1); -} - -export function EventsEmit(eventName) { - let args = [eventName].slice.call(arguments); - return window.runtime.EventsEmit.apply(null, args); -} - -export function WindowReload() { - window.runtime.WindowReload(); -} - -export function WindowReloadApp() { - window.runtime.WindowReloadApp(); -} - -export function WindowSetAlwaysOnTop(b) { - window.runtime.WindowSetAlwaysOnTop(b); -} - -export function WindowSetSystemDefaultTheme() { - window.runtime.WindowSetSystemDefaultTheme(); -} - -export function WindowSetLightTheme() { - window.runtime.WindowSetLightTheme(); -} - -export function WindowSetDarkTheme() { - window.runtime.WindowSetDarkTheme(); -} - -export function WindowCenter() { - window.runtime.WindowCenter(); -} - -export function WindowSetTitle(title) { - window.runtime.WindowSetTitle(title); -} - -export function WindowFullscreen() { - window.runtime.WindowFullscreen(); -} - -export function WindowUnfullscreen() { - window.runtime.WindowUnfullscreen(); -} - -export function WindowIsFullscreen() { - return window.runtime.WindowIsFullscreen(); -} - -export function WindowGetSize() { - return window.runtime.WindowGetSize(); -} - -export function WindowSetSize(width, height) { - window.runtime.WindowSetSize(width, height); -} - -export function WindowSetMaxSize(width, height) { - window.runtime.WindowSetMaxSize(width, height); -} - -export function WindowSetMinSize(width, height) { - window.runtime.WindowSetMinSize(width, height); -} - -export function WindowSetPosition(x, y) { - window.runtime.WindowSetPosition(x, y); -} - -export function WindowGetPosition() { - return window.runtime.WindowGetPosition(); -} - -export function WindowHide() { - window.runtime.WindowHide(); -} - -export function WindowShow() { - window.runtime.WindowShow(); -} - -export function WindowMaximise() { - window.runtime.WindowMaximise(); -} - -export function WindowToggleMaximise() { - window.runtime.WindowToggleMaximise(); -} - -export function WindowUnmaximise() { - window.runtime.WindowUnmaximise(); -} - -export function WindowIsMaximised() { - return window.runtime.WindowIsMaximised(); -} - -export function WindowMinimise() { - window.runtime.WindowMinimise(); -} - -export function WindowUnminimise() { - window.runtime.WindowUnminimise(); -} - -export function WindowSetBackgroundColour(R, G, B, A) { - window.runtime.WindowSetBackgroundColour(R, G, B, A); -} - -export function ScreenGetAll() { - return window.runtime.ScreenGetAll(); -} - -export function WindowIsMinimised() { - return window.runtime.WindowIsMinimised(); -} - -export function WindowIsNormal() { - return window.runtime.WindowIsNormal(); -} - -export function BrowserOpenURL(url) { - window.runtime.BrowserOpenURL(url); -} - -export function Environment() { - return window.runtime.Environment(); -} - -export function Quit() { - window.runtime.Quit(); -} - -export function Hide() { - window.runtime.Hide(); -} - -export function Show() { - window.runtime.Show(); -} - -export function ClipboardGetText() { - return window.runtime.ClipboardGetText(); -} - -export function ClipboardSetText(text) { - return window.runtime.ClipboardSetText(text); -} - -/** - * Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * - * @export - * @callback OnFileDropCallback - * @param {number} x - x coordinate of the drop - * @param {number} y - y coordinate of the drop - * @param {string[]} paths - A list of file paths. - */ - -/** - * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings. - * - * @export - * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished. - * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target) - */ -export function OnFileDrop(callback, useDropTarget) { - return window.runtime.OnFileDrop(callback, useDropTarget); -} - -/** - * OnFileDropOff removes the drag and drop listeners and handlers. - */ -export function OnFileDropOff() { - return window.runtime.OnFileDropOff(); -} - -export function CanResolveFilePaths() { - return window.runtime.CanResolveFilePaths(); -} - -export function ResolveFilePaths(files) { - return window.runtime.ResolveFilePaths(files); -} - -export function InitializeNotifications() { - return window.runtime.InitializeNotifications(); -} - -export function CleanupNotifications() { - return window.runtime.CleanupNotifications(); -} - -export function IsNotificationAvailable() { - return window.runtime.IsNotificationAvailable(); -} - -export function RequestNotificationAuthorization() { - return window.runtime.RequestNotificationAuthorization(); -} - -export function CheckNotificationAuthorization() { - return window.runtime.CheckNotificationAuthorization(); -} - -export function SendNotification(options) { - return window.runtime.SendNotification(options); -} - -export function SendNotificationWithActions(options) { - return window.runtime.SendNotificationWithActions(options); -} - -export function RegisterNotificationCategory(category) { - return window.runtime.RegisterNotificationCategory(category); -} - -export function RemoveNotificationCategory(categoryId) { - return window.runtime.RemoveNotificationCategory(categoryId); -} - -export function RemoveAllPendingNotifications() { - return window.runtime.RemoveAllPendingNotifications(); -} - -export function RemovePendingNotification(identifier) { - return window.runtime.RemovePendingNotification(identifier); -} - -export function RemoveAllDeliveredNotifications() { - return window.runtime.RemoveAllDeliveredNotifications(); -} - -export function RemoveDeliveredNotification(identifier) { - return window.runtime.RemoveDeliveredNotification(identifier); -} - -export function RemoveNotification(identifier) { - return window.runtime.RemoveNotification(identifier); -} \ No newline at end of file diff --git a/go.mod b/go.mod deleted file mode 100644 index facfaba..0000000 --- a/go.mod +++ /dev/null @@ -1,58 +0,0 @@ -module github.com/andydixon/link - -go 1.26.0 - -toolchain go1.26.5 - -require ( - github.com/creack/pty v1.1.24 - github.com/google/uuid v1.6.0 - github.com/gorilla/websocket v1.5.3 - github.com/hajimehoshi/go-mp3 v0.3.4 - github.com/klauspost/compress v1.17.11 - github.com/lrstanley/girc v1.1.1 - github.com/pkg/sftp v1.13.10 - github.com/pquerna/otp v1.5.0 - github.com/ulikunitz/xz v0.5.15 - github.com/wailsapp/wails/v2 v2.12.0 - github.com/zalando/go-keyring v0.2.8 - golang.org/x/crypto v0.56.0 - golang.org/x/net v0.57.0 - golang.org/x/text v0.41.0 - modernc.org/sqlite v1.53.0 -) - -require ( - git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 // indirect - github.com/bep/debounce v1.2.1 // indirect - github.com/boombuler/barcode v1.1.0 // indirect - github.com/danieljoos/wincred v1.2.3 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-ole/go-ole v1.3.0 // indirect - github.com/godbus/dbus/v5 v5.2.2 // indirect - github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect - github.com/kr/fs v0.1.0 // indirect - github.com/labstack/echo/v4 v4.15.2 // indirect - github.com/labstack/gommon v0.5.0 // indirect - github.com/leaanthony/go-ansi-parser v1.6.1 // indirect - github.com/leaanthony/gosod v1.0.4 // indirect - github.com/leaanthony/slicer v1.6.0 // indirect - github.com/leaanthony/u v1.1.1 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.22 // indirect - github.com/ncruces/go-strftime v1.0.0 // indirect - github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/samber/lo v1.53.0 // indirect - github.com/tkrajina/go-reflector v0.5.8 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasttemplate v1.2.2 // indirect - github.com/wailsapp/go-webview2 v1.0.23 // indirect - github.com/wailsapp/mimetype v1.4.1 // indirect - golang.org/x/sys v0.47.0 // indirect - modernc.org/libc v1.73.4 // indirect - modernc.org/mathutil v1.7.1 // indirect - modernc.org/memory v1.11.0 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 39b096b..0000000 --- a/go.sum +++ /dev/null @@ -1,156 +0,0 @@ -git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3 h1:N3IGoHHp9pb6mj1cbXbuaSXV/UMKwmbKLf53nQmtqMA= -git.sr.ht/~jackmordaunt/go-toast/v2 v2.0.3/go.mod h1:QtOLZGz8olr4qH2vWK0QH0w0O4T9fEIjMuWpKUsH7nc= -github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= -github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= -github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo= -github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= -github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= -github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= -github.com/danieljoos/wincred v1.2.3 h1:v7dZC2x32Ut3nEfRH+vhoZGvN72+dQ/snVXo/vMFLdQ= -github.com/danieljoos/wincred v1.2.3/go.mod h1:6qqX0WNrS4RzPZ1tnroDzq9kY3fu1KwE7MRLQK4X0bs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= -github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= -github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ= -github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= -github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= -github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/hajimehoshi/go-mp3 v0.3.4 h1:NUP7pBYH8OguP4diaTZ9wJbUbk3tC0KlfzsEpWmYj68= -github.com/hajimehoshi/go-mp3 v0.3.4/go.mod h1:fRtZraRFcWb0pu7ok0LqyFhCUrPeMsGRSVop0eemFmo= -github.com/hajimehoshi/oto/v2 v2.3.1/go.mod h1:seWLbgHH7AyUMYKfKYT9pg7PhUu9/SisyJvNTT+ASQo= -github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= -github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ= -github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= -github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/labstack/echo/v4 v4.15.2 h1:nnh2sCzGCVYnU+wCisMPiYapEg/QVo/gcI9ePKg5/T4= -github.com/labstack/echo/v4 v4.15.2/go.mod h1:Xzp1Ns1RA2c9fY7nSgUJkpkUZGNbEIVHZbtbOMPktBI= -github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c= -github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0= -github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= -github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= -github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A= -github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU= -github.com/leaanthony/gosod v1.0.4 h1:YLAbVyd591MRffDgxUOU1NwLhT9T1/YiwjKZpkNFeaI= -github.com/leaanthony/gosod v1.0.4/go.mod h1:GKuIL0zzPj3O1SdWQOdgURSuhkF+Urizzxh26t9f1cw= -github.com/leaanthony/slicer v1.6.0 h1:1RFP5uiPJvT93TAHi+ipd3NACobkW53yUiBqZheE/Js= -github.com/leaanthony/slicer v1.6.0/go.mod h1:o/Iz29g7LN0GqH3aMjWAe90381nyZlDNquK+mtH2Fj8= -github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M= -github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI= -github.com/lrstanley/girc v1.1.1 h1:0Y8a2tqQGDeFXfBQkAYOu5DbWqlydCJsi+4N+td4azk= -github.com/lrstanley/girc v1.1.1/go.mod h1:lgrnhcF8bg/Bd5HA5DOb4Z+uGqUqGnp4skr+J2GwVgI= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ= -github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= -github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= -github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= -github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= -github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU= -github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs= -github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= -github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM= -github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ= -github.com/tkrajina/go-reflector v0.5.8/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= -github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= -github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0= -github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc= -github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= -github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v2 v2.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozHn5c= -github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg= -github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs= -github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0= -golang.org/x/crypto v0.56.0 h1:GUh5Ii4J5jtcseSMiRqr1jXCNHoxjeV9Fmekc2oLy6Y= -golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= -golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= -golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= -golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= -golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= -golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= -golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= -golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= -golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8= -golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= -golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -modernc.org/cc/v4 v4.28.4 h1:Hd/4Es+MBj+/7hSdZaisNyu6bv3V0Dp2MdllyfqaH+c= -modernc.org/cc/v4 v4.28.4/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= -modernc.org/ccgo/v4 v4.34.4 h1:OVnSOWQjVKOYkFxoHYB+qQmSHK5gqMqARM+K9DpR/Ws= -modernc.org/ccgo/v4 v4.34.4/go.mod h1:qdKqE8FNIYyysougB1RX9MxCzp5oJOcQXSobANJ4TuE= -modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= -modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= -modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= -modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= -modernc.org/gc/v3 v3.1.3 h1:6QAplYyVO+KdPW3pGnqmJDUxtkec8ooEWvks/hhU3lc= -modernc.org/gc/v3 v3.1.3/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= -modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= -modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= -modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= -modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= -modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= -modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= -modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= -modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= -modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= -modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= -modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= -modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= -modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= -modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s= -modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= -modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= -modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= -modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/internal/api/api.go b/internal/api/api.go deleted file mode 100644 index 8e5d3ae..0000000 --- a/internal/api/api.go +++ /dev/null @@ -1,2262 +0,0 @@ -// Package api provides the Wails-bound API surface that the frontend calls. -// All methods are called from JavaScript and must return JSON-serialisable types. -package api - -import ( - "bytes" - "context" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "net" - "os" - "os/exec" - "path" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/pquerna/otp/totp" - wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" - - "github.com/andydixon/link/internal/audit" - "github.com/andydixon/link/internal/exportimport" - "github.com/andydixon/link/internal/localshell" - "github.com/andydixon/link/internal/models" - "github.com/andydixon/link/internal/preferences" - "github.com/andydixon/link/internal/secrets" - "github.com/andydixon/link/internal/sessions" - "github.com/andydixon/link/internal/sshconfig" - "github.com/andydixon/link/internal/storage" - "github.com/andydixon/link/internal/updater" - "github.com/andydixon/link/internal/validation" - "github.com/google/uuid" -) - -// appVersion is set at build time via ldflags. -var appVersion = "dev" - -// appUpdateURL is set at build time via ldflags. -var appUpdateURL = "" - -const ( - maxProfileImportJSONBytes = 5 * 1024 * 1024 - maxInlineSFTPUploadBytes = 25 * 1024 * 1024 - maxSFTPUploadChunkBytes = 1024 * 1024 - maxStreamingSFTPUploadSize = int64(2 * 1024 * 1024 * 1024) - maxConcurrentSFTPUploads = 16 - maxSSHPublicKeyBytes = 64 * 1024 - sshKeygenTimeout = 30 * time.Second -) - -// Service is the main API surface exposed to the frontend via Wails bindings. -type Service struct { - store *storage.Store - secretStore *secrets.SecretStore - keychainStore *secrets.KeychainStore - sessions *sessions.Manager - auditLog *audit.Logger - prefs *preferences.Manager - ctx context.Context - eventSink func(string, interface{}) - - // secretChans maps request IDs to channels awaiting user secret input. - secretMu sync.Mutex - secretChans map[string]chan secretResponse - secretListenerRegistered bool - secretListenerCancel func() - - uploadMu sync.Mutex - uploads map[string]*sftpUpload - - leviathan *leviathanState -} - -type sftpUpload struct { - mu sync.Mutex - writer io.WriteCloser - sessionID string - remotePath string - written int64 -} - -// NewService creates a Service wired to the given backing components. -func NewService( - store *storage.Store, - secretStore *secrets.SecretStore, - keychainStore *secrets.KeychainStore, - sess *sessions.Manager, - auditLog *audit.Logger, - prefs *preferences.Manager, -) *Service { - return &Service{ - store: store, - secretStore: secretStore, - keychainStore: keychainStore, - sessions: sess, - auditLog: auditLog, - prefs: prefs, - uploads: make(map[string]*sftpUpload), - leviathan: &leviathanState{}, - } -} - -// SetContext stores the Wails runtime context and registers event listeners. -func (s *Service) SetContext(ctx context.Context) { - s.ctx = ctx - s.RegisterSecretResponseListener() -} - -// Shutdown releases active uploads, prompt waiters, and app-wide Wails -// listeners. It is safe to call when no Wails context was configured. -func (s *Service) Shutdown() { - s.cancelSFTPUploads("") - - s.secretMu.Lock() - cancel := s.secretListenerCancel - s.secretListenerCancel = nil - s.secretListenerRegistered = false - waiters := s.secretChans - s.secretChans = make(map[string]chan secretResponse) - s.secretMu.Unlock() - - if cancel != nil { - cancel() - } - for _, ch := range waiters { - select { - case ch <- secretResponse{cancelled: true}: - default: - } - } -} - -// SetEventSink registers an alternate UI event sink for non-Wails hosts such -// as the VS Code bridge. -func (s *Service) SetEventSink(sink func(string, interface{})) { - s.eventSink = sink -} - -// --------------------------------------------------------------------------- -// Profile operations -// --------------------------------------------------------------------------- - -// CreateProfile validates and persists a new connection profile. -func (s *Service) CreateProfile(profile models.Profile) (models.Profile, error) { - if profile.ID == "" { - profile.ID = uuid.New().String() - } - normaliseProfileForSave(&profile) - now := time.Now() - profile.CreatedAt = now - profile.UpdatedAt = now - - if profile.Tags == nil { - profile.Tags = []string{} - } - - if err := validation.ValidateProfile(&profile); err != nil { - return models.Profile{}, err - } - if err := s.store.CreateProfile(&profile); err != nil { - return models.Profile{}, fmt.Errorf("failed to create profile: %w", err) - } - s.auditLog.LogProfileCreate(profile.ID, profile.DisplayName) - return profile, nil -} - -// GetProfile retrieves a profile by ID. -func (s *Service) GetProfile(id string) (models.Profile, error) { - p, err := s.store.GetProfile(id) - if err != nil { - return models.Profile{}, fmt.Errorf("profile not found: %w", err) - } - return *p, nil -} - -// UpdateProfile persists changes to an existing profile. -func (s *Service) UpdateProfile(profile models.Profile) (models.Profile, error) { - normaliseProfileForSave(&profile) - profile.UpdatedAt = time.Now() - if err := validation.ValidateProfile(&profile); err != nil { - return models.Profile{}, err - } - if err := s.store.UpdateProfile(&profile); err != nil { - return models.Profile{}, fmt.Errorf("failed to update profile: %w", err) - } - s.auditLog.LogProfileUpdate(profile.ID, profile.DisplayName) - return profile, nil -} - -// DeleteProfile removes a profile and its associated secrets. -func (s *Service) DeleteProfile(id string) error { - p, err := s.store.GetProfile(id) - if err != nil { - return fmt.Errorf("profile not found: %w", err) - } - - if err := s.store.DeleteProfile(id); err != nil { - return fmt.Errorf("failed to delete profile: %w", err) - } - - // Best-effort secret cleanup. - if s.secretStore != nil && s.secretStore.IsUnlocked() { - _ = s.secretStore.DeleteProfileSecrets(id) - } - - s.auditLog.LogProfileDelete(id, p.DisplayName) - return nil -} - -// ListProfiles returns all stored profiles. -func (s *Service) ListProfiles() ([]models.Profile, error) { - profiles, err := s.store.ListProfiles() - if err != nil { - return nil, err - } - return derefProfiles(profiles), nil -} - -// ListSSHKeys returns absolute paths to candidate SSH private keys in ~/.ssh. -func (s *Service) ListSSHKeys() ([]string, error) { - home, err := os.UserHomeDir() - if err != nil { - return []string{}, nil - } - - dir := filepath.Join(home, ".ssh") - entries, err := os.ReadDir(dir) - if err != nil { - if os.IsNotExist(err) { - return []string{}, nil - } - return nil, err - } - - ignored := map[string]bool{ - "authorized_keys": true, - "authorized_keys2": true, - "config": true, - "known_hosts": true, - "known_hosts.old": true, - "ssh_config": true, - "ssh_known_hosts": true, - "ssh_known_hosts2": true, - } - - keys := make([]string, 0) - for _, entry := range entries { - if entry.IsDir() { - continue - } - name := entry.Name() - if ignored[name] { - continue - } - if strings.HasSuffix(name, ".pub") { - continue - } - if strings.HasSuffix(name, ".old") { - continue - } - fullPath := filepath.Join(dir, name) - if !isLikelyPrivateKey(fullPath) { - continue - } - keys = append(keys, fullPath) - } - - sort.Strings(keys) - return keys, nil -} - -func isLikelyPrivateKey(path string) bool { - file, err := os.Open(path) - if err != nil { - return false - } - defer file.Close() - - buf := make([]byte, 512) - n, err := file.Read(buf) - if err != nil && n == 0 { - return false - } - head := buf[:n] - - if bytes.HasPrefix(head, []byte("openssh-key-v1")) { - return true - } - - if bytes.Contains(head, []byte("BEGIN OPENSSH PRIVATE KEY")) { - return true - } - if bytes.Contains(head, []byte("BEGIN RSA PRIVATE KEY")) { - return true - } - if bytes.Contains(head, []byte("BEGIN DSA PRIVATE KEY")) { - return true - } - if bytes.Contains(head, []byte("BEGIN EC PRIVATE KEY")) { - return true - } - if bytes.Contains(head, []byte("BEGIN ED25519 PRIVATE KEY")) { - return true - } - if bytes.Contains(head, []byte("BEGIN PRIVATE KEY")) { - return true - } - - return false -} - -// SearchProfiles searches profiles by display name, host, or tags. -func (s *Service) SearchProfiles(query string) ([]models.Profile, error) { - profiles, err := s.store.SearchProfiles(query) - if err != nil { - return nil, err - } - return derefProfiles(profiles), nil -} - -// ListProfilesByProtocol returns profiles filtered by protocol type. -func (s *Service) ListProfilesByProtocol(protocol string) ([]models.Profile, error) { - profiles, err := s.store.ListProfilesByProtocol(protocol) - if err != nil { - return nil, err - } - return derefProfiles(profiles), nil -} - -// ListProfilesByTag returns profiles that contain the given tag. -func (s *Service) ListProfilesByTag(tag string) ([]models.Profile, error) { - profiles, err := s.store.ListProfilesByTag(tag) - if err != nil { - return nil, err - } - return derefProfiles(profiles), nil -} - -// ListProfilesByFolder returns profiles in the specified folder. -func (s *Service) ListProfilesByFolder(folder string) ([]models.Profile, error) { - profiles, err := s.store.ListProfilesByFolder(folder) - if err != nil { - return nil, err - } - return derefProfiles(profiles), nil -} - -// ListFavourites returns all favourite profiles. -func (s *Service) ListFavourites() ([]models.Profile, error) { - profiles, err := s.store.ListFavourites() - if err != nil { - return nil, err - } - return derefProfiles(profiles), nil -} - -// SetFavourite updates the favourite status of a profile. -func (s *Service) SetFavourite(id string, favourite bool) error { - return s.store.SetFavourite(id, favourite) -} - -// DuplicateProfile creates a copy of an existing profile with a new ID. -func (s *Service) DuplicateProfile(id string) (models.Profile, error) { - orig, err := s.store.GetProfile(id) - if err != nil { - return models.Profile{}, fmt.Errorf("profile not found: %w", err) - } - - dup := *orig - dup.ID = uuid.New().String() - dup.DisplayName = orig.DisplayName + " (copy)" - now := time.Now() - dup.CreatedAt = now - dup.UpdatedAt = now - dup.LastConnected = nil - - if err := s.store.CreateProfile(&dup); err != nil { - return models.Profile{}, fmt.Errorf("failed to duplicate profile: %w", err) - } - s.auditLog.LogProfileCreate(dup.ID, dup.DisplayName) - return dup, nil -} - -// NewProfile returns a new profile with sensible defaults for the given protocol. -func (s *Service) NewProfile(protocol string) models.Profile { - return models.NewProfile(models.Protocol(protocol)) -} - -// --------------------------------------------------------------------------- -// Session operations -// --------------------------------------------------------------------------- - -// ConnectSession loads a profile by ID and starts a session asynchronously. -// Returns immediately with session info in "connecting" state; the frontend -// should listen for session:state events for status updates. -func (s *Service) ConnectSession(profileID string) (models.SessionInfo, error) { - profile, err := s.store.GetProfile(profileID) - if err != nil { - return models.SessionInfo{}, fmt.Errorf("profile not found: %w", err) - } - - sessionID, err := s.sessions.Connect(profile, s.promptSecret) - if err != nil { - return models.SessionInfo{}, err - } - - // Update last-connected timestamp. - now := time.Now() - profile.LastConnected = &now - profile.UpdatedAt = now - _ = s.store.UpdateProfile(profile) - - info := s.sessions.GetSessionInfo(sessionID) - if info == nil { - return models.SessionInfo{}, fmt.Errorf("session created but info not found") - } - return *info, nil -} - -// ConnectQuick opens a session from an ad-hoc (unsaved) profile. -func (s *Service) ConnectQuick(profile models.Profile) (models.SessionInfo, error) { - if profile.ID == "" { - profile.ID = uuid.New().String() - } - normaliseProfileForSave(&profile) - sessionID, err := s.sessions.Connect(&profile, s.promptSecret) - if err != nil { - return models.SessionInfo{}, err - } - info := s.sessions.GetSessionInfo(sessionID) - if info == nil { - return models.SessionInfo{}, fmt.Errorf("session created but info not found") - } - return *info, nil -} - -// ConnectLocal opens a local shell session using the configured (or default) shell. -func (s *Service) ConnectLocal() (models.SessionInfo, error) { - shell := "" - if s.prefs != nil { - prefs := s.prefs.Get() - shell = prefs.LocalShell - } - sessionID, err := s.sessions.ConnectLocal(shell) - if err != nil { - return models.SessionInfo{}, err - } - info := s.sessions.GetSessionInfo(sessionID) - if info == nil { - return models.SessionInfo{}, fmt.Errorf("session created but info not found") - } - return *info, nil -} - -// RestartLocalSession starts a new shell in an exited local terminal while -// preserving its tab, scrollback, terminal geometry, and best-effort cwd. -func (s *Service) RestartLocalSession(sessionID string, cols int, rows int) error { - return s.sessions.RestartLocal(sessionID, cols, rows) -} - -// DisconnectSession closes a session by ID. -func (s *Service) DisconnectSession(sessionID string) error { - s.cancelSFTPUploads(sessionID) - return s.sessions.Disconnect(sessionID) -} - -// ReconnectSession closes and re-establishes a session. -func (s *Service) ReconnectSession(sessionID string) error { - s.cancelSFTPUploads(sessionID) - return s.sessions.Reconnect(sessionID, s.promptSecret) -} - -// CancelReconnect cancels an in-progress auto-reconnect for a session. -func (s *Service) CancelReconnect(sessionID string) { - s.sessions.CancelReconnect(sessionID) -} - -// WriteSession writes base64-encoded data to a session. -func (s *Service) WriteSession(sessionID string, data string) error { - decoded, err := base64.StdEncoding.DecodeString(data) - if err != nil { - return fmt.Errorf("invalid base64 data: %w", err) - } - return s.sessions.WriteToSession(sessionID, decoded) -} - -// ResizeSession sends a terminal size change to a session. -func (s *Service) ResizeSession(sessionID string, cols int, rows int) error { - return s.sessions.ResizeSession(sessionID, cols, rows) -} - -// ListSessions returns all active sessions. -func (s *Service) ListSessions() []models.SessionInfo { - return s.sessions.ListSessions() -} - -// RenameSession changes the display name of a session tab. -func (s *Service) RenameSession(sessionID string, name string) error { - return s.sessions.RenameSession(sessionID, name) -} - -// PinSession toggles the pinned state of a session tab. -func (s *Service) PinSession(sessionID string, pinned bool) error { - return s.sessions.PinSession(sessionID, pinned) -} - -// CloseAllSessions terminates all active sessions. -func (s *Service) CloseAllSessions() { - s.cancelSFTPUploads("") - s.sessions.CloseAll() -} - -// --------------------------------------------------------------------------- -// RAW-specific -// --------------------------------------------------------------------------- - -// WriteSessionText sends plain text to a RAW session. -func (s *Service) WriteSessionText(sessionID string, text string) error { - return s.sessions.WriteSessionText(sessionID, text) -} - -// WriteSessionHex sends hex-encoded data to a RAW session. -func (s *Service) WriteSessionHex(sessionID string, hexStr string) error { - return s.sessions.WriteSessionHex(sessionID, hexStr) -} - -// WriteSessionEscaped sends text with C-style escape sequences to a RAW session. -func (s *Service) WriteSessionEscaped(sessionID string, text string) error { - return s.sessions.WriteSessionEscaped(sessionID, text) -} - -// GetSessionHistory returns the data history for a RAW session. -func (s *Service) GetSessionHistory(sessionID string) ([]map[string]interface{}, error) { - entries, err := s.sessions.GetSessionHistory(sessionID) - if err != nil { - return nil, err - } - - result := make([]map[string]interface{}, len(entries)) - for i, e := range entries { - result[i] = map[string]interface{}{ - "timestamp": e.Timestamp.UnixMilli(), - "direction": e.Direction, - "data": base64.StdEncoding.EncodeToString(e.Data), - } - } - return result, nil -} - -// ClearSessionHistory clears the data history for a RAW session. -func (s *Service) ClearSessionHistory(sessionID string) error { - return s.sessions.ClearSessionHistory(sessionID) -} - -// SaveSessionCapture saves the RAW session data to a file. -func (s *Service) SaveSessionCapture(sessionID string, path string) error { - return s.sessions.SaveSessionCapture(sessionID, path) -} - -// --------------------------------------------------------------------------- -// Telnet-specific -// --------------------------------------------------------------------------- - -// SendBreak transmits a Telnet BRK command to a session. -func (s *Service) SendBreak(sessionID string) error { - return s.sessions.SendBreak(sessionID) -} - -// --------------------------------------------------------------------------- -// IRC-specific -// --------------------------------------------------------------------------- - -// IRCHandleInput sends an IRC composer line, including slash commands. -func (s *Service) IRCHandleInput(sessionID string, target string, input string) error { - return s.sessions.IRCHandleInput(sessionID, target, input) -} - -// IRCJoin joins an IRC room. -func (s *Service) IRCJoin(sessionID string, room string) error { - return s.sessions.IRCJoin(sessionID, room) -} - -// IRCPart leaves an IRC room. -func (s *Service) IRCPart(sessionID string, room string) error { - return s.sessions.IRCPart(sessionID, room) -} - -// IRCListRooms requests the server room list. Results are emitted as events. -func (s *Service) IRCListRooms(sessionID string) error { - return s.sessions.IRCListRooms(sessionID) -} - -// IRCRooms returns currently joined IRC rooms. -func (s *Service) IRCRooms(sessionID string) ([]map[string]interface{}, error) { - rooms, err := s.sessions.IRCRooms(sessionID) - if err != nil { - return nil, err - } - result := make([]map[string]interface{}, len(rooms)) - for i, room := range rooms { - result[i] = map[string]interface{}{ - "name": room.Name, - "topic": room.Topic, - "users": room.Users, - } - } - return result, nil -} - -// IRCUsers returns the known users in a joined IRC room. -func (s *Service) IRCUsers(sessionID string, room string) ([]string, error) { - return s.sessions.IRCUsers(sessionID, room) -} - -// --------------------------------------------------------------------------- -// Secret store -// --------------------------------------------------------------------------- - -// IsSecretStoreInitialised reports whether the encrypted vault has been created. -func (s *Service) IsSecretStoreInitialised() bool { - if s.secretStore == nil { - return false - } - return s.secretStore.IsInitialised() -} - -// InitialiseSecretStore creates a new encrypted vault with the given master password. -func (s *Service) InitialiseSecretStore(masterPassword string) error { - if s.secretStore == nil { - return fmt.Errorf("secret store is not configured") - } - return s.secretStore.Initialise(masterPassword) -} - -// UnlockSecretStore decrypts the vault using the master password. -func (s *Service) UnlockSecretStore(masterPassword string) error { - if s.secretStore == nil { - return fmt.Errorf("secret store is not configured") - } - return s.secretStore.Unlock(masterPassword) -} - -// LockSecretStore zeroes key material and locks the vault. -func (s *Service) LockSecretStore() { - if s.secretStore != nil { - s.secretStore.Lock() - } -} - -// IsSecretStoreUnlocked reports whether the vault is currently unlocked. -func (s *Service) IsSecretStoreUnlocked() bool { - if s.secretStore == nil { - return false - } - return s.secretStore.IsUnlocked() -} - -// SetProfileSecret encrypts and stores a secret for the given profile. -func (s *Service) SetProfileSecret(profileID string, secretType string, value string) error { - key := fmt.Sprintf("profile:%s:%s", profileID, secretType) - - // Try the OS keychain first. - if s.keychainStore != nil && s.keychainStore.Available() { - return s.keychainStore.SetSecret(key, value) - } - - // Fall back to the encrypted file store. - if s.secretStore == nil || !s.secretStore.IsUnlocked() { - return fmt.Errorf("secret store is locked; unlock it first") - } - return s.secretStore.SetSecret(key, value) -} - -// DeleteProfileSecrets removes all secrets associated with a profile. -func (s *Service) DeleteProfileSecrets(profileID string) error { - // Remove from both stores where possible. - if s.secretStore != nil && s.secretStore.IsUnlocked() { - _ = s.secretStore.DeleteProfileSecrets(profileID) - } - // OS keychain entries need individual deletion — handled at a higher level - // because we cannot enumerate keychain keys by prefix portably. - return nil -} - -// --------------------------------------------------------------------------- -// TOTP / 2FA -// --------------------------------------------------------------------------- - -// SetTOTPSecret stores a TOTP secret for the given profile, encrypted in -// the secret vault (or OS keychain). -func (s *Service) SetTOTPSecret(profileID string, secret string) error { - return s.SetProfileSecret(profileID, "totp", secret) -} - -// GetTOTPCode generates the current 6-digit TOTP code for the given profile. -// Used for the "Test" button in the profile editor. -func (s *Service) GetTOTPCode(profileID string) (string, error) { - // Look up the TOTP secret from the vault. - totpKey := fmt.Sprintf("profile:%s:totp", profileID) - var secret string - - if s.keychainStore != nil && s.keychainStore.Available() { - if val, err := s.keychainStore.GetSecret(totpKey); err == nil && val != "" { - secret = val - } - } - if secret == "" && s.secretStore != nil && s.secretStore.IsUnlocked() { - if val, err := s.secretStore.GetSecret(totpKey); err == nil && val != "" { - secret = val - } - } - if secret == "" { - return "", fmt.Errorf("no TOTP secret configured for this profile") - } - - code, err := totp.GenerateCode(secret, time.Now()) - if err != nil { - return "", fmt.Errorf("failed to generate TOTP code: %w", err) - } - return code, nil -} - -// HasTOTPSecret checks whether a profile has a TOTP secret stored. -func (s *Service) HasTOTPSecret(profileID string) bool { - totpKey := fmt.Sprintf("profile:%s:totp", profileID) - if s.keychainStore != nil && s.keychainStore.Available() { - if val, err := s.keychainStore.GetSecret(totpKey); err == nil && val != "" { - return true - } - } - if s.secretStore != nil && s.secretStore.IsUnlocked() { - if val, err := s.secretStore.GetSecret(totpKey); err == nil && val != "" { - return true - } - } - return false -} - -// DeleteTOTPSecret removes the TOTP secret for the given profile. -func (s *Service) DeleteTOTPSecret(profileID string) error { - totpKey := fmt.Sprintf("profile:%s:totp", profileID) - if s.keychainStore != nil && s.keychainStore.Available() { - _ = s.keychainStore.DeleteSecret(totpKey) - } - if s.secretStore != nil && s.secretStore.IsUnlocked() { - _ = s.secretStore.DeleteSecret(totpKey) - } - return nil -} - -// --------------------------------------------------------------------------- -// Known hosts -// --------------------------------------------------------------------------- - -// ListKnownHosts returns all stored host key entries. -func (s *Service) ListKnownHosts() ([]models.KnownHost, error) { - hosts, err := s.store.ListKnownHosts() - if err != nil { - return nil, err - } - result := make([]models.KnownHost, len(hosts)) - for i, h := range hosts { - result[i] = *h - } - return result, nil -} - -// DeleteKnownHost removes a known host entry by ID. -func (s *Service) DeleteKnownHost(id string) error { - return s.store.DeleteKnownHost(id) -} - -// --------------------------------------------------------------------------- -// Preferences -// --------------------------------------------------------------------------- - -// GetPreferences returns the current application preferences. -func (s *Service) GetPreferences() (models.AppPreferences, error) { - p := s.prefs.Get() - if p == nil { - return models.DefaultAppPreferences(), nil - } - return *p, nil -} - -// SavePreferences persists the given application preferences. -func (s *Service) SavePreferences(prefs models.AppPreferences) error { - return s.prefs.Save(&prefs) -} - -// --------------------------------------------------------------------------- -// Import / Export -// --------------------------------------------------------------------------- - -// ExportProfiles serialises all profiles to a JSON string. When includeSecrets -// is true, decrypted credentials are embedded (use with caution). -func (s *Service) ExportProfiles(includeSecrets bool) (string, error) { - profiles, err := s.store.ListProfiles() - if err != nil { - return "", fmt.Errorf("failed to list profiles for export: %w", err) - } - if !includeSecrets { - profiles = redactProfileSecrets(profiles) - } - - data, err := json.MarshalIndent(profiles, "", " ") - if err != nil { - return "", fmt.Errorf("failed to serialise profiles: %w", err) - } - - s.auditLog.LogExport(len(profiles), includeSecrets) - return string(data), nil -} - -// ImportProfiles deserialises profiles from a JSON string and persists them. -// The strategy parameter controls conflict resolution: -// - "skip" ignores profiles whose ID already exists -// - "overwrite" replaces existing profiles -// - "duplicate" always creates a new profile with a fresh ID -func (s *Service) ImportProfiles(jsonData string, strategy string) (int, error) { - if len(jsonData) > maxProfileImportJSONBytes { - return 0, fmt.Errorf("import data is too large (max %d bytes)", maxProfileImportJSONBytes) - } - var profiles []*models.Profile - if err := json.Unmarshal([]byte(jsonData), &profiles); err != nil { - return 0, fmt.Errorf("invalid JSON: %w", err) - } - - if err := validateImportStrategy(strategy); err != nil { - return 0, err - } - if err := validateImportProfiles(profiles); err != nil { - return 0, err - } - - imported := 0 - for _, p := range profiles { - switch strategy { - case "skip": - if existing, _ := s.store.GetProfile(p.ID); existing != nil { - continue - } - if err := s.store.CreateProfile(p); err != nil { - continue - } - imported++ - - case "overwrite": - p.UpdatedAt = time.Now() - if existing, _ := s.store.GetProfile(p.ID); existing != nil { - if err := s.store.UpdateProfile(p); err != nil { - continue - } - } else { - if err := s.store.CreateProfile(p); err != nil { - continue - } - } - imported++ - - case "duplicate": - p.ID = uuid.New().String() - now := time.Now() - p.CreatedAt = now - p.UpdatedAt = now - if err := s.store.CreateProfile(p); err != nil { - continue - } - imported++ - - default: - return 0, fmt.Errorf("unknown import strategy %q (expected skip, overwrite, or duplicate)", strategy) - } - } - - s.auditLog.LogImport(imported) - return imported, nil -} - -// --------------------------------------------------------------------------- -// Audit -// --------------------------------------------------------------------------- - -// ListAuditEntries returns the most recent audit entries. -func (s *Service) ListAuditEntries(limit int) ([]models.AuditEntry, error) { - entries, err := s.auditLog.ListEntries(limit) - if err != nil { - return nil, err - } - result := make([]models.AuditEntry, len(entries)) - for i, e := range entries { - result[i] = *e - } - return result, nil -} - -// --------------------------------------------------------------------------- -// Snippets (command library) -// --------------------------------------------------------------------------- - -// ListSnippets returns all saved command snippets. -func (s *Service) ListSnippets() ([]models.Snippet, error) { - snippets, err := s.store.ListSnippets() - if err != nil { - return nil, err - } - result := make([]models.Snippet, len(snippets)) - for i, sn := range snippets { - result[i] = *sn - } - return result, nil -} - -// CreateSnippet saves a new command snippet. -func (s *Service) CreateSnippet(snippet models.Snippet) (models.Snippet, error) { - if snippet.ID == "" { - snippet.ID = uuid.New().String() - } - now := time.Now() - snippet.CreatedAt = now - snippet.UpdatedAt = now - if snippet.Tags == nil { - snippet.Tags = []string{} - } - if err := s.store.CreateSnippet(&snippet); err != nil { - return models.Snippet{}, fmt.Errorf("failed to create snippet: %w", err) - } - return snippet, nil -} - -// UpdateSnippet updates an existing command snippet. -func (s *Service) UpdateSnippet(snippet models.Snippet) (models.Snippet, error) { - snippet.UpdatedAt = time.Now() - if err := s.store.UpdateSnippet(&snippet); err != nil { - return models.Snippet{}, fmt.Errorf("failed to update snippet: %w", err) - } - return snippet, nil -} - -// DeleteSnippet removes a command snippet by ID. -func (s *Service) DeleteSnippet(id string) error { - return s.store.DeleteSnippet(id) -} - -// --------------------------------------------------------------------------- -// Utility -// --------------------------------------------------------------------------- - -// SelectFile opens a native file dialog and returns the chosen path. -func (s *Service) SelectFile(title string, filters string) (string, error) { - if s.ctx == nil { - return "", fmt.Errorf("application context not set") - } - - opts := wailsRuntime.OpenDialogOptions{ - Title: title, - } - if filters != "" { - opts.Filters = []wailsRuntime.FileFilter{ - {DisplayName: "Files", Pattern: filters}, - } - } - - path, err := wailsRuntime.OpenFileDialog(s.ctx, opts) - if err != nil { - return "", fmt.Errorf("file dialog failed: %w", err) - } - return path, nil -} - -// SaveFile opens a native save dialog and returns the chosen path. -func (s *Service) SaveFile(title string, defaultName string) (string, error) { - if s.ctx == nil { - return "", fmt.Errorf("application context not set") - } - - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: title, - DefaultFilename: defaultName, - }) - if err != nil { - return "", fmt.Errorf("save dialog failed: %w", err) - } - return path, nil -} - -// FileExists returns true if the path exists and is not a directory. -func (s *Service) FileExists(path string) (bool, error) { - if path == "" { - return false, nil - } - info, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) { - return false, nil - } - return false, err - } - return !info.IsDir(), nil -} - -// FileIsPrivateKey returns true if the path appears to be an SSH private key. -func (s *Service) FileIsPrivateKey(path string) (bool, error) { - if path == "" { - return false, nil - } - return isLikelyPrivateKey(path), nil -} - -// GetAppInfo returns build and environment information. -func (s *Service) GetAppInfo() map[string]string { - return map[string]string{ - "version": appVersion, - "go": runtime.Version(), - "os": runtime.GOOS, - "arch": runtime.GOARCH, - "platform": runtime.GOOS + "/" + runtime.GOARCH, - } -} - -// RefreshGitHubRepos triggers an on-demand refresh of GitHub repositories for -// the given session. The updated list is also emitted as a Wails event. -func (s *Service) RefreshGitHubRepos(sessionID string) ([]models.GitHubRepo, error) { - return s.sessions.FetchGitHubRepos(sessionID) -} - -// DownloadGitHubRepoArchive downloads a repository zip archive to a local file. -func (s *Service) DownloadGitHubRepoArchive(sessionID string, fullName string, ref string) (string, error) { - if s.ctx == nil { - return "", fmt.Errorf("application context not set") - } - if fullName == "" { - return "", fmt.Errorf("repository name cannot be empty") - } - - parts := strings.Split(fullName, "/") - repoName := parts[len(parts)-1] - defaultName := repoName + ".zip" - if ref != "" { - safeRef := strings.ReplaceAll(ref, "/", "-") - defaultName = fmt.Sprintf("%s-%s.zip", repoName, safeRef) - } - - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Download Repository", - DefaultFilename: defaultName, - Filters: []wailsRuntime.FileFilter{ - {DisplayName: "Zip Archive (*.zip)", Pattern: "*.zip"}, - }, - }) - if err != nil { - return "", fmt.Errorf("save dialog failed: %w", err) - } - if path == "" { - return "", fmt.Errorf("download cancelled") - } - if filepath.Ext(path) != ".zip" { - path += ".zip" - } - - if err := s.sessions.DownloadGitHubArchive(sessionID, fullName, ref, path); err != nil { - return "", err - } - return path, nil -} - -// DownloadGitHubRepoArchiveToPath downloads a repository zip archive to an -// explicit local destination. Non-Wails hosts provide the save path through -// their own file picker. -func (s *Service) DownloadGitHubRepoArchiveToPath(sessionID string, fullName string, ref string, destPath string) (string, error) { - if strings.TrimSpace(fullName) == "" { - return "", fmt.Errorf("repository name cannot be empty") - } - destPath = strings.TrimSpace(destPath) - if destPath == "" { - return "", fmt.Errorf("destination path cannot be empty") - } - if filepath.Ext(destPath) != ".zip" { - destPath += ".zip" - } - if err := s.sessions.DownloadGitHubArchive(sessionID, fullName, ref, destPath); err != nil { - return "", err - } - return destPath, nil -} - -// --------------------------------------------------------------------------- -// VNC -// --------------------------------------------------------------------------- - -// GetVNCWebSocketURL returns the local WebSocket proxy URL for a VNC session. -// The frontend connects noVNC to this URL for display. -func (s *Service) GetVNCWebSocketURL(sessionID string) (string, error) { - return s.sessions.GetVNCWebSocketURL(sessionID) -} - -// --------------------------------------------------------------------------- -// Local shell -// --------------------------------------------------------------------------- - -// GetDefaultShell returns the system's default shell path. -func (s *Service) GetDefaultShell() string { - return localshell.DefaultShell() -} - -// BrowseForShell opens a file dialog to select a shell binary. -func (s *Service) BrowseForShell() (string, error) { - if s.ctx == nil { - return "", fmt.Errorf("application context not set") - } - path, err := wailsRuntime.OpenFileDialog(s.ctx, wailsRuntime.OpenDialogOptions{ - Title: "Select Shell Binary", - }) - if err != nil { - return "", fmt.Errorf("file dialog failed: %w", err) - } - return path, nil -} - -// --------------------------------------------------------------------------- -// Port forwarding -// --------------------------------------------------------------------------- - -// ListPortForwards returns the active port-forwarding tunnels for an SSH session. -func (s *Service) ListPortForwards(sessionID string) ([]models.PortForward, error) { - return s.sessions.ListPortForwards(sessionID) -} - -// StartPortForward activates a new forwarding tunnel on a live SSH session. -func (s *Service) StartPortForward(sessionID string, rule models.PortForward) error { - return s.sessions.StartPortForward(sessionID, rule) -} - -// StopPortForward terminates an active forwarding tunnel. -func (s *Service) StopPortForward(sessionID string, forwardID string) error { - return s.sessions.StopPortForward(sessionID, forwardID) -} - -// PingSession sends a keepalive request and returns the round-trip time in milliseconds. -func (s *Service) PingSession(sessionID string) (int64, error) { - return s.sessions.PingSession(sessionID) -} - -// --------------------------------------------------------------------------- -// Session recording -// --------------------------------------------------------------------------- - -// StartRecording begins recording terminal I/O for a session. -func (s *Service) StartRecording(sessionID string, cols int, rows int) (string, error) { - return s.sessions.StartRecording(sessionID, cols, rows) -} - -// StopRecording stops an active recording for a session. -func (s *Service) StopRecording(sessionID string) error { - return s.sessions.StopRecording(sessionID) -} - -// IsRecording reports whether a session is currently being recorded. -func (s *Service) IsRecording(sessionID string) bool { - return s.sessions.IsRecording(sessionID) -} - -// ListRecordings returns metadata for all saved recordings. -func (s *Service) ListRecordings() ([]map[string]interface{}, error) { - metas, err := s.sessions.ListRecordings() - if err != nil { - return nil, err - } - - result := make([]map[string]interface{}, len(metas)) - for i, m := range metas { - result[i] = map[string]interface{}{ - "id": m.ID, - "sessionId": m.SessionID, - "title": m.Title, - "width": m.Width, - "height": m.Height, - "duration": m.Duration, - "size": m.Size, - "createdAt": m.CreatedAt.Format(time.RFC3339), - "filePath": m.FilePath, - } - } - return result, nil -} - -// GetRecording returns the full recording data for playback. -func (s *Service) GetRecording(id string) (map[string]interface{}, error) { - hdr, events, err := s.sessions.GetRecording(id) - if err != nil { - return nil, err - } - - evtList := make([][]interface{}, len(events)) - for i, e := range events { - evtList[i] = []interface{}{e.Elapsed, string(e.Type), e.Data} - } - - return map[string]interface{}{ - "header": map[string]interface{}{ - "version": hdr.Version, - "width": hdr.Width, - "height": hdr.Height, - "timestamp": hdr.Timestamp, - "title": hdr.Title, - }, - "events": evtList, - }, nil -} - -// DeleteRecording removes a saved recording. -func (s *Service) DeleteRecording(id string) error { - return s.sessions.DeleteRecording(id) -} - -// GetRecordingStorageInfo returns aggregate recording storage usage and limits. -func (s *Service) GetRecordingStorageInfo() (map[string]interface{}, error) { - info, err := s.sessions.RecordingStorageInfo() - if err != nil { - return nil, err - } - result := map[string]interface{}{ - "count": info.Count, - "totalBytes": info.TotalBytes, - "maxRecordingBytes": info.MaxRecordingBytes, - "maxPlaybackBytes": info.MaxPlaybackBytes, - } - if info.OldestCreatedAt != nil { - result["oldestCreatedAt"] = info.OldestCreatedAt.Format(time.RFC3339) - } - if info.NewestCreatedAt != nil { - result["newestCreatedAt"] = info.NewestCreatedAt.Format(time.RFC3339) - } - return result, nil -} - -// CleanupRecordings removes recordings by age and total storage limit. -func (s *Service) CleanupRecordings(maxAgeDays int, maxTotalBytes int64) (map[string]interface{}, error) { - result, err := s.sessions.CleanupRecordings(maxAgeDays, maxTotalBytes) - if err != nil { - return nil, err - } - return map[string]interface{}{ - "deleted": result.Deleted, - "freedBytes": result.FreedBytes, - }, nil -} - -// --------------------------------------------------------------------------- -// Encrypted export / import -// --------------------------------------------------------------------------- - -// ExportProfilesEncrypted encrypts and exports selected profiles to a file. -// The profileIDs parameter selects which profiles to include; if empty, all -// profiles are exported. Returns the file path on success. -func (s *Service) ExportProfilesEncrypted(profileIDs []string, password string) (string, error) { - if s.ctx == nil { - return "", fmt.Errorf("application context not set") - } - - var profiles []models.Profile - if len(profileIDs) == 0 { - // Export all profiles. - all, err := s.store.ListProfiles() - if err != nil { - return "", fmt.Errorf("failed to list profiles: %w", err) - } - for _, p := range all { - profiles = append(profiles, *p) - } - } else { - for _, id := range profileIDs { - p, err := s.store.GetProfile(id) - if err != nil { - continue // skip missing profiles - } - profiles = append(profiles, *p) - } - } - - if len(profiles) == 0 { - return "", fmt.Errorf("no profiles to export") - } - - // Prompt for save location. - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Export Encrypted Profiles", - DefaultFilename: "link-profiles.link", - Filters: []wailsRuntime.FileFilter{ - {DisplayName: "Link Export (*.link)", Pattern: "*.link"}, - }, - }) - if err != nil { - return "", fmt.Errorf("save dialog failed: %w", err) - } - if path == "" { - return "", fmt.Errorf("export cancelled") - } - - // Ensure .link extension. - if filepath.Ext(path) != ".link" { - path += ".link" - } - - if err := exportimport.EncryptAndExport(profiles, password, path); err != nil { - return "", err - } - - s.auditLog.LogExport(len(profiles), false) - return path, nil -} - -// ImportProfilesEncrypted decrypts and imports profiles from an encrypted file. -// Returns the number of profiles successfully imported. -func (s *Service) ImportProfilesEncrypted(password string, strategy string) (int, error) { - if s.ctx == nil { - return 0, fmt.Errorf("application context not set") - } - - // Prompt for file selection. - path, err := wailsRuntime.OpenFileDialog(s.ctx, wailsRuntime.OpenDialogOptions{ - Title: "Import Encrypted Profiles", - Filters: []wailsRuntime.FileFilter{ - {DisplayName: "Link Export (*.link)", Pattern: "*.link"}, - }, - }) - if err != nil { - return 0, fmt.Errorf("open dialog failed: %w", err) - } - if path == "" { - return 0, fmt.Errorf("import cancelled") - } - - profiles, err := exportimport.DecryptAndImport(path, password) - if err != nil { - return 0, err - } - - if err := validateImportStrategy(strategy); err != nil { - return 0, err - } - profilePtrs := make([]*models.Profile, len(profiles)) - for i := range profiles { - profilePtrs[i] = &profiles[i] - } - if err := validateImportProfiles(profilePtrs); err != nil { - return 0, err - } - - // Import using the existing strategy logic. - imported := 0 - for _, p := range profiles { - switch strategy { - case "skip": - if existing, _ := s.store.GetProfile(p.ID); existing != nil { - continue - } - if err := s.store.CreateProfile(&p); err != nil { - continue - } - imported++ - - case "overwrite": - p.UpdatedAt = time.Now() - if existing, _ := s.store.GetProfile(p.ID); existing != nil { - if err := s.store.UpdateProfile(&p); err != nil { - continue - } - } else { - if err := s.store.CreateProfile(&p); err != nil { - continue - } - } - imported++ - - case "duplicate": - p.ID = uuid.New().String() - now := time.Now() - p.CreatedAt = now - p.UpdatedAt = now - if err := s.store.CreateProfile(&p); err != nil { - continue - } - imported++ - - default: - return 0, fmt.Errorf("unknown import strategy %q", strategy) - } - } - - s.auditLog.LogImport(imported) - return imported, nil -} - -// --------------------------------------------------------------------------- -// SFTP file browser -// --------------------------------------------------------------------------- - -// SFTPList lists the contents of a remote directory. -func (s *Service) SFTPList(sessionID string, remotePath string) ([]map[string]interface{}, error) { - entries, err := s.sessions.SFTPList(sessionID, remotePath) - if err != nil { - return nil, err - } - result := make([]map[string]interface{}, len(entries)) - for i, e := range entries { - result[i] = map[string]interface{}{ - "name": e.Name, - "path": e.Path, - "size": e.Size, - "isDir": e.IsDir, - "mode": e.Mode, - "modifiedAt": e.ModifiedAt, - } - } - return result, nil -} - -// SFTPMkdir creates a remote directory (including parents). -func (s *Service) SFTPMkdir(sessionID string, remotePath string) error { - return s.sessions.SFTPMkdir(sessionID, remotePath) -} - -// SFTPStat returns information about a remote path. -func (s *Service) SFTPStat(sessionID string, remotePath string) (map[string]interface{}, error) { - entry, err := s.sessions.SFTPStat(sessionID, remotePath) - if err != nil { - return nil, err - } - return map[string]interface{}{ - "name": entry.Name, - "path": entry.Path, - "size": entry.Size, - "isDir": entry.IsDir, - "mode": entry.Mode, - "modifiedAt": entry.ModifiedAt, - }, nil -} - -// SFTPRemove deletes a remote file or empty directory. -func (s *Service) SFTPRemove(sessionID string, remotePath string) error { - return s.sessions.SFTPRemove(sessionID, remotePath) -} - -// SFTPRename moves or renames a remote file/directory. -func (s *Service) SFTPRename(sessionID string, oldPath string, newPath string) error { - return s.sessions.SFTPRename(sessionID, oldPath, newPath) -} - -// SFTPDownload downloads a remote file. Opens a native save dialog for the -// local destination. -func (s *Service) SFTPDownload(sessionID string, remotePath string) error { - if s.ctx == nil { - return fmt.Errorf("application context not set") - } - - localPath, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Save File", - DefaultFilename: filepath.Base(remotePath), - }) - if err != nil { - return fmt.Errorf("save dialog failed: %w", err) - } - if localPath == "" { - return fmt.Errorf("download cancelled") - } - - return s.sessions.SFTPDownload(sessionID, remotePath, localPath) -} - -// SFTPDownloadToPath downloads a remote file to an explicit local path. This -// is used by non-Wails hosts, where the UI owns the save dialog. -func (s *Service) SFTPDownloadToPath(sessionID string, remotePath string, localPath string) error { - if strings.TrimSpace(remotePath) == "" { - return fmt.Errorf("remote path cannot be empty") - } - localPath = strings.TrimSpace(localPath) - if localPath == "" { - return fmt.Errorf("local path cannot be empty") - } - return s.sessions.SFTPDownload(sessionID, remotePath, localPath) -} - -// SFTPUpload uploads a local file to a remote directory. Opens a native file -// dialog to select the local file. -func (s *Service) SFTPUpload(sessionID string, remotePath string) error { - if s.ctx == nil { - return fmt.Errorf("application context not set") - } - - localPath, err := wailsRuntime.OpenFileDialog(s.ctx, wailsRuntime.OpenDialogOptions{ - Title: "Select File to Upload", - }) - if err != nil { - return fmt.Errorf("open dialog failed: %w", err) - } - if localPath == "" { - return fmt.Errorf("upload cancelled") - } - - destPath := remotePath + "/" + filepath.Base(localPath) - return s.sessions.SFTPUpload(sessionID, localPath, destPath) -} - -// SFTPUploadPath uploads an explicit local file path to a remote directory. -// The remote filename is derived from the local basename and sanitised through -// the same upload-name validation used by browser uploads. -func (s *Service) SFTPUploadPath(sessionID string, remoteDir string, localPath string) error { - localPath = strings.TrimSpace(localPath) - if localPath == "" { - return fmt.Errorf("local path cannot be empty") - } - info, err := os.Stat(localPath) - if err != nil { - return fmt.Errorf("reading local file metadata: %w", err) - } - if info.IsDir() { - return fmt.Errorf("local path is a directory") - } - safeName, err := safeSFTPUploadName(filepath.Base(localPath)) - if err != nil { - return err - } - destPath := path.Join(remoteDir, safeName) - return s.sessions.SFTPUpload(sessionID, localPath, destPath) -} - -// SFTPUploadData uploads raw file data to a remote directory. -// Data is expected as base64-encoded bytes from the frontend. -func (s *Service) SFTPUploadData(sessionID string, remoteDir string, fileName string, base64Data string) error { - if base64Data == "" { - return fmt.Errorf("empty upload payload") - } - if base64.StdEncoding.DecodedLen(len(base64Data)) > maxInlineSFTPUploadBytes { - return fmt.Errorf("upload payload is too large (max %d bytes)", maxInlineSFTPUploadBytes) - } - - data, err := base64.StdEncoding.DecodeString(base64Data) - if err != nil { - return fmt.Errorf("failed to decode upload data: %w", err) - } - if len(data) > maxInlineSFTPUploadBytes { - return fmt.Errorf("upload payload is too large (max %d bytes)", maxInlineSFTPUploadBytes) - } - - safeName, err := safeSFTPUploadName(fileName) - if err != nil { - return err - } - - destPath := path.Join(remoteDir, safeName) - return s.sessions.SFTPUploadBytes(sessionID, destPath, data) -} - -// SFTPUploadStart opens a remote SFTP file for chunked browser uploads. -func (s *Service) SFTPUploadStart(sessionID string, remoteDir string, fileName string, totalBytes int64) (string, error) { - if s.sessions == nil { - return "", fmt.Errorf("sessions service is not configured") - } - if totalBytes < 0 { - return "", fmt.Errorf("upload size must not be negative") - } - if totalBytes > maxStreamingSFTPUploadSize { - return "", fmt.Errorf("upload is too large (max %d bytes)", maxStreamingSFTPUploadSize) - } - if err := s.checkSFTPUploadCapacity(); err != nil { - return "", err - } - - safeName, err := safeSFTPUploadName(fileName) - if err != nil { - return "", err - } - remotePath := path.Join(remoteDir, safeName) - writer, err := s.sessions.SFTPOpenUpload(sessionID, remotePath) - if err != nil { - return "", err - } - - uploadID := uuid.New().String() - s.uploadMu.Lock() - if len(s.uploads) >= maxConcurrentSFTPUploads { - s.uploadMu.Unlock() - _ = writer.Close() - if s.sessions != nil { - _ = s.sessions.SFTPRemove(sessionID, remotePath) - } - return "", fmt.Errorf("too many active uploads (max %d)", maxConcurrentSFTPUploads) - } - s.uploads[uploadID] = &sftpUpload{ - writer: writer, - sessionID: sessionID, - remotePath: remotePath, - } - s.uploadMu.Unlock() - return uploadID, nil -} - -// SFTPUploadChunk writes a base64-encoded chunk to an active browser upload. -func (s *Service) SFTPUploadChunk(uploadID string, base64Chunk string) error { - if base64Chunk == "" { - return fmt.Errorf("empty upload chunk") - } - if base64.StdEncoding.DecodedLen(len(base64Chunk)) > maxSFTPUploadChunkBytes { - return fmt.Errorf("upload chunk is too large (max %d bytes)", maxSFTPUploadChunkBytes) - } - data, err := base64.StdEncoding.DecodeString(base64Chunk) - if err != nil { - return fmt.Errorf("failed to decode upload chunk: %w", err) - } - if len(data) > maxSFTPUploadChunkBytes { - return fmt.Errorf("upload chunk is too large (max %d bytes)", maxSFTPUploadChunkBytes) - } - - upload, err := s.lookupSFTPUpload(uploadID) - if err != nil { - return err - } - upload.mu.Lock() - defer upload.mu.Unlock() - if upload.written+int64(len(data)) > maxStreamingSFTPUploadSize { - return fmt.Errorf("upload is too large (max %d bytes)", maxStreamingSFTPUploadSize) - } - n, err := upload.writer.Write(data) - upload.written += int64(n) - if err != nil { - s.removeSFTPUpload(uploadID) - _ = upload.writer.Close() - if s.sessions != nil { - _ = s.sessions.SFTPRemove(upload.sessionID, upload.remotePath) - } - return fmt.Errorf("failed to write upload chunk: %w", err) - } - if n != len(data) { - s.removeSFTPUpload(uploadID) - _ = upload.writer.Close() - if s.sessions != nil { - _ = s.sessions.SFTPRemove(upload.sessionID, upload.remotePath) - } - return io.ErrShortWrite - } - return nil -} - -// SFTPUploadFinish closes an active browser upload. -func (s *Service) SFTPUploadFinish(uploadID string) error { - upload, err := s.takeSFTPUpload(uploadID) - if err != nil { - return err - } - upload.mu.Lock() - defer upload.mu.Unlock() - return upload.writer.Close() -} - -// SFTPUploadCancel closes and removes a partially uploaded remote file. -func (s *Service) SFTPUploadCancel(uploadID string) error { - upload, err := s.takeSFTPUpload(uploadID) - if err != nil { - return err - } - upload.mu.Lock() - closeErr := upload.writer.Close() - upload.mu.Unlock() - if s.sessions != nil { - _ = s.sessions.SFTPRemove(upload.sessionID, upload.remotePath) - } - return closeErr -} - -// SFTPPwd returns the current working directory on the remote host. -func (s *Service) SFTPPwd(sessionID string) (string, error) { - return s.sessions.SFTPPwd(sessionID) -} - -// GetSessionCwd returns a best-effort working directory for SSH sessions. -func (s *Service) GetSessionCwd(sessionID string) (string, error) { - return s.sessions.GetSessionCwd(sessionID) -} - -// --------------------------------------------------------------------------- -// OpenSSH config import -// --------------------------------------------------------------------------- - -// ImportSSHConfig parses the given SSH config file (or the default -// ~/.ssh/config if path is empty) and imports the Host blocks as profiles. -func (s *Service) ImportSSHConfig(configPath string, folder string) (int, error) { - if configPath == "" { - configPath = sshconfig.DefaultConfigPath() - } - - blocks, err := sshconfig.ParseFile(configPath) - if err != nil { - return 0, err - } - - profiles := sshconfig.ToProfiles(blocks, folder) - imported := 0 - for _, p := range profiles { - if err := s.store.CreateProfile(&p); err != nil { - continue // skip duplicates or errors - } - imported++ - } - - s.auditLog.LogImport(imported) - return imported, nil -} - -// --------------------------------------------------------------------------- -// Automatic update checking -// --------------------------------------------------------------------------- - -// CheckForUpdates queries GitHub Releases for a newer version. -func (s *Service) CheckForUpdates() (map[string]interface{}, error) { - ctx := context.Background() - var info *updater.UpdateInfo - var err error - - if appUpdateURL != "" { - url := strings.ReplaceAll(appUpdateURL, "{platform}", runtime.GOOS) - url = strings.ReplaceAll(url, "{os}", runtime.GOOS) - url = strings.ReplaceAll(url, "{arch}", runtime.GOARCH) - checker := updater.NewURLChecker(url, appVersion) - info, err = checker.Check(ctx) - } else { - checker := updater.NewChecker(appVersion) - info, err = checker.Check(ctx) - } - if err != nil { - return nil, err - } - - return map[string]interface{}{ - "available": info.Available, - "currentVersion": info.CurrentVersion, - "latestVersion": info.LatestVersion, - "releaseURL": info.ReleaseURL, - "releaseName": info.ReleaseName, - "releaseNotes": info.ReleaseNotes, - "publishedAt": info.PublishedAt, - }, nil -} - -// TestConnection performs a basic TCP reachability check to the given host and port. -// This verifies the network path without establishing a full protocol session. -func (s *Service) TestConnection(host string, port int) (map[string]interface{}, error) { - if host == "" { - return nil, fmt.Errorf("host is required") - } - if port <= 0 || port > 65535 { - return nil, fmt.Errorf("port must be between 1 and 65535") - } - - addr := net.JoinHostPort(host, strconv.Itoa(port)) - start := time.Now() - - conn, err := net.DialTimeout("tcp", addr, 10*time.Second) - elapsed := time.Since(start) - - if err != nil { - return nil, fmt.Errorf("connection to %s failed after %dms: %w", addr, elapsed.Milliseconds(), err) - } - conn.Close() - - return map[string]interface{}{ - "success": true, - "host": host, - "port": port, - "latencyMs": elapsed.Milliseconds(), - "remoteAddr": addr, - }, nil -} - -// --------------------------------------------------------------------------- -// Internal helpers -// --------------------------------------------------------------------------- - -// promptSecret emits a Wails event to request a secret from the user, then -// blocks until the frontend responds via the session:secret:response event. -// This bridges the synchronous Go SSH auth callback with the asynchronous -// frontend prompt UI. -func (s *Service) promptSecret(prompt string) (string, error) { - if s.ctx == nil && s.eventSink == nil { - return "", fmt.Errorf("application context not available") - } - - // Generate a unique request ID so we can correlate the response. - requestID := uuid.New().String() - ch := make(chan secretResponse, 1) - - // Register a one-shot listener for the response. - s.secretMu.Lock() - if s.secretChans == nil { - s.secretChans = make(map[string]chan secretResponse) - } - s.secretChans[requestID] = ch - s.secretMu.Unlock() - - // Emit the prompt event to the frontend. - s.emitEvent("session:secret:prompt", map[string]interface{}{ - "requestID": requestID, - "message": prompt, - "title": "Credentials Required", - }) - - // Block until we receive a response (with a generous timeout). - select { - case resp := <-ch: - s.secretMu.Lock() - delete(s.secretChans, requestID) - s.secretMu.Unlock() - if resp.cancelled { - return "", fmt.Errorf("user cancelled secret prompt") - } - return resp.value, nil - case <-time.After(5 * time.Minute): - s.secretMu.Lock() - delete(s.secretChans, requestID) - s.secretMu.Unlock() - return "", fmt.Errorf("secret prompt timed out") - } -} - -// secretResponse carries the result of a frontend secret prompt. -type secretResponse struct { - value string - cancelled bool -} - -// RegisterSecretResponseListener sets up the Wails event listener that receives -// secrets entered by the user. Must be called after SetContext. -func (s *Service) RegisterSecretResponseListener() { - if s.ctx == nil { - return - } - - s.secretMu.Lock() - if s.secretListenerRegistered { - s.secretMu.Unlock() - return - } - s.secretListenerRegistered = true - s.secretMu.Unlock() - - cancel := wailsRuntime.EventsOn(s.ctx, "session:secret:response", func(optionalData ...interface{}) { - if len(optionalData) == 0 { - return - } - data, ok := optionalData[0].(map[string]interface{}) - if !ok { - return - } - requestID, _ := data["requestID"].(string) - value, _ := data["value"].(string) - cancelled, _ := data["cancelled"].(bool) - - s.secretMu.Lock() - ch, exists := s.secretChans[requestID] - s.secretMu.Unlock() - - if exists { - select { - case ch <- secretResponse{value: value, cancelled: cancelled}: - default: - } - } - }) - s.secretMu.Lock() - s.secretListenerCancel = cancel - s.secretMu.Unlock() -} - -// RespondSecret supplies a credential prompt response for non-Wails hosts. -func (s *Service) RespondSecret(requestID string, value string, cancelled bool) { - s.secretMu.Lock() - ch, exists := s.secretChans[requestID] - s.secretMu.Unlock() - - if exists { - select { - case ch <- secretResponse{value: value, cancelled: cancelled}: - default: - } - } -} - -func (s *Service) emitEvent(eventName string, data interface{}) { - if s.eventSink != nil { - s.eventSink(eventName, data) - } - if s.ctx != nil { - wailsRuntime.EventsEmit(s.ctx, eventName, data) - } -} - -// derefProfiles converts a slice of profile pointers to values. -func derefProfiles(ptrs []*models.Profile) []models.Profile { - result := make([]models.Profile, len(ptrs)) - for i, p := range ptrs { - result[i] = *p - } - return result -} - -// --------------------------------------------------------------------------- -// SSH Key Management -// --------------------------------------------------------------------------- - -// SSHKeyInfo describes a key found in ~/.ssh/. -type SSHKeyInfo struct { - Name string `json:"name"` - Type string `json:"type"` - Bits string `json:"bits"` - PublicKey string `json:"publicKey"` - PrivateKey string `json:"privateKeyPath"` -} - -// ListSSHKeyPairs returns detailed info about SSH key pairs in ~/.ssh/. -func (s *Service) ListSSHKeyPairs() ([]SSHKeyInfo, error) { - home, err := os.UserHomeDir() - if err != nil { - return nil, fmt.Errorf("unable to determine home directory: %w", err) - } - sshDir := filepath.Join(home, ".ssh") - - entries, err := os.ReadDir(sshDir) - if err != nil { - if os.IsNotExist(err) { - return []SSHKeyInfo{}, nil - } - return nil, fmt.Errorf("unable to read .ssh directory: %w", err) - } - - var keys []SSHKeyInfo - seen := make(map[string]bool) - - for _, entry := range entries { - if entry.IsDir() { - continue - } - name := entry.Name() - // Look for .pub files and match them with private keys - if !strings.HasSuffix(name, ".pub") { - continue - } - baseName := strings.TrimSuffix(name, ".pub") - privPath := filepath.Join(sshDir, baseName) - - // Verify private key exists - if _, err := os.Stat(privPath); err != nil { - continue - } - - if seen[baseName] { - continue - } - seen[baseName] = true - - // Read public key content - pubBytes, err := readSSHPublicKeyFile(filepath.Join(sshDir, name)) - if err != nil { - continue - } - pubContent := strings.TrimSpace(string(pubBytes)) - - // Determine key type from public key content - keyType := "unknown" - parts := strings.Fields(pubContent) - if len(parts) >= 1 { - switch { - case strings.Contains(parts[0], "ed25519"): - keyType = "ed25519" - case strings.Contains(parts[0], "ecdsa"): - keyType = "ecdsa" - case strings.Contains(parts[0], "rsa"): - keyType = "rsa" - case strings.Contains(parts[0], "dsa"): - keyType = "dsa" - } - } - - keys = append(keys, SSHKeyInfo{ - Name: baseName, - Type: keyType, - PublicKey: pubContent, - PrivateKey: privPath, - }) - } - - if keys == nil { - keys = []SSHKeyInfo{} - } - return keys, nil -} - -// GenerateSSHKey generates a new SSH key pair. -func (s *Service) GenerateSSHKey(name string, keyType string, bits int, passphrase string) error { - keyPath, _, err := sshKeyPairPaths(name) - if err != nil { - return err - } - keyType, bits, err = validateSSHKeyGenerationOptions(keyType, bits) - if err != nil { - return err - } - sshDir := filepath.Dir(keyPath) - - // Ensure .ssh directory exists - if err := os.MkdirAll(sshDir, 0700); err != nil { - return fmt.Errorf("unable to create .ssh directory: %w", err) - } - - // Check if key already exists - if _, err := os.Stat(keyPath); err == nil { - return fmt.Errorf("key %q already exists", name) - } - - // Build ssh-keygen arguments - args := []string{"-t", keyType, "-f", keyPath, "-C", name + "@alchementary-link"} - if passphrase != "" { - args = append(args, "-N", passphrase) - } else { - args = append(args, "-N", "") - } - if keyType == "rsa" && bits > 0 { - args = append(args, "-b", fmt.Sprintf("%d", bits)) - } - - // #nosec G204 — arguments are sanitised above - ctx, cancel := context.WithTimeout(context.Background(), sshKeygenTimeout) - defer cancel() - cmd := exec.CommandContext(ctx, "ssh-keygen", args...) - var stderr bytes.Buffer - cmd.Stderr = &stderr - if err := cmd.Run(); err != nil { - if ctx.Err() == context.DeadlineExceeded { - return fmt.Errorf("ssh-keygen timed out after %s", sshKeygenTimeout) - } - return fmt.Errorf("ssh-keygen failed: %s", stderr.String()) - } - - return nil -} - -// GetSSHPublicKey returns the content of a public key file. -func (s *Service) GetSSHPublicKey(name string) (string, error) { - _, pubPath, err := sshKeyPairPaths(name) - if err != nil { - return "", err - } - data, err := readSSHPublicKeyFile(pubPath) - if err != nil { - return "", fmt.Errorf("unable to read public key: %w", err) - } - return strings.TrimSpace(string(data)), nil -} - -// DeleteSSHKey removes an SSH key pair. -func (s *Service) DeleteSSHKey(name string) error { - privPath, pubPath, err := sshKeyPairPaths(name) - if err != nil { - return err - } - - var errs []string - if err := os.Remove(privPath); err != nil && !os.IsNotExist(err) { - errs = append(errs, "private key: "+err.Error()) - } - if err := os.Remove(pubPath); err != nil && !os.IsNotExist(err) { - errs = append(errs, "public key: "+err.Error()) - } - if len(errs) > 0 { - return fmt.Errorf("failed to delete: %s", strings.Join(errs, "; ")) - } - return nil -} - -func sshKeyPairPaths(name string) (string, string, error) { - keyName, err := validateSSHKeyName(name) - if err != nil { - return "", "", err - } - home, err := os.UserHomeDir() - if err != nil { - return "", "", fmt.Errorf("unable to determine home directory: %w", err) - } - sshDir := filepath.Join(home, ".ssh") - privPath := filepath.Join(sshDir, keyName) - pubPath := privPath + ".pub" - return privPath, pubPath, nil -} - -func readSSHPublicKeyFile(path string) ([]byte, error) { - info, err := os.Stat(path) - if err != nil { - return nil, err - } - if info.IsDir() { - return nil, fmt.Errorf("public key path is a directory") - } - if info.Size() > maxSSHPublicKeyBytes { - return nil, fmt.Errorf("public key file is too large: %d bytes exceeds %d bytes", info.Size(), maxSSHPublicKeyBytes) - } - return os.ReadFile(path) -} - -func validateSSHKeyName(name string) (string, error) { - name = strings.TrimSpace(name) - if name == "" { - return "", fmt.Errorf("key name is required") - } - for _, ch := range name { - if !((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '-' || ch == '_') { - return "", fmt.Errorf("key name contains invalid characters (use alphanumeric, hyphens, underscores)") - } - } - return name, nil -} - -func safeSFTPUploadName(fileName string) (string, error) { - name := strings.TrimSpace(fileName) - if name == "" { - return "", fmt.Errorf("invalid file name") - } - name = strings.ReplaceAll(name, "\\", "/") - safeName := path.Base(name) - if safeName == "" || safeName == "." || safeName == ".." || safeName == "/" || strings.ContainsRune(safeName, 0) { - return "", fmt.Errorf("invalid file name") - } - return safeName, nil -} - -func validateSSHKeyGenerationOptions(keyType string, bits int) (string, int, error) { - keyType = strings.ToLower(strings.TrimSpace(keyType)) - if keyType == "" { - keyType = "ed25519" - } - switch keyType { - case "ed25519", "ecdsa": - return keyType, bits, nil - case "rsa": - if bits == 0 { - bits = 4096 - } - switch bits { - case 2048, 3072, 4096: - return keyType, bits, nil - default: - return "", 0, fmt.Errorf("unsupported RSA key size %d (use 2048, 3072, or 4096)", bits) - } - default: - return "", 0, fmt.Errorf("unsupported SSH key type %q", keyType) - } -} - -func (s *Service) lookupSFTPUpload(uploadID string) (*sftpUpload, error) { - s.uploadMu.Lock() - defer s.uploadMu.Unlock() - upload := s.uploads[uploadID] - if upload == nil { - return nil, fmt.Errorf("upload session not found") - } - return upload, nil -} - -func (s *Service) takeSFTPUpload(uploadID string) (*sftpUpload, error) { - s.uploadMu.Lock() - defer s.uploadMu.Unlock() - upload := s.uploads[uploadID] - if upload == nil { - return nil, fmt.Errorf("upload session not found") - } - delete(s.uploads, uploadID) - return upload, nil -} - -func (s *Service) removeSFTPUpload(uploadID string) { - s.uploadMu.Lock() - defer s.uploadMu.Unlock() - delete(s.uploads, uploadID) -} - -func (s *Service) checkSFTPUploadCapacity() error { - s.uploadMu.Lock() - defer s.uploadMu.Unlock() - if len(s.uploads) >= maxConcurrentSFTPUploads { - return fmt.Errorf("too many active uploads (max %d)", maxConcurrentSFTPUploads) - } - return nil -} - -// cancelSFTPUploads closes and removes active uploads. An empty session ID -// cancels every upload; otherwise only uploads owned by that session are -// affected. -func (s *Service) cancelSFTPUploads(sessionID string) { - s.uploadMu.Lock() - uploads := make([]*sftpUpload, 0) - for uploadID, upload := range s.uploads { - if sessionID == "" || upload.sessionID == sessionID { - delete(s.uploads, uploadID) - uploads = append(uploads, upload) - } - } - s.uploadMu.Unlock() - - for _, upload := range uploads { - upload.mu.Lock() - _ = upload.writer.Close() - upload.mu.Unlock() - if s.sessions != nil { - _ = s.sessions.SFTPRemove(upload.sessionID, upload.remotePath) - } - } -} - -func validateImportStrategy(strategy string) error { - switch strategy { - case "skip", "overwrite", "duplicate": - return nil - default: - return fmt.Errorf("unknown import strategy %q (expected skip, overwrite, or duplicate)", strategy) - } -} - -func validateImportProfiles(profiles []*models.Profile) error { - for i, p := range profiles { - if p == nil { - return fmt.Errorf("profile import entry %d is null", i) - } - normaliseProfileForSave(p) - if err := validation.ValidateProfile(p); err != nil { - return fmt.Errorf("profile import entry %d is invalid: %w", i, err) - } - } - return nil -} - -func normaliseProfileForSave(profile *models.Profile) { - models.NormalizeProfileDefaults(profile) -} - -func redactProfileSecrets(profiles []*models.Profile) []*models.Profile { - redacted := make([]*models.Profile, len(profiles)) - for i, profile := range profiles { - cp := *profile - if cp.SSH != nil { - sshCfg := *cp.SSH - sshCfg.ProxyPassword = "" - sshCfg.TOTPSecret = "" - cp.SSH = &sshCfg - } - redacted[i] = &cp - } - return redacted -} diff --git a/internal/api/api_test.go b/internal/api/api_test.go deleted file mode 100644 index 6871682..0000000 --- a/internal/api/api_test.go +++ /dev/null @@ -1,387 +0,0 @@ -package api - -import ( - "encoding/base64" - "os" - "path/filepath" - "strings" - "testing" - - "github.com/andydixon/link/internal/audit" - "github.com/andydixon/link/internal/leviathan/document" - "github.com/andydixon/link/internal/models" - "github.com/andydixon/link/internal/storage" -) - -type closeTrackingWriter struct { - closed int -} - -func (w *closeTrackingWriter) Write(p []byte) (int, error) { - return len(p), nil -} - -func (w *closeTrackingWriter) Close() error { - w.closed++ - return nil -} - -func TestSSHKeyPairPathsAcceptsSafeName(t *testing.T) { - home := t.TempDir() - t.Setenv("HOME", home) - - privPath, pubPath, err := sshKeyPairPaths("deploy_key-01") - if err != nil { - t.Fatalf("sshKeyPairPaths() error = %v", err) - } - - wantPriv := filepath.Join(home, ".ssh", "deploy_key-01") - if privPath != wantPriv { - t.Fatalf("private path = %q, want %q", privPath, wantPriv) - } - if pubPath != wantPriv+".pub" { - t.Fatalf("public path = %q, want %q", pubPath, wantPriv+".pub") - } -} - -func TestSSHKeyPairPathsRejectsTraversal(t *testing.T) { - home := t.TempDir() - t.Setenv("HOME", home) - - _, _, err := sshKeyPairPaths("../outside") - if err == nil { - t.Fatal("expected traversal key name to be rejected") - } - if !strings.Contains(err.Error(), "invalid characters") { - t.Fatalf("error = %q, want invalid characters", err.Error()) - } -} - -func TestSSHKeyPairPathsRejectsNestedPath(t *testing.T) { - home := t.TempDir() - t.Setenv("HOME", home) - - _, _, err := sshKeyPairPaths("nested/key") - if err == nil { - t.Fatal("expected nested key path to be rejected") - } -} - -func TestRedactProfileSecretsClearsInlineSSHSecrets(t *testing.T) { - profile := models.NewProfile(models.ProtocolSSH) - profile.SSH.ProxyPassword = "proxy-secret" - profile.SSH.TOTPSecret = "totp-secret" - - got := redactProfileSecrets([]*models.Profile{&profile}) - if got[0].SSH.ProxyPassword != "" { - t.Fatalf("ProxyPassword = %q, want empty", got[0].SSH.ProxyPassword) - } - if got[0].SSH.TOTPSecret != "" { - t.Fatalf("TOTPSecret = %q, want empty", got[0].SSH.TOTPSecret) - } - if profile.SSH.ProxyPassword == "" || profile.SSH.TOTPSecret == "" { - t.Fatal("redaction mutated the original profile") - } -} - -func TestCreateProfileRejectsUnsafeGitHubBaseURL(t *testing.T) { - svc := newTestService(t) - profile := models.NewProfile(models.ProtocolGitHub) - profile.DisplayName = "Unsafe GitHub" - profile.GitHub.BaseURL = "http://evil.example/api/v3" - - if _, err := svc.CreateProfile(profile); err == nil { - t.Fatal("expected unsafe GitHub base URL to be rejected") - } -} - -func TestImportProfilesRejectsNullEntry(t *testing.T) { - svc := newTestService(t) - _, err := svc.ImportProfiles(`[null]`, "skip") - if err == nil { - t.Fatal("expected null import entry to be rejected") - } - if !strings.Contains(err.Error(), "entry 0 is null") { - t.Fatalf("error = %q, want null entry detail", err.Error()) - } -} - -func TestImportProfilesRejectsOversizedJSON(t *testing.T) { - svc := newTestService(t) - oversized := strings.Repeat(" ", maxProfileImportJSONBytes+1) - _, err := svc.ImportProfiles(oversized, "skip") - if err == nil { - t.Fatal("expected oversized import JSON to be rejected") - } -} - -func TestSFTPUploadDataRejectsOversizedPayloadBeforeSessionLookup(t *testing.T) { - svc := newTestService(t) - payload := base64.StdEncoding.EncodeToString(make([]byte, maxInlineSFTPUploadBytes+1)) - - err := svc.SFTPUploadData("missing-session", "/", "large.bin", payload) - if err == nil { - t.Fatal("expected oversized upload payload to be rejected") - } - if !strings.Contains(err.Error(), "too large") { - t.Fatalf("error = %q, want too large", err.Error()) - } -} - -func TestSFTPUploadChunkRejectsOversizedChunkBeforeUploadLookup(t *testing.T) { - svc := newTestService(t) - payload := base64.StdEncoding.EncodeToString(make([]byte, maxSFTPUploadChunkBytes+1)) - - err := svc.SFTPUploadChunk("missing-upload", payload) - if err == nil { - t.Fatal("expected oversized upload chunk to be rejected") - } - if !strings.Contains(err.Error(), "chunk is too large") { - t.Fatalf("error = %q, want chunk is too large", err.Error()) - } -} - -func TestCancelSFTPUploadsClosesOnlyMatchingSession(t *testing.T) { - first := &closeTrackingWriter{} - second := &closeTrackingWriter{} - svc := &Service{ - uploads: map[string]*sftpUpload{ - "first": {writer: first, sessionID: "session-1", remotePath: "/first"}, - "second": {writer: second, sessionID: "session-2", remotePath: "/second"}, - }, - } - - svc.cancelSFTPUploads("session-1") - - if first.closed != 1 { - t.Fatalf("matching writer closed %d times, want 1", first.closed) - } - if second.closed != 0 { - t.Fatalf("unrelated writer closed %d times, want 0", second.closed) - } - if len(svc.uploads) != 1 || svc.uploads["second"] == nil { - t.Fatalf("remaining uploads = %#v, want only session-2", svc.uploads) - } -} - -func TestSFTPUploadCapacityIsBounded(t *testing.T) { - svc := &Service{uploads: make(map[string]*sftpUpload)} - for i := 0; i < maxConcurrentSFTPUploads; i++ { - svc.uploads[string(rune('a'+i))] = &sftpUpload{writer: &closeTrackingWriter{}} - } - if err := svc.checkSFTPUploadCapacity(); err == nil { - t.Fatal("expected active upload limit to be enforced") - } -} - -func TestServiceShutdownReleasesUploadsAndSecretWaiters(t *testing.T) { - writer := &closeTrackingWriter{} - waiter := make(chan secretResponse, 1) - svc := &Service{ - uploads: map[string]*sftpUpload{"upload": {writer: writer}}, - secretChans: map[string]chan secretResponse{"prompt": waiter}, - } - - svc.Shutdown() - - if writer.closed != 1 { - t.Fatalf("writer closed %d times, want 1", writer.closed) - } - if len(svc.uploads) != 0 { - t.Fatalf("active uploads = %d, want 0", len(svc.uploads)) - } - select { - case response := <-waiter: - if !response.cancelled { - t.Fatal("shutdown response was not cancelled") - } - default: - t.Fatal("shutdown did not release secret prompt waiter") - } -} - -func TestSafeSFTPUploadNamePreservesHTMLCharacters(t *testing.T) { - got, err := safeSFTPUploadName(`../report .txt`) - if err != nil { - t.Fatalf("safeSFTPUploadName() error = %v", err) - } - if got != "report .txt" { - t.Fatalf("safe name = %q, want HTML characters preserved in base name", got) - } -} - -func TestSafeSFTPUploadNameRejectsParentDirectory(t *testing.T) { - for _, name := range []string{"..", "folder/..", `folder\..`} { - t.Run(name, func(t *testing.T) { - if _, err := safeSFTPUploadName(name); err == nil { - t.Fatal("expected parent directory upload name to be rejected") - } - }) - } -} - -func TestValidateSSHKeyGenerationOptions(t *testing.T) { - keyType, bits, err := validateSSHKeyGenerationOptions("", 0) - if err != nil { - t.Fatalf("default options error = %v", err) - } - if keyType != "ed25519" { - t.Fatalf("default key type = %q, want ed25519", keyType) - } - if bits != 0 { - t.Fatalf("default bits = %d, want 0", bits) - } - - keyType, bits, err = validateSSHKeyGenerationOptions("rsa", 0) - if err != nil { - t.Fatalf("rsa default options error = %v", err) - } - if keyType != "rsa" || bits != 4096 { - t.Fatalf("rsa defaults = %q/%d, want rsa/4096", keyType, bits) - } - - if _, _, err := validateSSHKeyGenerationOptions("rsa", 999999); err == nil { - t.Fatal("expected oversized RSA key size to be rejected") - } - if _, _, err := validateSSHKeyGenerationOptions("dsa", 0); err == nil { - t.Fatal("expected unsupported key type to be rejected") - } -} - -func TestImportProfilesNormalizesSparseRAWProfile(t *testing.T) { - svc := newTestService(t) - data := `[ - { - "displayName": "Raw Device", - "protocol": "raw", - "host": "device.local", - "raw": {} - } - ]` - - count, err := svc.ImportProfiles(data, "skip") - if err != nil { - t.Fatalf("ImportProfiles() error = %v", err) - } - if count != 1 { - t.Fatalf("imported = %d, want 1", count) - } - profiles, err := svc.ListProfiles() - if err != nil { - t.Fatalf("ListProfiles() error = %v", err) - } - if len(profiles) != 1 { - t.Fatalf("profiles = %d, want 1", len(profiles)) - } - if profiles[0].Port != models.DefaultRAWPort { - t.Fatalf("port = %d, want %d", profiles[0].Port, models.DefaultRAWPort) - } - if profiles[0].RAW.Encoding != "utf-8" { - t.Fatalf("raw encoding = %q, want utf-8", profiles[0].RAW.Encoding) - } -} - -func TestLeviathanDocumentBytesRejectsOversizedDocument(t *testing.T) { - doc := document.New("large.bin", []byte("abcd")) - _, err := leviathanDocumentBytes(doc, 3, "test") - if err == nil { - t.Fatal("expected oversized document to be rejected") - } - if !strings.Contains(err.Error(), "too large") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestWriteLeviathanDocumentFileWritesPatchedBytes(t *testing.T) { - doc := document.New("patched.bin", []byte("abcd")) - if _, err := doc.Set(1, 'Z'); err != nil { - t.Fatalf("Set() failed: %v", err) - } - path := filepath.Join(t.TempDir(), "out.bin") - if err := writeLeviathanDocumentFile(path, doc); err != nil { - t.Fatalf("writeLeviathanDocumentFile() failed: %v", err) - } - got, err := os.ReadFile(path) - if err != nil { - t.Fatalf("ReadFile() failed: %v", err) - } - if string(got) != "aZcd" { - t.Fatalf("written bytes = %q, want %q", string(got), "aZcd") - } -} - -func TestWriteLeviathanDocumentRangeFileWritesSelectedPatchedBytes(t *testing.T) { - doc := document.New("patched.bin", []byte("abcdef")) - if _, err := doc.Set(3, 'Z'); err != nil { - t.Fatalf("Set() failed: %v", err) - } - path := filepath.Join(t.TempDir(), "range.bin") - if err := writeLeviathanDocumentRangeFile(path, doc, 2, 3); err != nil { - t.Fatalf("writeLeviathanDocumentRangeFile() failed: %v", err) - } - got, err := os.ReadFile(path) - if err != nil { - t.Fatalf("ReadFile() failed: %v", err) - } - if string(got) != "cZe" { - t.Fatalf("written bytes = %q, want %q", string(got), "cZe") - } -} - -func TestReadLeviathanSmallFileRejectsOversizedFile(t *testing.T) { - path := filepath.Join(t.TempDir(), "big.img") - if err := os.WriteFile(path, []byte("abcd"), 0600); err != nil { - t.Fatalf("WriteFile() failed: %v", err) - } - _, err := readLeviathanSmallFile(path, 3, "image") - if err == nil { - t.Fatal("expected oversized file to be rejected") - } - if !strings.Contains(err.Error(), "too large") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestReadSSHPublicKeyFileRejectsOversizedFile(t *testing.T) { - path := filepath.Join(t.TempDir(), "big.pub") - if err := os.WriteFile(path, []byte(strings.Repeat("x", maxSSHPublicKeyBytes+1)), 0600); err != nil { - t.Fatalf("WriteFile() failed: %v", err) - } - _, err := readSSHPublicKeyFile(path) - if err == nil { - t.Fatal("expected oversized public key to be rejected") - } - if !strings.Contains(err.Error(), "too large") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestCreateProfileNormalizesLegacyVNCSecretMode(t *testing.T) { - svc := newTestService(t) - profile := models.NewProfile(models.ProtocolVNC) - profile.DisplayName = "VNC" - profile.Host = "vnc.local" - profile.VNC.SecretMode = "prompt" - profile.VNC.SSHSecretMode = "prompt" - - got, err := svc.CreateProfile(profile) - if err != nil { - t.Fatalf("CreateProfile() error = %v", err) - } - if got.VNC.SecretMode != models.SecretModeAskEveryTime { - t.Fatalf("secret mode = %q, want %q", got.VNC.SecretMode, models.SecretModeAskEveryTime) - } - if got.VNC.SSHSecretMode != models.SecretModeAskEveryTime { - t.Fatalf("ssh secret mode = %q, want %q", got.VNC.SSHSecretMode, models.SecretModeAskEveryTime) - } -} - -func newTestService(t *testing.T) *Service { - t.Helper() - store, err := storage.NewStore(filepath.Join(t.TempDir(), "test.db")) - if err != nil { - t.Fatalf("NewStore() error = %v", err) - } - t.Cleanup(func() { store.Close() }) - return NewService(store, nil, nil, nil, audit.NewLogger(store), nil) -} diff --git a/internal/api/leviathan.go b/internal/api/leviathan.go deleted file mode 100644 index 224f05f..0000000 --- a/internal/api/leviathan.go +++ /dev/null @@ -1,1035 +0,0 @@ -package api - -import ( - "archive/zip" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "os" - "path/filepath" - "sort" - "strconv" - "strings" - "sync" - "time" - - wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" - - "github.com/andydixon/link/internal/leviathan/analysis" - "github.com/andydixon/link/internal/leviathan/binsnoop" - "github.com/andydixon/link/internal/leviathan/disasm" - "github.com/andydixon/link/internal/leviathan/document" - "github.com/andydixon/link/internal/leviathan/parsers" - "github.com/andydixon/link/internal/leviathan/patch" - "github.com/andydixon/link/internal/leviathan/pdf" - "github.com/andydixon/link/internal/leviathan/spectrogram" - "github.com/andydixon/link/internal/leviathan/squashfs" - "github.com/andydixon/link/internal/leviathan/stego" - "github.com/andydixon/link/internal/leviathan/transforms" -) - -type leviathanState struct { - mu sync.Mutex - doc *document.Document - cmp *document.Document -} - -type leviathanDocState struct { - Name string `json:"name"` - Size int64 `json:"size"` - Dirty bool `json:"dirty"` - UndoLen int `json:"undoLen"` - RedoLen int `json:"redoLen"` - Compare *leviathanDocState `json:"compare,omitempty"` - PreviewHex []map[string]any `json:"previewHex"` -} - -func (s *Service) leviathanEngine() *leviathanState { - if s.leviathan == nil { - s.leviathan = &leviathanState{} - } - return s.leviathan -} - -// LeviathanOpenFile prompts for a binary and loads it into the Leviathan workbench. -func (s *Service) LeviathanOpenFile() (map[string]any, error) { - if s.ctx == nil { - return nil, fmt.Errorf("application context not set") - } - path, err := wailsRuntime.OpenFileDialog(s.ctx, wailsRuntime.OpenDialogOptions{ - Title: "Open Binary", - }) - if err != nil || path == "" { - return nil, err - } - return s.LeviathanLoadFile(path, false) -} - -// LeviathanOpenCompare prompts for a binary to compare against the working file. -func (s *Service) LeviathanOpenCompare() (map[string]any, error) { - if s.ctx == nil { - return nil, fmt.Errorf("application context not set") - } - path, err := wailsRuntime.OpenFileDialog(s.ctx, wailsRuntime.OpenDialogOptions{ - Title: "Open Compare Binary", - }) - if err != nil || path == "" { - return nil, err - } - return s.LeviathanLoadFile(path, true) -} - -// LeviathanLoadFile loads a path supplied by drag/drop or the native file picker. -func (s *Service) LeviathanLoadFile(path string, compare bool) (map[string]any, error) { - d, err := document.Open(path) - if err != nil { - return nil, err - } - engine := s.leviathanEngine() - engine.mu.Lock() - if compare { - engine.cmp = d - } else { - engine.doc = d - } - state := engine.stateLocked() - engine.mu.Unlock() - return map[string]any{"state": state}, nil -} - -// LeviathanState returns the current binary workbench state. -func (s *Service) LeviathanState() (leviathanDocState, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - defer engine.mu.Unlock() - return engine.stateLocked(), nil -} - -// LeviathanRunTool runs one Leviathan analysis/editing tool. -func (s *Service) LeviathanRunTool(tool string, params map[string]any) (map[string]any, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - if engine.doc == nil { - engine.mu.Unlock() - return nil, errors.New("open a file first") - } - if tool == "spectrogram-encode" || tool == "spectrogram-insert" || tool == "spectrogram-view" || tool == "case" { - doc := engine.doc - engine.mu.Unlock() - switch tool { - case "spectrogram-encode": - return s.leviathanSpectrogramEncode(doc, params) - case "spectrogram-insert": - return s.leviathanSpectrogramInsert(doc, params) - case "case": - return s.leviathanExportCase(doc) - default: - return leviathanSpectrogramView(doc, params) - } - } - defer engine.mu.Unlock() - return engine.runToolLocked(tool, params) -} - -// LeviathanSetByte patches one byte in the working document. -func (s *Service) LeviathanSetByte(offset int64, value string) (map[string]any, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - defer engine.mu.Unlock() - if engine.doc == nil { - return nil, errors.New("open a file first") - } - value = strings.TrimSpace(value) - value = strings.TrimPrefix(strings.TrimPrefix(value, "0x"), "0X") - if len(value) == 1 { - value = "0" + value - } - if len(value) != 2 { - return nil, errors.New("byte value must be two hex digits") - } - b, err := hex.DecodeString(value) - if err != nil { - return nil, err - } - if _, err := engine.doc.Set(offset, b[0]); err != nil { - return nil, err - } - return map[string]any{"state": engine.stateLocked()}, nil -} - -// LeviathanUndo reverts the latest byte edit. -func (s *Service) LeviathanUndo() (leviathanDocState, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - defer engine.mu.Unlock() - if engine.doc == nil { - return leviathanDocState{}, errors.New("open a file first") - } - engine.doc.Undo() - return engine.stateLocked(), nil -} - -// LeviathanRedo reapplies the latest undone byte edit. -func (s *Service) LeviathanRedo() (leviathanDocState, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - defer engine.mu.Unlock() - if engine.doc == nil { - return leviathanDocState{}, errors.New("open a file first") - } - engine.doc.Redo() - return engine.stateLocked(), nil -} - -// LeviathanRevert clears all working edits. -func (s *Service) LeviathanRevert() (leviathanDocState, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - defer engine.mu.Unlock() - if engine.doc == nil { - return leviathanDocState{}, errors.New("open a file first") - } - engine.doc.Revert() - return engine.stateLocked(), nil -} - -// LeviathanSaveWorking prompts for a destination and writes the patched bytes. -func (s *Service) LeviathanSaveWorking() (string, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - if engine.doc == nil { - engine.mu.Unlock() - return "", errors.New("open a file first") - } - doc := engine.doc - engine.mu.Unlock() - if s.ctx == nil { - return "", fmt.Errorf("application context not set") - } - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Save Working Copy", - DefaultFilename: doc.Name() + ".patched", - }) - if err != nil || path == "" { - return "", err - } - return path, writeLeviathanDocumentFile(path, doc) -} - -// LeviathanSaveWorkingToPath writes the patched bytes to an explicit path. -// This is used by non-Wails hosts, where file picking is owned by the host UI. -func (s *Service) LeviathanSaveWorkingToPath(path string) (string, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - if engine.doc == nil { - engine.mu.Unlock() - return "", errors.New("open a file first") - } - doc := engine.doc - engine.mu.Unlock() - if strings.TrimSpace(path) == "" { - return "", errors.New("save path is required") - } - return path, writeLeviathanDocumentFile(path, doc) -} - -// LeviathanExportJSON writes a tool result to disk. -func (s *Service) LeviathanExportJSON(name string, value map[string]any) (string, error) { - if s.ctx == nil { - return "", fmt.Errorf("application context not set") - } - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Export JSON", - DefaultFilename: safeExportName(name, "leviathan-result.json"), - }) - if err != nil || path == "" { - return "", err - } - b, err := json.MarshalIndent(value, "", " ") - if err != nil { - return "", err - } - return path, os.WriteFile(path, b, 0600) -} - -// LeviathanExportRange prompts for a destination and streams a document range to disk. -func (s *Service) LeviathanExportRange(name string, start, length int64) (string, error) { - engine := s.leviathanEngine() - engine.mu.Lock() - if engine.doc == nil { - engine.mu.Unlock() - return "", errors.New("open a file first") - } - doc := engine.doc - engine.mu.Unlock() - - if s.ctx == nil { - return "", fmt.Errorf("application context not set") - } - if start < 0 { - return "", errors.New("export start must be non-negative") - } - if length <= 0 { - return "", errors.New("this finding does not have a known exportable length") - } - size := doc.Size() - if start >= size { - return "", errors.New("export start is outside the document") - } - if length > size-start { - length = size - start - } - defaultName := fmt.Sprintf("leviathan-%08x.bin", uint64(start)) - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Export Found File", - DefaultFilename: safeExportName(name, defaultName), - }) - if err != nil || path == "" { - return "", err - } - return path, writeLeviathanDocumentRangeFile(path, doc, start, length) -} - -func (l *leviathanState) stateLocked() leviathanDocState { - state := leviathanDocState{} - if l.doc != nil { - state = leviathanDocStateFor(l.doc, true) - } - if l.cmp != nil { - cmp := leviathanDocStateFor(l.cmp, false) - state.Compare = &cmp - } - return state -} - -func leviathanDocStateFor(d *document.Document, preview bool) leviathanDocState { - s := leviathanDocState{ - Name: d.Name(), - Size: d.Size(), - Dirty: d.IsDirty(), - UndoLen: d.HistoryLen(), - RedoLen: d.RedoLen(), - } - if preview { - s.PreviewHex = leviathanHexRows(d, 0, 512, 8) - } - return s -} - -func (l *leviathanState) runToolLocked(tool string, params map[string]any) (map[string]any, error) { - start := int64Param(params, "start", 0) - length := int64Param(params, "length", -1) - switch tool { - case "overview": - return analysis.Overview(l.doc) - case "hashes": - return analysis.Hashes(l.doc, start, length) - case "entropy": - return analysis.Entropy(l.doc, start, length, int64Param(params, "window", 4096), int64Param(params, "step", 4096)) - case "strings": - items, err := analysis.ExtractStrings(l.doc, start, length, intParam(params, "min", 4), []string{"ascii", "utf16le", "utf16be"}, intParam(params, "max", 5000)) - return map[string]any{"items": items}, err - case "iocs": - return analysis.ExtractIOCs(l.doc, start, length, intParam(params, "min", 5)) - case "carve": - items, err := analysis.Carve(l.doc, start, length, intParam(params, "max", 256)) - return map[string]any{"items": items}, err - case "keys": - items, err := analysis.ScanKeys(l.doc) - return map[string]any{"items": items}, err - case "timestamps": - return analysis.ScanTimestamps(l.doc, start, length, intParam(params, "max", 1000)) - case "search": - return l.runSearch(params, start, length) - case "multi-search": - return analysis.MultiSearch(l.doc, start, length, stringParam(params, "spec", ""), intParam(params, "max", 1000)) - case "parse": - name, tree, err := parseLeviathanDocument(l.doc, stringParam(params, "kind", "auto")) - if err != nil { - return nil, err - } - return map[string]any{"parser": name, "tree": tree.ToMap()}, nil - case "disasm": - return l.runDisasm(params, start, length) - case "diff": - if l.cmp == nil { - return nil, errors.New("open a compare file first") - } - return analysis.Diff(l.doc, l.cmp, start, length) - case "stego": - out, err := stego.AnalyseAppended(l.doc) - if err != nil { - return nil, err - } - meta, _ := stego.ScanMetadata(l.doc) - out["metadata"] = meta - return out, nil - case "metadata": - items, err := stego.ScanMetadata(l.doc) - return map[string]any{"items": items}, err - case "pdf": - return pdf.Parse(l.doc) - case "squashfs": - fs, err := squashfs.Open(l.doc) - if err != nil { - return nil, err - } - entries, err := fs.Walk() - return map[string]any{"superblock": fs.Superblock(), "compression": fs.CompressionName(), "entries": entries}, err - case "binsnoop": - results, err := leviathanBinsnoopDocument(l.doc, intParam(params, "max", 10000)) - return map[string]any{"results": results, "signatures": binsnoop.ListSignatures()}, err - case "rules": - rules, err := analysis.ParseRules(stringParam(params, "source", "")) - if err != nil { - return nil, err - } - items, err := analysis.RunRules(l.doc, start, length, rules, intParam(params, "max", 1000)) - return map[string]any{"items": items}, err - case "patch-plan": - steps, err := patch.Parse(stringParam(params, "source", "")) - if err != nil { - return nil, err - } - plan, err := patch.Plan(l.doc, steps) - return map[string]any{"plan": plan}, err - case "patch-apply": - steps, err := patch.Parse(stringParam(params, "source", "")) - if err != nil { - return nil, err - } - if _, err := patch.Plan(l.doc, steps); err != nil { - return nil, err - } - return map[string]any{"state": l.stateLocked()}, patch.Apply(l.doc, steps) - case "transform": - return l.runTransform(params, start, length) - case "hex": - return map[string]any{"rows": leviathanHexRows(l.doc, start, int64Param(params, "bytes", 1024), intParam(params, "width", 16))}, nil - case "case": - return nil, errors.New("case export must run through the service") - default: - return nil, errors.New("unknown tool: " + tool) - } -} - -func (l *leviathanState) runSearch(params map[string]any, start, length int64) (map[string]any, error) { - mode := stringParam(params, "mode", "text") - query := stringParam(params, "query", "") - max := intParam(params, "max", 1000) - switch mode { - case "hex": - b, err := hex.DecodeString(strings.ReplaceAll(query, " ", "")) - if err != nil { - return nil, err - } - hits, err := analysis.SearchBytes(l.doc, start, length, b, max) - return map[string]any{"hits": analysis.HitsToMaps(hits)}, err - case "regex": - hits, err := analysis.SearchRegex(l.doc, start, length, query, max) - return map[string]any{"hits": analysis.HitsToMaps(hits)}, err - default: - hits, err := analysis.SearchBytes(l.doc, start, length, []byte(query), max) - return map[string]any{"hits": analysis.HitsToMaps(hits)}, err - } -} - -func (l *leviathanState) runDisasm(params map[string]any, start, length int64) (map[string]any, error) { - if length < 0 || length > 65536 { - length = 4096 - } - buf, err := l.rangeBytes(start, length) - if err != nil { - return nil, err - } - return map[string]any{"items": disasm.Disassemble(buf, start, stringParam(params, "arch", "x86_64"), intParam(params, "max", 1000))}, nil -} - -func (l *leviathanState) runTransform(params map[string]any, start, length int64) (map[string]any, error) { - if length < 0 || length > 8<<20 { - length = min64(l.doc.Size()-start, 1<<20) - } - buf, err := l.rangeBytes(start, length) - if err != nil { - return nil, err - } - steps := parseLeviathanTransformPipeline(stringParam(params, "pipeline", "hex.encode")) - results, err := transforms.ApplyPipeline(buf, steps) - if err != nil { - return nil, err - } - out := make([]map[string]any, 0, len(results)) - for i, r := range results { - out = append(out, map[string]any{"step": i, "length": len(r), "preview": previewLeviathanBytes(r)}) - } - return map[string]any{"steps": steps, "results": out}, nil -} - -func leviathanSpectrogramView(doc *document.Document, params map[string]any) (map[string]any, error) { - buf, err := leviathanDocumentBytes(doc, spectrogram.MaxAudioBytes, "audio") - if err != nil { - return nil, err - } - pngBytes, summary, err := spectrogram.AudioSpectrogramPNG(buf, doc.Name(), leviathanSpectrogramViewOptions(params)) - if err != nil { - return nil, err - } - return map[string]any{ - "mime": "image/png", - "pngBase64": base64.StdEncoding.EncodeToString(pngBytes), - "summary": summary, - }, nil -} - -func (s *Service) leviathanExportCase(doc *document.Document) (map[string]any, error) { - if s.ctx == nil { - return nil, fmt.Errorf("application context not set") - } - filename := strings.TrimSuffix(doc.Name(), filepath.Ext(doc.Name())) + ".case.zip" - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Export Leviathan Case", - DefaultFilename: filename, - }) - if err != nil || path == "" { - return nil, err - } - - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return nil, err - } - defer f.Close() - - zw := zip.NewWriter(f) - add := func(name string, data []byte) error { - w, err := zw.Create(name) - if err != nil { - return err - } - _, err = w.Write(data) - return err - } - hashes, _ := analysis.Hashes(doc, 0, -1) - overview, _ := analysis.Overview(doc) - manifest, _ := json.MarshalIndent(map[string]any{"name": doc.Name(), "size": doc.Size(), "exportedAt": time.Now().Format(time.RFC3339), "hashes": hashes}, "", " ") - overviewJSON, _ := json.MarshalIndent(overview, "", " ") - patchesJSON, _ := json.MarshalIndent(doc.Patches(), "", " ") - if err := add("manifest.json", manifest); err != nil { - return nil, err - } - if err := addDocument("working.bin", doc, zw); err != nil { - return nil, err - } - if err := add("overview.json", overviewJSON); err != nil { - return nil, err - } - if err := add("patches.json", patchesJSON); err != nil { - return nil, err - } - if err := zw.Close(); err != nil { - return nil, err - } - if err := f.Sync(); err != nil { - return nil, err - } - info, err := f.Stat() - if err != nil { - return nil, err - } - return map[string]any{"path": path, "bytes": info.Size()}, nil -} - -func (s *Service) leviathanSpectrogramEncode(doc *document.Document, params map[string]any) (map[string]any, error) { - if s.ctx == nil { - return nil, fmt.Errorf("application context not set") - } - imageBytes, err := leviathanDocumentBytes(doc, spectrogram.MaxImageBytes, "image") - if err != nil { - return nil, err - } - wavBytes, summary, err := spectrogram.ImageToWAV(imageBytes, leviathanSpectrogramEncodeOptions(params)) - if err != nil { - return nil, err - } - defaultName := strings.TrimSuffix(doc.Name(), filepath.Ext(doc.Name())) + ".spectrogram.wav" - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Save Spectrogram WAV", - DefaultFilename: safeExportName(defaultName, "spectrogram.wav"), - Filters: []wailsRuntime.FileFilter{ - {DisplayName: "WAV Audio (*.wav)", Pattern: "*.wav"}, - }, - }) - if err != nil || path == "" { - return nil, err - } - if filepath.Ext(strings.ToLower(path)) != ".wav" { - path += ".wav" - } - if err := os.WriteFile(path, wavBytes, 0600); err != nil { - return nil, err - } - summary["path"] = path - return map[string]any{"path": path, "summary": summary}, nil -} - -func (s *Service) leviathanSpectrogramInsert(doc *document.Document, params map[string]any) (map[string]any, error) { - if s.ctx == nil { - return nil, fmt.Errorf("application context not set") - } - imagePath := strings.TrimSpace(stringParam(params, "imagePath", "")) - if imagePath == "" { - return nil, errors.New("select an image to insert") - } - imageBytes, err := readLeviathanSmallFile(imagePath, spectrogram.MaxImageBytes, "image") - if err != nil { - return nil, fmt.Errorf("read image: %w", err) - } - audioBytes, err := leviathanDocumentBytes(doc, spectrogram.MaxAudioBytes, "audio") - if err != nil { - return nil, err - } - wavBytes, summary, err := spectrogram.InsertImageIntoAudio(imageBytes, audioBytes, doc.Name(), leviathanSpectrogramInsertOptions(params)) - if err != nil { - return nil, err - } - defaultName := strings.TrimSuffix(doc.Name(), filepath.Ext(doc.Name())) + ".with-spectrogram.wav" - path, err := wailsRuntime.SaveFileDialog(s.ctx, wailsRuntime.SaveDialogOptions{ - Title: "Save Audio With Spectrogram Image", - DefaultFilename: safeExportName(defaultName, "with-spectrogram.wav"), - Filters: []wailsRuntime.FileFilter{ - {DisplayName: "WAV Audio (*.wav)", Pattern: "*.wav"}, - }, - }) - if err != nil || path == "" { - return nil, err - } - if filepath.Ext(strings.ToLower(path)) != ".wav" { - path += ".wav" - } - if err := os.WriteFile(path, wavBytes, 0600); err != nil { - return nil, err - } - summary["path"] = path - summary["imagePath"] = imagePath - return map[string]any{"path": path, "summary": summary}, nil -} - -func writeLeviathanDocumentFile(path string, doc *document.Document) error { - return writeLeviathanDocumentRangeFile(path, doc, 0, doc.Size()) -} - -func writeLeviathanDocumentRangeFile(path string, doc *document.Document, start, length int64) error { - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return err - } - defer f.Close() - if err := doc.ForEachChunk(start, length, func(_ int64, chunk []byte) error { - _, err := f.Write(chunk) - return err - }); err != nil { - return err - } - return f.Sync() -} - -func addDocument(name string, doc *document.Document, zw *zip.Writer) error { - w, err := zw.Create(name) - if err != nil { - return err - } - return doc.ForEachChunk(0, doc.Size(), func(_ int64, chunk []byte) error { - _, err := w.Write(chunk) - return err - }) -} - -func leviathanDocumentBytes(doc *document.Document, maxBytes int64, label string) ([]byte, error) { - size := doc.Size() - if maxBytes > 0 && size > maxBytes { - return nil, fmt.Errorf("%s file is too large for this operation: %s exceeds %s", label, humanBytes(size), humanBytes(maxBytes)) - } - return doc.Range(0, size) -} - -func readLeviathanSmallFile(path string, maxBytes int64, label string) ([]byte, error) { - info, err := os.Stat(path) - if err != nil { - return nil, err - } - if info.IsDir() { - return nil, fmt.Errorf("%s path is a directory", label) - } - if maxBytes > 0 && info.Size() > maxBytes { - return nil, fmt.Errorf("%s file is too large for this operation: %s exceeds %s", label, humanBytes(info.Size()), humanBytes(maxBytes)) - } - return os.ReadFile(path) -} - -func humanBytes(n int64) string { - const unit = 1024 - if n < unit { - return fmt.Sprintf("%d B", n) - } - value := float64(n) - for _, suffix := range []string{"KiB", "MiB", "GiB", "TiB"} { - value /= unit - if value < unit { - return fmt.Sprintf("%.1f %s", value, suffix) - } - } - return fmt.Sprintf("%.1f PiB", value/unit) -} - -func (l *leviathanState) rangeBytes(start, length int64) ([]byte, error) { - if start < 0 { - start = 0 - } - if length < 0 || start+length > l.doc.Size() { - length = l.doc.Size() - start - } - if length < 0 { - length = 0 - } - return l.doc.Range(start, length) -} - -func parseLeviathanDocument(d *document.Document, kind string) (string, *parsers.Field, error) { - kind = strings.ToLower(strings.TrimSpace(kind)) - if kind == "" || kind == "auto" { - return parsers.DetectAndParse(d) - } - var tree *parsers.Field - var err error - switch kind { - case "png": - tree, err = parsers.ParsePNG(d) - case "jpeg", "jpg": - tree, err = parsers.ParseJPEG(d) - kind = "jpeg" - case "gif": - tree, err = parsers.ParseGIF(d) - case "bmp": - tree, err = parsers.ParseBMP(d) - case "zip": - tree, err = parsers.ParseZIP(d) - case "elf": - tree, err = parsers.ParseELF(d) - case "pe": - tree, err = parsers.ParsePE(d) - case "macho": - tree, err = parsers.ParseMachO(d) - case "riff": - tree, err = parsers.ParseRIFF(d) - case "asn1": - tree, err = parsers.ParseASN1(d, 0, d.Size()) - case "cert": - tree, err = parsers.ParseCerts(d) - default: - return "", nil, errors.New("unknown parser: " + kind) - } - return kind, tree, err -} - -func leviathanHexRows(d *document.Document, start, length int64, width int) []map[string]any { - if width <= 0 { - width = 16 - } - if length < 0 || length > 8192 { - length = 8192 - } - rows := []map[string]any{} - for off := start; off < start+length && off < d.Size(); off += int64(width) { - n := int64(width) - if off+n > d.Size() { - n = d.Size() - off - } - buf, err := d.Range(off, n) - if err != nil { - break - } - rows = append(rows, map[string]any{"offset": off, "hex": spacedLeviathanHex(buf), "bytes": byteHexCells(buf), "ascii": asciiLeviathanPreview(buf)}) - } - return rows -} - -func parseLeviathanTransformPipeline(src string) []transforms.Step { - parts := strings.Split(src, "|") - steps := []transforms.Step{} - for _, part := range parts { - fields := strings.Fields(strings.TrimSpace(part)) - if len(fields) == 0 { - continue - } - op := fields[0] - arg := "" - if len(fields) > 1 { - arg = strings.Join(fields[1:], " ") - } - switch op { - case "hex": - op = "hex.encode" - case "unhex": - op = "hex.decode" - case "base64", "b64": - op = "base64.encode" - case "unbase64", "unb64": - op = "base64.decode" - case "url": - op = "url.encode" - case "unurl": - op = "url.decode" - } - steps = append(steps, transforms.Step{Op: op, Arg: arg}) - } - return steps -} - -func spacedLeviathanHex(buf []byte) string { - return strings.Join(byteHexCells(buf), " ") -} - -func byteHexCells(buf []byte) []string { - parts := make([]string, len(buf)) - for i, b := range buf { - parts[i] = fmt.Sprintf("%02x", b) - } - return parts -} - -func asciiLeviathanPreview(buf []byte) string { - var b strings.Builder - for _, c := range buf { - if c >= 32 && c < 127 { - b.WriteByte(c) - } else { - b.WriteByte('.') - } - } - return b.String() -} - -func previewLeviathanBytes(buf []byte) string { - if len(buf) > 2048 { - buf = buf[:2048] - } - if isMostlyLeviathanText(buf) { - return string(buf) - } - return spacedLeviathanHex(buf) -} - -func isMostlyLeviathanText(buf []byte) bool { - if len(buf) == 0 { - return true - } - printable := 0 - for _, b := range buf { - if b == '\n' || b == '\r' || b == '\t' || (b >= 32 && b < 127) { - printable++ - } - } - return printable*100/len(buf) > 85 -} - -func intParam(m map[string]any, key string, def int) int { - return int(int64Param(m, key, int64(def))) -} - -func int64Param(m map[string]any, key string, def int64) int64 { - v, ok := m[key] - if !ok || v == nil { - return def - } - switch n := v.(type) { - case int64: - return n - case int: - return int64(n) - case float64: - return int64(n) - case string: - if n == "" { - return def - } - if parsed, err := strconv.ParseInt(n, 0, 64); err == nil { - return parsed - } - } - return def -} - -func stringParam(m map[string]any, key, def string) string { - if v, ok := m[key]; ok && v != nil { - return fmt.Sprint(v) - } - return def -} - -func floatParam(m map[string]any, key string, def float64) float64 { - v, ok := m[key] - if !ok || v == nil { - return def - } - switch n := v.(type) { - case float64: - return n - case float32: - return float64(n) - case int: - return float64(n) - case int64: - return float64(n) - case string: - if n == "" { - return def - } - if parsed, err := strconv.ParseFloat(n, 64); err == nil { - return parsed - } - } - return def -} - -func boolParam(m map[string]any, key string, def bool) bool { - v, ok := m[key] - if !ok || v == nil { - return def - } - switch b := v.(type) { - case bool: - return b - case string: - if b == "" { - return def - } - if parsed, err := strconv.ParseBool(b); err == nil { - return parsed - } - } - return def -} - -func leviathanBinsnoopDocument(d *document.Document, maxResults int) (map[string]any, error) { - if maxResults <= 0 { - maxResults = 10000 - } - const windowSize int64 = 32 << 20 - const overlapSize int64 = 1 << 20 - - scanner := binsnoop.New() - size := d.Size() - seen := make(map[string]bool) - fileMap := make([]binsnoop.SignatureResult, 0, 128) - truncated := false - - maxInt := int64(^uint(0) >> 1) - for off := int64(0); off < size; { - readLen := windowSize + overlapSize - if off+readLen > size { - readLen = size - off - } - buf, err := d.Range(off, readLen) - if err != nil { - return nil, err - } - for _, hit := range scanner.Scan(buf) { - abs := off + int64(hit.Offset) - if abs < 0 || abs > maxInt || abs >= size { - continue - } - if hit.Size < 0 || int64(hit.Size) > size-abs { - continue - } - hit.Offset = int(abs) - key := fmt.Sprintf("%d:%s", hit.Offset, hit.Name) - if seen[key] { - continue - } - seen[key] = true - fileMap = append(fileMap, hit) - if len(fileMap) >= maxResults { - truncated = true - break - } - } - if truncated || off+windowSize >= size { - break - } - off += windowSize - } - - sort.Slice(fileMap, func(i, j int) bool { - if fileMap[i].Offset == fileMap[j].Offset { - return fileMap[i].Confidence > fileMap[j].Confidence - } - return fileMap[i].Offset < fileMap[j].Offset - }) - - return map[string]any{ - "file_path": d.Name(), - "file_map": fileMap, - "scanned_bytes": size, - "window_bytes": windowSize, - "overlap_bytes": overlapSize, - "whole_file": true, - "windowed": true, - "truncated": truncated, - "max_results": maxResults, - "extractions": map[string]any{}, - "result_count": len(fileMap), - "document_bytes": size, - }, nil -} - -func leviathanSpectrogramEncodeOptions(params map[string]any) spectrogram.EncodeOptions { - return spectrogram.EncodeOptions{ - DurationSec: floatParam(params, "durationSec", 5), - SampleRate: intParam(params, "sampleRate", 44100), - MinHz: floatParam(params, "minHz", 200), - MaxHz: floatParam(params, "maxHz", 20000), - Bins: intParam(params, "bins", 160), - Columns: intParam(params, "columns", 768), - Invert: boolParam(params, "invert", true), - PreserveAR: boolParam(params, "preserveAspectRatio", true), - Gain: floatParam(params, "gain", 0.85), - } -} - -func leviathanSpectrogramInsertOptions(params map[string]any) spectrogram.InsertOptions { - return spectrogram.InsertOptions{ - EncodeOptions: leviathanSpectrogramEncodeOptions(params), - StartSec: floatParam(params, "startSec", 0), - MixLevel: floatParam(params, "mixLevel", 0.35), - } -} - -func leviathanSpectrogramViewOptions(params map[string]any) spectrogram.ViewOptions { - return spectrogram.ViewOptions{ - FFTSize: intParam(params, "fftSize", 2048), - Height: intParam(params, "height", 1024), - MaxWidth: intParam(params, "maxWidth", 1400), - Aspect: floatParam(params, "aspectRatio", 1), - MinHz: floatParam(params, "minHz", 0), - MaxHz: floatParam(params, "maxHz", 0), - Brightness: floatParam(params, "brightness", 1), - Contrast: floatParam(params, "contrast", 1), - } -} - -func min64(a, b int64) int64 { - if a < b { - return a - } - return b -} - -func safeExportName(name, fallback string) string { - name = filepath.Base(strings.TrimSpace(name)) - if name == "" || name == "." || strings.ContainsRune(name, 0) { - return fallback - } - return name -} diff --git a/internal/app/app.go b/internal/app/app.go deleted file mode 100644 index 4c16caf..0000000 --- a/internal/app/app.go +++ /dev/null @@ -1,171 +0,0 @@ -// Package app provides the core application lifecycle for Alchementary Link. -// It wires together storage, secrets, sessions, preferences, known hosts, -// and the audit log, and exposes Wails lifecycle hooks. -package app - -import ( - "context" - "log" - "path/filepath" - "sync" - - "github.com/wailsapp/wails/v2/pkg/runtime" - - "github.com/andydixon/link/internal/api" - "github.com/andydixon/link/internal/audit" - "github.com/andydixon/link/internal/config" - "github.com/andydixon/link/internal/knownhosts" - "github.com/andydixon/link/internal/preferences" - "github.com/andydixon/link/internal/recording" - "github.com/andydixon/link/internal/secrets" - "github.com/andydixon/link/internal/sessions" - "github.com/andydixon/link/internal/storage" -) - -// App is the central application struct. Wails binds its lifecycle methods -// (Startup, Shutdown, DomReady) and it holds references to every subsystem. -type App struct { - ctx context.Context - mu sync.Mutex - - Config *config.AppConfig - Store *storage.Store - SecretStore *secrets.SecretStore - KeychainStore *secrets.KeychainStore - SessionManager *sessions.Manager - Prefs *preferences.Manager - KnownHosts *knownhosts.Store - AuditLog *audit.Logger - Service *api.Service -} - -// NewApp creates a new App instance. Subsystems are not initialised until -// Startup is called by the Wails runtime. The API service is pre-created -// so it can be bound by Wails before Startup runs. -func NewApp() *App { - a := &App{} - // The API service is created eagerly so Wails can bind it. Its backing - // stores are nil at this point; they are wired during Startup. - a.Service = &api.Service{} - return a -} - -// APIService returns the Wails-bound API service instance. This must be -// called before wails.Run so the binding is registered. -func (a *App) APIService() *api.Service { - return a.Service -} - -// Startup is called by Wails when the application starts. It loads -// configuration, ensures required directories exist, and initialises all -// subsystems (storage, secret store, session manager, preferences). -func (a *App) Startup(ctx context.Context) { - a.mu.Lock() - defer a.mu.Unlock() - - a.ctx = ctx - - // Load platform-specific configuration paths. - cfg, err := config.Load() - if err != nil { - log.Fatalf("failed to load configuration: %v", err) - } - a.Config = cfg - - if err := cfg.EnsureDirectories(); err != nil { - log.Fatalf("failed to create application directories: %v", err) - } - - // Initialise SQLite storage (migrations run automatically on open). - store, err := storage.NewStore(cfg.DBPath) - if err != nil { - log.Fatalf("failed to open storage: %v", err) - } - a.Store = store - - // Initialise secret stores (keychain preferred, encrypted file fallback). - a.SecretStore = secrets.NewSecretStore(cfg.SecretPath) - a.KeychainStore = secrets.NewKeychainStore("AlchementaryLink") - - // Initialise the audit logger backed by SQLite. - a.AuditLog = audit.NewLogger(store) - - // Initialise known hosts store. - a.KnownHosts = knownhosts.NewStore(store) - - // Initialise preferences manager. - a.Prefs = preferences.NewManager(store) - if err := a.Prefs.Load(); err != nil { - log.Printf("warning: failed to load preferences, using defaults: %v", err) - } - - // Initialise session recording store. - recordDir := filepath.Join(cfg.DataDir, "recordings") - recordStore := recording.NewStore(recordDir) - - // Initialise the session manager. - a.SessionManager = sessions.NewManager(store, a.SecretStore, a.KeychainStore, a.AuditLog, recordStore) - a.SessionManager.SetContext(ctx) - - // Wire the API service with all backing stores now that they exist. - *a.Service = *api.NewService(store, a.SecretStore, a.KeychainStore, a.SessionManager, a.AuditLog, a.Prefs) - a.Service.SetContext(ctx) -} - -// Shutdown is called by Wails when the application is closing. It ensures all -// sessions are cleanly terminated and persistent storage is flushed. -func (a *App) Shutdown(ctx context.Context) { - a.mu.Lock() - defer a.mu.Unlock() - - if a.Service != nil { - a.Service.Shutdown() - } - - if a.SessionManager != nil { - a.SessionManager.Shutdown() - } - - if a.Store != nil { - if err := a.Store.Close(); err != nil { - log.Printf("warning: error closing storage: %v", err) - } - } - - // Lock the secret store to zero key material. - if a.SecretStore != nil { - a.SecretStore.Lock() - } -} - -// DomReady is called by Wails once the frontend DOM is fully loaded. It emits -// a "backend:ready" event so the frontend knows the backend is prepared. -func (a *App) DomReady(ctx context.Context) { - runtime.EventsEmit(ctx, "backend:ready", map[string]interface{}{ - "message": "Backend is ready", - }) -} - -// EmitOpenAbout tells the frontend to open the About tab. -func (a *App) EmitOpenAbout() { - a.mu.Lock() - ctx := a.ctx - a.mu.Unlock() - if ctx == nil { - log.Println("unable to open About: application context not initialised") - return - } - runtime.EventsEmit(ctx, "app:open-about", nil) -} - -// RequestQuit asks the runtime to quit the application. -func (a *App) RequestQuit() { - a.mu.Lock() - ctx := a.ctx - a.mu.Unlock() - if ctx == nil { - log.Println("unable to quit: application context not initialised") - return - } - runtime.Quit(ctx) -} diff --git a/internal/audit/audit.go b/internal/audit/audit.go deleted file mode 100644 index 0d46c95..0000000 --- a/internal/audit/audit.go +++ /dev/null @@ -1,100 +0,0 @@ -// Package audit provides structured audit logging for security-relevant -// operations. Secrets are never included in audit entries. -package audit - -import ( - "fmt" - "time" - - "github.com/andydixon/link/internal/models" - "github.com/google/uuid" -) - -// AuditStore is the persistence interface used by the Logger. -type AuditStore interface { - AddAuditEntry(entry *models.AuditEntry) error - ListAuditEntries(limit int) ([]*models.AuditEntry, error) -} - -// Logger writes structured audit entries for connection events, profile -// changes, secret access, and import/export operations. -type Logger struct { - store AuditStore -} - -// NewLogger creates a Logger backed by the given store. -func NewLogger(store AuditStore) *Logger { - return &Logger{store: store} -} - -// Log records a generic audit entry. Fields left empty are stored as-is. -func (l *Logger) Log(action string, profileID string, sessionID string, details string) { - entry := &models.AuditEntry{ - ID: uuid.New().String(), - Timestamp: time.Now(), - Action: action, - ProfileID: profileID, - SessionID: sessionID, - Details: details, - } - // Best-effort logging — errors are silently discarded to avoid - // disrupting the caller's operation. - _ = l.store.AddAuditEntry(entry) -} - -// LogConnect records a session connection event. -func (l *Logger) LogConnect(profileID string, sessionID string, protocol string, host string) { - l.Log("connect", profileID, sessionID, - fmt.Sprintf("Connected via %s to %s", protocol, host)) -} - -// LogDisconnect records a session disconnection event. -func (l *Logger) LogDisconnect(sessionID string) { - l.Log("disconnect", "", sessionID, "Session disconnected") -} - -// LogProfileCreate records the creation of a new profile. -func (l *Logger) LogProfileCreate(profileID string, name string) { - l.Log("profile.create", profileID, "", - fmt.Sprintf("Created profile %q", name)) -} - -// LogProfileUpdate records an update to an existing profile. -func (l *Logger) LogProfileUpdate(profileID string, name string) { - l.Log("profile.update", profileID, "", - fmt.Sprintf("Updated profile %q", name)) -} - -// LogProfileDelete records the deletion of a profile. -func (l *Logger) LogProfileDelete(profileID string, name string) { - l.Log("profile.delete", profileID, "", - fmt.Sprintf("Deleted profile %q", name)) -} - -// LogSecretAccess records access to stored credentials. The secret value is -// never recorded — only the type of secret accessed. -func (l *Logger) LogSecretAccess(profileID string, secretType string) { - l.Log("secret.access", profileID, "", - fmt.Sprintf("Accessed %s credential", secretType)) -} - -// LogImport records a bulk profile import. -func (l *Logger) LogImport(count int) { - l.Log("import", "", "", - fmt.Sprintf("Imported %d profile(s)", count)) -} - -// LogExport records a profile export, noting whether secrets were included. -func (l *Logger) LogExport(count int, includeSecrets bool) { - secretsNote := "without secrets" - if includeSecrets { - secretsNote = "with secrets" - } - l.Log("export", "", "", - fmt.Sprintf("Exported %d profile(s) %s", count, secretsNote)) -} - -// ListEntries returns recent audit entries, delegating to the backing store. -func (l *Logger) ListEntries(limit int) ([]*models.AuditEntry, error) { - return l.store.ListAuditEntries(limit) -} diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 8856c9f..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,87 +0,0 @@ -// Package config resolves platform-specific application paths and configuration. -package config - -import ( - "os" - "path/filepath" - "runtime" -) - -const appName = "AlchementaryLink" - -// AppConfig holds the resolved application configuration paths. -type AppConfig struct { - DataDir string // base data directory - DBPath string // SQLite database path - SecretPath string // encrypted vault directory - LogPath string // log file path -} - -// Load resolves the application configuration. -// Data directory follows platform conventions: -// - macOS: ~/Library/Application Support/AlchementaryLink -// - Linux: ~/.config/AlchementaryLink -// - Windows: %APPDATA%/AlchementaryLink -func Load() (*AppConfig, error) { - dataDir, err := resolveDataDir() - if err != nil { - return nil, err - } - - cfg := &AppConfig{ - DataDir: dataDir, - DBPath: filepath.Join(dataDir, "link.db"), - SecretPath: filepath.Join(dataDir, "vault"), - LogPath: filepath.Join(dataDir, "link.log"), - } - return cfg, nil -} - -// EnsureDirectories creates required directories if they don't exist. -func (c *AppConfig) EnsureDirectories() error { - dirs := []string{ - c.DataDir, - c.SecretPath, - filepath.Dir(c.LogPath), - } - for _, d := range dirs { - if err := os.MkdirAll(d, 0700); err != nil { - return err - } - } - return nil -} - -// resolveDataDir determines the base data directory for the current platform. -func resolveDataDir() (string, error) { - var base string - - switch runtime.GOOS { - case "darwin": - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - base = filepath.Join(home, "Library", "Application Support") - case "windows": - base = os.Getenv("APPDATA") - if base == "" { - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - base = filepath.Join(home, "AppData", "Roaming") - } - default: // Linux and other UNIX-likes - base = os.Getenv("XDG_CONFIG_HOME") - if base == "" { - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - base = filepath.Join(home, ".config") - } - } - - return filepath.Join(base, appName), nil -} diff --git a/internal/exportimport/exportimport.go b/internal/exportimport/exportimport.go deleted file mode 100644 index 4486c7d..0000000 --- a/internal/exportimport/exportimport.go +++ /dev/null @@ -1,209 +0,0 @@ -// Package exportimport provides encrypted profile export and import. -// Profiles are serialised to JSON, then encrypted with AES-256-GCM using -// an Argon2id-derived key from a user-supplied password. No hard-coded -// secrets are used. The file format includes a version byte, salt, nonce, -// and ciphertext — everything needed for self-contained decryption. -package exportimport - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "time" - - "golang.org/x/crypto/argon2" - - "github.com/andydixon/link/internal/models" -) - -const ( - // File format version. Increment if the envelope format changes. - formatVersion byte = 1 - - // Argon2id parameters for export encryption (same strength as the vault). - exportArgonTime = 3 - exportArgonMemory = 128 * 1024 // 128 MiB - exportArgonThreads = 4 - exportArgonKeyLen = 32 // AES-256 - - saltLength = 32 - nonceLength = 12 // AES-GCM standard nonce size - - maxEncryptedImportBytes = 10 * 1024 * 1024 -) - -// ExportBundle is the cleartext payload that gets encrypted. -type ExportBundle struct { - Version int `json:"version"` - ExportedAt string `json:"exportedAt"` // ISO 8601 - Profiles []models.Profile `json:"profiles"` - Count int `json:"count"` -} - -// EncryptAndExport serialises the given profiles, encrypts the JSON with -// AES-256-GCM (Argon2id-derived key), and writes the result to the file. -// -// File layout: [1 byte version][32 bytes salt][12 bytes nonce][ciphertext] -func EncryptAndExport(profiles []models.Profile, password, filePath string) error { - if password == "" { - return errors.New("exportimport: password cannot be empty") - } - if len(profiles) == 0 { - return errors.New("exportimport: no profiles to export") - } - - bundle := ExportBundle{ - Version: 1, - ExportedAt: time.Now().UTC().Format(time.RFC3339), - Profiles: profiles, - Count: len(profiles), - } - - plaintext, err := json.MarshalIndent(bundle, "", " ") - if err != nil { - return fmt.Errorf("exportimport: failed to serialise profiles: %w", err) - } - defer clear(plaintext) - - // Generate random salt and nonce. - salt := make([]byte, saltLength) - if _, err := io.ReadFull(rand.Reader, salt); err != nil { - return fmt.Errorf("exportimport: failed to generate salt: %w", err) - } - - nonce := make([]byte, nonceLength) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return fmt.Errorf("exportimport: failed to generate nonce: %w", err) - } - - // Derive encryption key from password. - passwordBytes := []byte(password) - defer clear(passwordBytes) - key := argon2.IDKey( - passwordBytes, - salt, - exportArgonTime, - exportArgonMemory, - exportArgonThreads, - exportArgonKeyLen, - ) - defer clear(key) - - // Encrypt with AES-256-GCM. - block, err := aes.NewCipher(key) - if err != nil { - return fmt.Errorf("exportimport: cipher init failed: %w", err) - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return fmt.Errorf("exportimport: GCM init failed: %w", err) - } - - ciphertext := gcm.Seal(nil, nonce, plaintext, nil) - - // Write the file: version + salt + nonce + ciphertext. - f, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) - if err != nil { - return fmt.Errorf("exportimport: failed to create file: %w", err) - } - defer f.Close() - if err := f.Chmod(0600); err != nil { - return fmt.Errorf("exportimport: failed to restrict file permissions: %w", err) - } - - if _, err := f.Write([]byte{formatVersion}); err != nil { - return fmt.Errorf("exportimport: write version failed: %w", err) - } - if _, err := f.Write(salt); err != nil { - return fmt.Errorf("exportimport: write salt failed: %w", err) - } - if _, err := f.Write(nonce); err != nil { - return fmt.Errorf("exportimport: write nonce failed: %w", err) - } - if _, err := f.Write(ciphertext); err != nil { - return fmt.Errorf("exportimport: write ciphertext failed: %w", err) - } - - return f.Sync() -} - -// DecryptAndImport reads an encrypted export file, decrypts it using the -// given password, and returns the contained profiles. -func DecryptAndImport(filePath, password string) ([]models.Profile, error) { - if password == "" { - return nil, errors.New("exportimport: password cannot be empty") - } - - info, err := os.Stat(filePath) - if err != nil { - return nil, fmt.Errorf("exportimport: failed to stat file: %w", err) - } - if info.Size() > maxEncryptedImportBytes { - return nil, fmt.Errorf("exportimport: file is too large (max %d bytes)", maxEncryptedImportBytes) - } - - data, err := os.ReadFile(filePath) - if err != nil { - return nil, fmt.Errorf("exportimport: failed to read file: %w", err) - } - - // Minimum size: version(1) + salt(32) + nonce(12) + at least 1 byte ciphertext + GCM tag(16). - minSize := 1 + saltLength + nonceLength + 1 + 16 - if len(data) < minSize { - return nil, errors.New("exportimport: file is too small or corrupt") - } - - // Parse envelope. - version := data[0] - if version != formatVersion { - return nil, fmt.Errorf("exportimport: unsupported format version %d", version) - } - - salt := data[1 : 1+saltLength] - nonce := data[1+saltLength : 1+saltLength+nonceLength] - ciphertext := data[1+saltLength+nonceLength:] - - // Derive decryption key. - passwordBytes := []byte(password) - defer clear(passwordBytes) - key := argon2.IDKey( - passwordBytes, - salt, - exportArgonTime, - exportArgonMemory, - exportArgonThreads, - exportArgonKeyLen, - ) - defer clear(key) - - // Decrypt with AES-256-GCM. - block, err := aes.NewCipher(key) - if err != nil { - return nil, fmt.Errorf("exportimport: cipher init failed: %w", err) - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return nil, fmt.Errorf("exportimport: GCM init failed: %w", err) - } - - plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - return nil, errors.New("exportimport: decryption failed — wrong password or corrupt file") - } - defer clear(plaintext) - - // Parse the JSON bundle. - var bundle ExportBundle - if err := json.Unmarshal(plaintext, &bundle); err != nil { - return nil, fmt.Errorf("exportimport: invalid bundle JSON: %w", err) - } - - return bundle.Profiles, nil -} diff --git a/internal/exportimport/exportimport_test.go b/internal/exportimport/exportimport_test.go deleted file mode 100644 index 7535b9d..0000000 --- a/internal/exportimport/exportimport_test.go +++ /dev/null @@ -1,173 +0,0 @@ -package exportimport - -import ( - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/andydixon/link/internal/models" -) - -func TestEncryptDecryptRoundTrip(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "test-export.link") - - profiles := []models.Profile{ - { - ID: "test-1", - DisplayName: "Test Server", - Protocol: models.ProtocolSSH, - Host: "example.com", - Port: 22, - Username: "admin", - }, - { - ID: "test-2", - DisplayName: "Dev Box", - Protocol: models.ProtocolSSH, - Host: "dev.example.com", - Port: 2222, - Username: "developer", - }, - } - - password := "strong-test-password-2024!" - - // Encrypt and export. - if err := EncryptAndExport(profiles, password, path); err != nil { - t.Fatalf("EncryptAndExport failed: %v", err) - } - - // Verify file exists and has content. - info, err := os.Stat(path) - if err != nil { - t.Fatalf("exported file not found: %v", err) - } - if info.Size() == 0 { - t.Fatal("exported file is empty") - } - - // Decrypt and import. - imported, err := DecryptAndImport(path, password) - if err != nil { - t.Fatalf("DecryptAndImport failed: %v", err) - } - - if len(imported) != len(profiles) { - t.Fatalf("expected %d profiles, got %d", len(profiles), len(imported)) - } - - for i, p := range imported { - if p.ID != profiles[i].ID { - t.Errorf("profile %d: expected ID %q, got %q", i, profiles[i].ID, p.ID) - } - if p.DisplayName != profiles[i].DisplayName { - t.Errorf("profile %d: expected DisplayName %q, got %q", i, profiles[i].DisplayName, p.DisplayName) - } - if p.Host != profiles[i].Host { - t.Errorf("profile %d: expected Host %q, got %q", i, profiles[i].Host, p.Host) - } - } -} - -func TestDecryptWrongPassword(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "test-export.link") - - profiles := []models.Profile{ - { - ID: "test-1", - DisplayName: "Test", - Protocol: models.ProtocolSSH, - Host: "example.com", - Port: 22, - }, - } - - if err := EncryptAndExport(profiles, "correct-password", path); err != nil { - t.Fatalf("EncryptAndExport failed: %v", err) - } - - _, err := DecryptAndImport(path, "wrong-password") - if err == nil { - t.Fatal("expected error with wrong password, got nil") - } -} - -func TestEncryptEmptyProfiles(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "test.link") - - err := EncryptAndExport(nil, "password", path) - if err == nil { - t.Fatal("expected error for empty profiles, got nil") - } -} - -func TestEncryptAndExportRestrictsExistingFilePermissions(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("POSIX permission bits are not enforced on Windows") - } - path := filepath.Join(t.TempDir(), "profiles.link") - if err := os.WriteFile(path, []byte("old"), 0644); err != nil { - t.Fatal(err) - } - if err := EncryptAndExport([]models.Profile{{ID: "1"}}, "password", path); err != nil { - t.Fatal(err) - } - info, err := os.Stat(path) - if err != nil { - t.Fatal(err) - } - if got := info.Mode().Perm(); got != 0600 { - t.Fatalf("export permissions = %04o, want 0600", got) - } -} - -func TestEncryptEmptyPassword(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "test.link") - - profiles := []models.Profile{{ID: "1"}} - err := EncryptAndExport(profiles, "", path) - if err == nil { - t.Fatal("expected error for empty password, got nil") - } -} - -func TestDecryptCorruptFile(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "corrupt.link") - - // Write garbage data. - if err := os.WriteFile(path, []byte("corrupt"), 0600); err != nil { - t.Fatal(err) - } - - _, err := DecryptAndImport(path, "password") - if err == nil { - t.Fatal("expected error for corrupt file, got nil") - } -} - -func TestDecryptRejectsOversizedFile(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "huge.link") - f, err := os.Create(path) - if err != nil { - t.Fatal(err) - } - if err := f.Truncate(maxEncryptedImportBytes + 1); err != nil { - f.Close() - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } - - _, err = DecryptAndImport(path, "password") - if err == nil { - t.Fatal("expected error for oversized file, got nil") - } -} diff --git a/internal/githubclient/githubclient.go b/internal/githubclient/githubclient.go deleted file mode 100644 index 1f18b34..0000000 --- a/internal/githubclient/githubclient.go +++ /dev/null @@ -1,408 +0,0 @@ -// Package githubclient provides a lightweight GitHub REST API client for -// fetching repository metadata. It supports both github.com and GitHub -// Enterprise instances via a configurable base URL. -package githubclient - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "os" - "sort" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/andydixon/link/internal/models" -) - -const ( - maxGitHubJSONBodyBytes = 4 << 20 - maxGitHubErrorBodyBytes = 64 << 10 - maxGitHubArchiveBytes = int64(2 << 30) -) - -// Client wraps the GitHub REST API for repository listing. -type Client struct { - baseURL string - token string - httpClient *http.Client -} - -// New creates a Client for the given API base URL and personal access token. -// baseURL should be the API root, e.g. "https://api.github.com" or -// "https://ghe.example.com/api/v3". -func New(baseURL, token string) *Client { - return &Client{ - baseURL: strings.TrimRight(baseURL, "/"), - token: token, - httpClient: &http.Client{ - Timeout: 30 * time.Second, - }, - } -} - -// apiRepo is the subset of the GitHub API response we care about. -type apiRepo struct { - FullName string `json:"full_name"` - Name string `json:"name"` - Description string `json:"description"` - HTMLURL string `json:"html_url"` - DefaultBranch string `json:"default_branch"` - Private bool `json:"private"` - Fork bool `json:"fork"` - Archived bool `json:"archived"` - Language string `json:"language"` - Stars int `json:"stargazers_count"` - PushedAt time.Time `json:"pushed_at"` - UpdatedAt time.Time `json:"updated_at"` - Owner struct { - Login string `json:"login"` - } `json:"owner"` -} - -// FetchRepos retrieves all repositories visible to the authenticated user, -// paginating through the API. Results are sorted by pushed_at descending -// (most recently active first). -func (c *Client) FetchRepos(ctx context.Context) ([]models.GitHubRepo, error) { - return c.fetchRepos(ctx, nil) -} - -// FetchReposWithProgress fetches repositories and reports enrichment progress. -func (c *Client) FetchReposWithProgress(ctx context.Context, progress func(done int, total int)) ([]models.GitHubRepo, error) { - return c.fetchRepos(ctx, progress) -} - -func (c *Client) fetchRepos(ctx context.Context, progress func(done int, total int)) ([]models.GitHubRepo, error) { - var all []apiRepo - - page := 1 - for { - url := fmt.Sprintf("%s/user/repos?per_page=100&page=%d&sort=pushed&direction=desc", c.baseURL, page) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("building request: %w", err) - } - req.Header.Set("Authorization", "Bearer "+c.token) - req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("User-Agent", "AlchementaryLink/1.0") - - resp, err := c.httpClient.Do(req) - if err != nil { - return nil, fmt.Errorf("fetching repos page %d: %w", page, err) - } - - body, readErr := readLimitedGitHubBody(resp.Body, maxGitHubJSONBodyBytes) - resp.Body.Close() - if readErr != nil { - return nil, fmt.Errorf("reading response body: %w", readErr) - } - - if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { - return nil, fmt.Errorf("authentication failed (HTTP %d) — check your access token", resp.StatusCode) - } - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("GitHub API returned HTTP %d: %s", resp.StatusCode, string(body)) - } - - var batch []apiRepo - if err := json.Unmarshal(body, &batch); err != nil { - return nil, fmt.Errorf("decoding repos: %w", err) - } - - if len(batch) == 0 { - break - } - all = append(all, batch...) - - // Safety cap — 5000 repos should be more than enough. - if len(all) >= 5000 || len(batch) < 100 { - break - } - page++ - } - - // Sort by pushed_at descending (newest first). - sort.Slice(all, func(i, j int) bool { - return all[i].PushedAt.After(all[j].PushedAt) - }) - - result := make([]models.GitHubRepo, len(all)) - for i, r := range all { - result[i] = models.GitHubRepo{ - FullName: r.FullName, - Name: r.Name, - Owner: r.Owner.Login, - Description: r.Description, - HTMLURL: r.HTMLURL, - DefaultBranch: r.DefaultBranch, - Private: r.Private, - Fork: r.Fork, - Archived: r.Archived, - Language: r.Language, - Stars: r.Stars, - PushedAt: r.PushedAt.Format(time.RFC3339), - UpdatedAt: r.UpdatedAt.Format(time.RFC3339), - } - } - - if progress != nil { - progress(0, len(result)) - } - - // Enrich with latest commit metadata (best-effort). - sem := make(chan struct{}, 5) - var wg sync.WaitGroup - var done int32 - for i := range result { - wg.Add(1) - go func(idx int) { - defer wg.Done() - sem <- struct{}{} - defer func() { <-sem }() - - ref := result[idx].DefaultBranch - if ref == "" { - ref = "HEAD" - } - committer, committedAt, err := c.FetchLatestCommit(ctx, result[idx].FullName, ref) - if err != nil { - if progress != nil { - current := int(atomic.AddInt32(&done, 1)) - progress(current, len(result)) - } - return - } - result[idx].LatestCommitter = committer - result[idx].LatestCommitAt = committedAt - if progress != nil { - current := int(atomic.AddInt32(&done, 1)) - progress(current, len(result)) - } - }(i) - } - wg.Wait() - return result, nil -} - -// FetchLatestCommit returns the committer name and commit time for a repo/ref. -func (c *Client) FetchLatestCommit(ctx context.Context, fullName string, ref string) (string, string, error) { - if ref == "" { - ref = "HEAD" - } - owner, repo, err := splitRepoFullName(fullName) - if err != nil { - return "", "", err - } - url, err := c.endpointURL("repos", owner, repo, "commits", ref) - if err != nil { - return "", "", err - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return "", "", fmt.Errorf("building request: %w", err) - } - req.Header.Set("Authorization", "Bearer "+c.token) - req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("User-Agent", "AlchementaryLink/1.0") - - resp, err := c.httpClient.Do(req) - if err != nil { - return "", "", fmt.Errorf("fetching commit: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { - return "", "", fmt.Errorf("authentication failed (HTTP %d)", resp.StatusCode) - } - if resp.StatusCode != http.StatusOK { - return "", "", fmt.Errorf("GitHub API returned HTTP %d: %s", resp.StatusCode, readGitHubErrorBody(resp.Body)) - } - - var payload struct { - Commit struct { - Committer struct { - Name string `json:"name"` - Date time.Time `json:"date"` - } `json:"committer"` - Author struct { - Name string `json:"name"` - Date time.Time `json:"date"` - } `json:"author"` - } `json:"commit"` - } - - body, err := readLimitedGitHubBody(resp.Body, maxGitHubJSONBodyBytes) - if err != nil { - return "", "", fmt.Errorf("reading commit response: %w", err) - } - if err := json.Unmarshal(body, &payload); err != nil { - return "", "", fmt.Errorf("decoding commit: %w", err) - } - - name := payload.Commit.Committer.Name - date := payload.Commit.Committer.Date - if name == "" { - name = payload.Commit.Author.Name - date = payload.Commit.Author.Date - } - if name == "" || date.IsZero() { - return "", "", fmt.Errorf("commit metadata missing") - } - - return name, date.Format(time.RFC3339), nil -} - -// DownloadRepoArchive downloads a repository zip archive to the given path. -func (c *Client) DownloadRepoArchive(ctx context.Context, fullName string, ref string, destPath string) error { - owner, repo, err := splitRepoFullName(fullName) - if err != nil { - return err - } - segments := []string{"repos", owner, repo, "zipball"} - if ref != "" { - segments = append(segments, ref) - } - url, err := c.endpointURL(segments...) - if err != nil { - return err - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return fmt.Errorf("building request: %w", err) - } - req.Header.Set("Authorization", "Bearer "+c.token) - req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("User-Agent", "AlchementaryLink/1.0") - - resp, err := c.httpClient.Do(req) - if err != nil { - return fmt.Errorf("downloading archive: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { - return fmt.Errorf("authentication failed (HTTP %d) — check your access token", resp.StatusCode) - } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("GitHub API returned HTTP %d: %s", resp.StatusCode, readGitHubErrorBody(resp.Body)) - } - if resp.ContentLength > maxGitHubArchiveBytes { - return fmt.Errorf("archive is too large: %d bytes exceeds %d bytes", resp.ContentLength, maxGitHubArchiveBytes) - } - - file, err := os.Create(destPath) - if err != nil { - return fmt.Errorf("creating file: %w", err) - } - defer file.Close() - - if _, err := copyLimitedArchive(file, resp.Body, maxGitHubArchiveBytes); err != nil { - return fmt.Errorf("writing archive: %w", err) - } - return nil -} - -// ValidateToken makes a lightweight call to /user to confirm the token works. -func (c *Client) ValidateToken(ctx context.Context) (string, error) { - url := c.baseURL + "/user" - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return "", fmt.Errorf("building request: %w", err) - } - req.Header.Set("Authorization", "Bearer "+c.token) - req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("User-Agent", "AlchementaryLink/1.0") - - resp, err := c.httpClient.Do(req) - if err != nil { - return "", fmt.Errorf("contacting GitHub API: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return "", fmt.Errorf("token validation failed (HTTP %d)", resp.StatusCode) - } - - var user struct { - Login string `json:"login"` - } - body, err := readLimitedGitHubBody(resp.Body, maxGitHubJSONBodyBytes) - if err != nil { - return "", fmt.Errorf("reading user response: %w", err) - } - if err := json.Unmarshal(body, &user); err != nil { - return "", fmt.Errorf("decoding user: %w", err) - } - return user.Login, nil -} - -func readLimitedGitHubBody(r io.Reader, maxBytes int64) ([]byte, error) { - body, err := io.ReadAll(io.LimitReader(r, maxBytes+1)) - if err != nil { - return nil, err - } - if int64(len(body)) > maxBytes { - return nil, fmt.Errorf("response body exceeds %d bytes", maxBytes) - } - return body, nil -} - -func readGitHubErrorBody(r io.Reader) string { - body, err := readLimitedGitHubBody(r, maxGitHubErrorBodyBytes) - if err != nil { - return "" - } - return strings.TrimSpace(string(body)) -} - -func splitRepoFullName(fullName string) (string, string, error) { - parts := strings.Split(strings.TrimSpace(fullName), "/") - if len(parts) != 2 { - return "", "", fmt.Errorf("repository name must be in owner/repo format") - } - owner := strings.TrimSpace(parts[0]) - repo := strings.TrimSpace(parts[1]) - if owner == "" || repo == "" { - return "", "", fmt.Errorf("repository name must be in owner/repo format") - } - return owner, repo, nil -} - -func (c *Client) endpointURL(segments ...string) (string, error) { - u, err := url.Parse(c.baseURL) - if err != nil { - return "", fmt.Errorf("invalid GitHub base URL: %w", err) - } - plainPath := strings.TrimRight(u.Path, "/") - escapedPath := strings.TrimRight(u.EscapedPath(), "/") - for _, segment := range segments { - if segment == "" { - return "", fmt.Errorf("empty GitHub API path segment") - } - plainPath += "/" + segment - escapedPath += "/" + url.PathEscape(segment) - } - u.Path = plainPath - u.RawPath = escapedPath - return u.String(), nil -} - -func copyLimitedArchive(dst io.Writer, src io.Reader, maxBytes int64) (int64, error) { - limited := &io.LimitedReader{R: src, N: maxBytes + 1} - n, err := io.Copy(dst, limited) - if err != nil { - return n, err - } - if n > maxBytes { - return n, fmt.Errorf("archive exceeds %d bytes", maxBytes) - } - return n, nil -} diff --git a/internal/githubclient/githubclient_test.go b/internal/githubclient/githubclient_test.go deleted file mode 100644 index 3a26f89..0000000 --- a/internal/githubclient/githubclient_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package githubclient - -import ( - "bytes" - "io" - "strings" - "testing" -) - -func TestReadLimitedGitHubBodyRejectsOversizedResponse(t *testing.T) { - _, err := readLimitedGitHubBody(strings.NewReader("abcd"), 3) - if err == nil { - t.Fatal("expected oversized response to be rejected") - } - if !strings.Contains(err.Error(), "response body exceeds") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestReadLimitedGitHubBodyAllowsResponseAtLimit(t *testing.T) { - got, err := readLimitedGitHubBody(strings.NewReader("abc"), 3) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if string(got) != "abc" { - t.Fatalf("body = %q, want abc", string(got)) - } -} - -func TestEndpointURLEscapesRepoPathSegments(t *testing.T) { - client := New("https://ghe.example.com/api/v3", "") - - got, err := client.endpointURL("repos", "owner", "repo", "commits", "feature/a b") - if err != nil { - t.Fatalf("endpointURL() error = %v", err) - } - want := "https://ghe.example.com/api/v3/repos/owner/repo/commits/feature%2Fa%20b" - if got != want { - t.Fatalf("endpointURL() = %q, want %q", got, want) - } -} - -func TestSplitRepoFullNameRejectsExtraSegments(t *testing.T) { - if _, _, err := splitRepoFullName("owner/repo/../../user"); err == nil { - t.Fatal("expected malformed repository name to be rejected") - } -} - -func TestCopyLimitedArchiveRejectsOversizedStream(t *testing.T) { - var dst bytes.Buffer - n, err := copyLimitedArchive(&dst, strings.NewReader("abcd"), 3) - if err == nil { - t.Fatal("expected oversized archive stream to be rejected") - } - if n != 4 { - t.Fatalf("copied = %d, want 4", n) - } - if !strings.Contains(err.Error(), "archive exceeds") { - t.Fatalf("error = %q, want archive exceeds", err.Error()) - } -} - -func TestCopyLimitedArchiveAllowsStreamAtLimit(t *testing.T) { - var dst bytes.Buffer - n, err := copyLimitedArchive(&dst, strings.NewReader("abc"), 3) - if err != nil { - t.Fatalf("copyLimitedArchive() error = %v", err) - } - if n != 3 { - t.Fatalf("copied = %d, want 3", n) - } - if got, err := io.ReadAll(&dst); err != nil || string(got) != "abc" { - t.Fatalf("dst = %q, err = %v; want abc", string(got), err) - } -} diff --git a/internal/ircclient/ircclient.go b/internal/ircclient/ircclient.go deleted file mode 100644 index f94a14e..0000000 --- a/internal/ircclient/ircclient.go +++ /dev/null @@ -1,833 +0,0 @@ -// Package ircclient wraps a full IRC client with Link-specific event and -// command handling, including optional SSH tunnelling. -package ircclient - -import ( - "crypto/tls" - "fmt" - "net" - "sort" - "strconv" - "strings" - "sync" - "time" - - "github.com/lrstanley/girc" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" -) - -const ServerTarget = "server" - -const maxRoomListEntries = 2000 - -// Config describes an IRC connection. -type Config struct { - Server string - Port int - Nick string - User string - RealName string - UseTLS bool - ServerPass string - AutoJoinRooms []string - - NickServIdentify bool - NickServAccount string - NickServPassword string - - SSHTunnel bool - SSHHost string - SSHPort int - SSHUsername string - SSHAuthMethod string - SSHPassword string - SSHPrivateKey []byte - SSHPassphrase string - SSHAgentAuth bool - SSHAgentConn net.Conn - SSHHostKeyCallback ssh.HostKeyCallback - - ConnectTimeout time.Duration - OnEvent func(Event) - OnError func(error) - OnClose func() - OnRoomsChanged func([]string) -} - -// Event is a typed IRC event suitable for JSON delivery to the frontend. -type Event struct { - Type string `json:"type"` - Target string `json:"target,omitempty"` - From string `json:"from,omitempty"` - Text string `json:"text,omitempty"` - Command string `json:"command,omitempty"` - Timestamp int64 `json:"timestamp"` - Rooms []RoomInfo `json:"rooms,omitempty"` - Users []string `json:"users,omitempty"` - Topic string `json:"topic,omitempty"` -} - -// RoomInfo describes a joined or listed IRC room. -type RoomInfo struct { - Name string `json:"name"` - Topic string `json:"topic,omitempty"` - Users int `json:"users,omitempty"` -} - -// Client owns a girc client and optional SSH tunnel. -type Client struct { - cfg Config - client *girc.Client - sshClient *ssh.Client - agentConn net.Conn - - mu sync.Mutex - connected bool - closing bool - listRooms []RoomInfo - roomNames []string -} - -// New creates an IRC client wrapper. -func New(cfg Config) *Client { - if cfg.ConnectTimeout <= 0 { - cfg.ConnectTimeout = 30 * time.Second - } - if cfg.User == "" { - cfg.User = cfg.Nick - } - if cfg.RealName == "" { - cfg.RealName = cfg.Nick - } - - c := &Client{cfg: cfg} - c.client = girc.New(girc.Config{ - Server: cfg.Server, - ServerPass: cfg.ServerPass, - Port: cfg.Port, - Nick: cfg.Nick, - User: cfg.User, - Name: cfg.RealName, - SSL: cfg.UseTLS && !cfg.SSHTunnel, - TLSConfig: &tls.Config{ - MinVersion: tls.VersionTLS12, - ServerName: cfg.Server, - }, - AllowFlood: false, - }) - c.registerHandlers() - return c -} - -// Connect starts the IRC connection and returns once registration succeeds. -func (c *Client) Connect() error { - connectedCh := make(chan struct{}, 1) - errCh := make(chan error, 1) - var connectedOnce sync.Once - - c.client.Handlers.Add(girc.CONNECTED, func(gc *girc.Client, e girc.Event) { - c.mu.Lock() - c.connected = true - c.mu.Unlock() - - c.emit(Event{ - Type: "system", - Target: ServerTarget, - Text: fmt.Sprintf("Connected to %s", gc.Server()), - Timestamp: eventTime(e), - }) - - c.identifyWithNickServ(gc) - if len(c.cfg.AutoJoinRooms) > 0 { - gc.Cmd.Join(c.cfg.AutoJoinRooms...) - } - c.emitRooms() - - connectedOnce.Do(func() { - select { - case connectedCh <- struct{}{}: - default: - } - }) - }) - - go func() { - var err error - if c.cfg.SSHTunnel { - dialer, dialerErr := c.newSSHDialer() - if dialerErr != nil { - select { - case errCh <- dialerErr: - default: - } - return - } - err = c.client.DialerConnect(dialer) - } else { - err = c.client.Connect() - } - - c.mu.Lock() - wasClosing := c.closing - wasConnected := c.connected - c.connected = false - c.mu.Unlock() - - if err != nil && !wasClosing { - c.emit(Event{ - Type: "error", - Target: ServerTarget, - Text: err.Error(), - Timestamp: time.Now().UnixMilli(), - }) - if c.cfg.OnError != nil { - c.cfg.OnError(err) - } - select { - case errCh <- err: - default: - } - } - if wasConnected && !wasClosing && c.cfg.OnClose != nil { - c.cfg.OnClose() - } - }() - - select { - case <-connectedCh: - return nil - case err := <-errCh: - return err - case <-time.After(c.cfg.ConnectTimeout): - c.Close() - return fmt.Errorf("IRC connection timed out after %s", c.cfg.ConnectTimeout) - } -} - -// Close disconnects from IRC and closes any SSH tunnel resources. -func (c *Client) Close() { - c.mu.Lock() - if c.closing { - c.mu.Unlock() - return - } - c.closing = true - c.mu.Unlock() - - if c.client != nil { - c.client.Quit("Alchementary Link disconnected") - c.client.Close() - } - if c.sshClient != nil { - _ = c.sshClient.Close() - c.sshClient = nil - } - if c.agentConn != nil { - _ = c.agentConn.Close() - c.agentConn = nil - } -} - -// HandleInput parses a user-entered IRC composer line. -func (c *Client) HandleInput(target string, input string) error { - text := strings.TrimSpace(input) - if text == "" { - return nil - } - if target == "" { - target = ServerTarget - } - - if !strings.HasPrefix(text, "/") || text == "/" { - if target == ServerTarget { - return fmt.Errorf("select a room or use /msg ") - } - c.client.Cmd.Message(target, text) - c.emitLocalMessage("message", target, text) - return nil - } - - commandLine := strings.TrimSpace(strings.TrimPrefix(text, "/")) - if commandLine == "" { - return nil - } - fields := strings.Fields(commandLine) - cmd := strings.ToLower(fields[0]) - args := strings.TrimSpace(commandLine[len(fields[0]):]) - - switch cmd { - case "me", "action": - if target == ServerTarget { - return fmt.Errorf("/me requires an active room or private tab") - } - if args == "" { - return fmt.Errorf("/me requires an action") - } - c.client.Cmd.Action(target, args) - c.emitLocalMessage("action", target, args) - case "msg", "query": - recipient, body, ok := splitFirstArg(args) - if !ok || body == "" { - return fmt.Errorf("/msg requires a target and message") - } - c.client.Cmd.Message(recipient, body) - c.emitLocalMessage("message", recipient, body) - case "notice": - recipient, body, ok := splitFirstArg(args) - if !ok || body == "" { - return fmt.Errorf("/notice requires a target and message") - } - c.client.Cmd.Notice(recipient, body) - c.emitLocalMessage("notice", recipient, body) - case "join", "j": - room, key, ok := splitFirstArg(args) - if !ok { - return fmt.Errorf("/join requires a room") - } - if key != "" { - c.client.Cmd.JoinKey(room, key) - } else { - c.client.Cmd.Join(room) - } - case "part", "leave": - room, msg, _ := splitFirstArg(args) - if room == "" { - room = target - } - if room == "" || room == ServerTarget { - return fmt.Errorf("/part requires a room") - } - if msg != "" { - c.client.Cmd.PartMessage(room, msg) - } else { - c.client.Cmd.Part(room) - } - case "quit": - if args == "" { - args = "Alchementary Link disconnected" - } - c.client.Quit(args) - c.client.Close() - case "nick": - if args == "" { - return fmt.Errorf("/nick requires a nickname") - } - c.client.Cmd.Nick(strings.Fields(args)[0]) - case "topic": - room, topic, _ := splitFirstArg(args) - if room == "" { - room = target - } - if room == "" || room == ServerTarget { - return fmt.Errorf("/topic requires a room") - } - c.client.Cmd.Topic(room, topic) - case "list": - if args == "" { - c.client.Cmd.List() - } else { - c.client.Cmd.List(strings.Fields(args)...) - } - case "names": - room := args - if room == "" && target != ServerTarget { - room = target - } - if room == "" { - return c.client.Cmd.SendRaw("NAMES") - } - return c.client.Cmd.SendRaw("NAMES " + room) - case "whois": - if args == "" { - return fmt.Errorf("/whois requires a nickname") - } - c.client.Cmd.Whois(strings.Fields(args)...) - case "who": - if args == "" { - if target == ServerTarget { - return fmt.Errorf("/who requires a target") - } - args = target - } - c.client.Cmd.Who(strings.Fields(args)...) - case "raw", "quote": - if args == "" { - return fmt.Errorf("/raw requires an IRC command") - } - return c.client.Cmd.SendRawNoSplit(args) - case "help": - c.emit(Event{ - Type: "system", - Target: target, - Text: "Commands: /join, /part, /msg, /notice, /me, /nick, /topic, /list, /names, /whois, /who, /raw, /quit", - Timestamp: time.Now().UnixMilli(), - }) - default: - raw := strings.ToUpper(fields[0]) - if args != "" { - raw += " " + args - } - return c.client.Cmd.SendRawNoSplit(raw) - } - - return nil -} - -// Join requests a room join. -func (c *Client) Join(room string) error { - room = strings.TrimSpace(room) - if room == "" { - return fmt.Errorf("room is required") - } - c.client.Cmd.Join(room) - return nil -} - -// Part leaves a room. -func (c *Client) Part(room string) error { - room = strings.TrimSpace(room) - if room == "" { - return fmt.Errorf("room is required") - } - c.client.Cmd.Part(room) - return nil -} - -// ListRooms requests a server-side room list. Results arrive as events. -func (c *Client) ListRooms() error { - c.mu.Lock() - c.listRooms = nil - c.mu.Unlock() - c.client.Cmd.List() - return nil -} - -// Rooms returns currently joined rooms. -func (c *Client) Rooms() []RoomInfo { - if c.client == nil { - return nil - } - c.mu.Lock() - roomNames := append([]string{}, c.roomNames...) - c.mu.Unlock() - rooms := make([]RoomInfo, 0, len(roomNames)) - for _, roomName := range roomNames { - ch := c.client.LookupChannel(roomName) - if ch == nil { - rooms = append(rooms, RoomInfo{Name: roomName}) - continue - } - rooms = append(rooms, RoomInfo{ - Name: ch.Name, - Topic: ch.Topic, - Users: len(ch.UserList), - }) - } - sort.Slice(rooms, func(i, j int) bool { - return strings.ToLower(rooms[i].Name) < strings.ToLower(rooms[j].Name) - }) - return rooms -} - -// Users returns known users in a room. -func (c *Client) Users(room string) []string { - if c.client == nil { - return nil - } - ch := c.client.LookupChannel(room) - if ch == nil { - return []string{} - } - users := append([]string{}, ch.UserList...) - sort.Slice(users, func(i, j int) bool { - return strings.TrimLeft(users[i], "~&@%+") < strings.TrimLeft(users[j], "~&@%+") - }) - return users -} - -func (c *Client) registerHandlers() { - c.client.Handlers.Add(girc.PRIVMSG, c.handleMessage) - c.client.Handlers.Add(girc.NOTICE, c.handleNotice) - c.client.Handlers.Add(girc.JOIN, c.handleJoin) - c.client.Handlers.Add(girc.PART, c.handlePart) - c.client.Handlers.Add(girc.KICK, c.handleKick) - c.client.Handlers.Add(girc.QUIT, c.handleQuit) - c.client.Handlers.Add(girc.NICK, c.handleNick) - c.client.Handlers.Add(girc.TOPIC, c.handleTopic) - c.client.Handlers.Add(girc.RPL_TOPIC, c.handleTopic) - c.client.Handlers.Add(girc.RPL_NAMREPLY, c.handleNames) - c.client.Handlers.Add(girc.RPL_ENDOFNAMES, c.handleEndOfNames) - c.client.Handlers.Add(girc.RPL_LIST, c.handleList) - c.client.Handlers.Add(girc.RPL_LISTEND, c.handleListEnd) - c.client.Handlers.Add(girc.ERROR, func(_ *girc.Client, e girc.Event) { - c.emit(Event{Type: "error", Target: ServerTarget, Text: e.Last(), Timestamp: eventTime(e)}) - }) -} - -func (c *Client) handleMessage(gc *girc.Client, e girc.Event) { - if len(e.Params) < 2 || e.Source == nil { - return - } - target := e.Params[0] - if strings.EqualFold(target, gc.GetNick()) { - target = e.Source.Name - } - eventType := "message" - text := e.Last() - if e.IsAction() { - eventType = "action" - text = e.StripAction() - } - c.emit(Event{Type: eventType, Target: target, From: e.Source.Name, Text: text, Timestamp: eventTime(e)}) -} - -func (c *Client) handleNotice(gc *girc.Client, e girc.Event) { - target := ServerTarget - if len(e.Params) > 0 { - target = e.Params[0] - if target == "*" { - target = ServerTarget - } - if strings.EqualFold(target, gc.GetNick()) && e.Source != nil { - target = e.Source.Name - } - } - from := "" - if e.Source != nil { - from = e.Source.Name - } - c.emit(Event{Type: "notice", Target: target, From: from, Text: e.Last(), Timestamp: eventTime(e)}) -} - -func (c *Client) handleJoin(gc *girc.Client, e girc.Event) { - room := eventRoom(e) - if room == "" { - return - } - from := sourceName(e) - if strings.EqualFold(from, gc.GetNick()) { - c.rememberRoomMembership(room, true) - } - c.emit(Event{Type: "join", Target: room, From: from, Text: fmt.Sprintf("%s joined %s", from, room), Timestamp: eventTime(e)}) - c.emitRooms() - c.emitUsers(room) -} - -func (c *Client) handlePart(gc *girc.Client, e girc.Event) { - room := eventRoom(e) - if room == "" { - return - } - from := sourceName(e) - message := "" - if len(e.Params) > 1 { - message = strings.TrimSpace(e.Last()) - } - if strings.EqualFold(from, gc.GetNick()) { - c.rememberRoomMembership(room, false) - } - c.emit(Event{Type: "part", Target: room, From: from, Text: message, Timestamp: eventTime(e)}) - c.emitRooms() - c.emitUsers(room) -} - -func (c *Client) handleKick(gc *girc.Client, e girc.Event) { - if len(e.Params) < 2 { - return - } - room := e.Params[0] - kicked := e.Params[1] - if strings.EqualFold(kicked, gc.GetNick()) { - c.rememberRoomMembership(room, false) - } - c.emit(Event{ - Type: "kick", - Target: room, - From: sourceName(e), - Command: kicked, - Text: strings.TrimSpace(e.Last()), - Timestamp: eventTime(e), - }) - c.emitRooms() - c.emitUsers(room) -} - -func (c *Client) handleQuit(gc *girc.Client, e girc.Event) { - from := sourceName(e) - c.emit(Event{Type: "quit", Target: ServerTarget, From: from, Text: strings.TrimSpace(e.Last()), Timestamp: eventTime(e)}) - c.emitUsersForAll() -} - -func (c *Client) handleNick(gc *girc.Client, e girc.Event) { - from := sourceName(e) - newNick := e.Last() - c.emit(Event{Type: "nick", Target: ServerTarget, From: from, Text: newNick, Timestamp: eventTime(e)}) - c.emitUsersForAll() -} - -func (c *Client) handleTopic(gc *girc.Client, e girc.Event) { - room := "" - topic := e.Last() - if e.Command == girc.RPL_TOPIC && len(e.Params) >= 3 { - room = e.Params[1] - topic = e.Params[2] - } else if len(e.Params) >= 2 { - room = e.Params[0] - topic = e.Params[1] - } - if room == "" { - return - } - c.emit(Event{Type: "topic", Target: room, From: sourceName(e), Text: topic, Topic: topic, Timestamp: eventTime(e)}) - c.emitRooms() -} - -func (c *Client) handleNames(gc *girc.Client, e girc.Event) { - if len(e.Params) >= 4 { - c.emit(Event{Type: "users", Target: e.Params[2], Users: splitNames(e.Params[3]), Timestamp: eventTime(e)}) - } -} - -func (c *Client) handleEndOfNames(gc *girc.Client, e girc.Event) { - if len(e.Params) >= 2 { - c.emitUsers(e.Params[1]) - } -} - -func (c *Client) handleList(gc *girc.Client, e girc.Event) { - if len(e.Params) < 4 { - return - } - users, _ := strconv.Atoi(e.Params[2]) - room := RoomInfo{Name: e.Params[1], Users: users, Topic: e.Params[3]} - c.mu.Lock() - if len(c.listRooms) < maxRoomListEntries { - c.listRooms = append(c.listRooms, room) - } - c.mu.Unlock() -} - -func (c *Client) handleListEnd(gc *girc.Client, e girc.Event) { - c.mu.Lock() - rooms := append([]RoomInfo{}, c.listRooms...) - c.listRooms = nil - c.mu.Unlock() - sort.Slice(rooms, func(i, j int) bool { - if rooms[i].Users == rooms[j].Users { - return strings.ToLower(rooms[i].Name) < strings.ToLower(rooms[j].Name) - } - return rooms[i].Users > rooms[j].Users - }) - c.emit(Event{Type: "room-list", Target: ServerTarget, Rooms: rooms, Timestamp: eventTime(e)}) -} - -func (c *Client) identifyWithNickServ(gc *girc.Client) { - if !c.cfg.NickServIdentify || c.cfg.NickServPassword == "" { - return - } - if c.cfg.NickServAccount != "" { - gc.Cmd.Message("NickServ", fmt.Sprintf("IDENTIFY %s %s", c.cfg.NickServAccount, c.cfg.NickServPassword)) - } else { - gc.Cmd.Message("NickServ", "IDENTIFY "+c.cfg.NickServPassword) - } - c.emit(Event{Type: "system", Target: ServerTarget, Text: "Sent NickServ identify request", Timestamp: time.Now().UnixMilli()}) -} - -func (c *Client) newSSHDialer() (*sshDialer, error) { - if c.cfg.SSHHostKeyCallback == nil { - return nil, fmt.Errorf("SSH host key callback is required") - } - authMethods, err := c.sshAuthMethods() - if err != nil { - return nil, err - } - sshAddr := net.JoinHostPort(c.cfg.SSHHost, strconv.Itoa(c.cfg.SSHPort)) - sshConfig := &ssh.ClientConfig{ - User: c.cfg.SSHUsername, - Auth: authMethods, - HostKeyCallback: c.cfg.SSHHostKeyCallback, - Timeout: c.cfg.ConnectTimeout, - } - sshClient, err := ssh.Dial("tcp", sshAddr, sshConfig) - if err != nil { - return nil, fmt.Errorf("SSH connection failed: %w", err) - } - c.sshClient = sshClient - return &sshDialer{ - client: sshClient, - useTLS: c.cfg.UseTLS, - serverName: c.cfg.Server, - timeout: c.cfg.ConnectTimeout, - }, nil -} - -func (c *Client) sshAuthMethods() ([]ssh.AuthMethod, error) { - var authMethods []ssh.AuthMethod - if c.cfg.SSHAgentAuth && c.cfg.SSHAgentConn != nil { - c.agentConn = c.cfg.SSHAgentConn - ag := agent.NewClient(c.cfg.SSHAgentConn) - signers, err := ag.Signers() - if err == nil && len(signers) > 0 { - authMethods = append(authMethods, ssh.PublicKeys(signers...)) - } - } - if len(c.cfg.SSHPrivateKey) > 0 { - var signer ssh.Signer - var err error - if c.cfg.SSHPassphrase != "" { - signer, err = ssh.ParsePrivateKeyWithPassphrase(c.cfg.SSHPrivateKey, []byte(c.cfg.SSHPassphrase)) - } else { - signer, err = ssh.ParsePrivateKey(c.cfg.SSHPrivateKey) - } - if err != nil { - return nil, fmt.Errorf("parsing SSH private key: %w", err) - } - authMethods = append(authMethods, ssh.PublicKeys(signer)) - } - if c.cfg.SSHPassword != "" { - authMethods = append(authMethods, ssh.Password(c.cfg.SSHPassword)) - } - if len(authMethods) == 0 { - return nil, fmt.Errorf("no SSH authentication method available") - } - return authMethods, nil -} - -func (c *Client) emitRooms() { - rooms := c.Rooms() - c.emit(Event{Type: "rooms", Target: ServerTarget, Rooms: rooms, Timestamp: time.Now().UnixMilli()}) -} - -func (c *Client) rememberRoomMembership(room string, joined bool) { - room = strings.TrimSpace(room) - if room == "" { - return - } - - c.mu.Lock() - position := -1 - changed := false - for i, existing := range c.roomNames { - if strings.EqualFold(existing, room) { - position = i - break - } - } - if joined && position == -1 { - c.roomNames = append(c.roomNames, room) - changed = true - } - if !joined && position >= 0 { - c.roomNames = append(c.roomNames[:position], c.roomNames[position+1:]...) - changed = true - } - names := append([]string{}, c.roomNames...) - c.mu.Unlock() - - if changed && c.cfg.OnRoomsChanged != nil { - c.cfg.OnRoomsChanged(names) - } -} - -func (c *Client) emitUsers(room string) { - if room == "" { - return - } - c.emit(Event{Type: "users", Target: room, Users: c.Users(room), Timestamp: time.Now().UnixMilli()}) -} - -func (c *Client) emitUsersForAll() { - for _, room := range c.Rooms() { - c.emitUsers(room.Name) - } -} - -func (c *Client) emitLocalMessage(eventType string, target string, text string) { - c.emit(Event{Type: eventType, Target: target, From: c.client.GetNick(), Text: text, Timestamp: time.Now().UnixMilli()}) -} - -func (c *Client) emit(event Event) { - if event.Timestamp == 0 { - event.Timestamp = time.Now().UnixMilli() - } - if event.Target == "" { - event.Target = ServerTarget - } - if c.cfg.OnEvent != nil { - c.cfg.OnEvent(event) - } -} - -type sshDialer struct { - client *ssh.Client - useTLS bool - serverName string - timeout time.Duration -} - -func (d *sshDialer) Dial(network, address string) (net.Conn, error) { - conn, err := d.client.Dial(network, address) - if err != nil { - return nil, err - } - if !d.useTLS { - return conn, nil - } - tlsConn := tls.Client(conn, &tls.Config{ - MinVersion: tls.VersionTLS12, - ServerName: d.serverName, - }) - if d.timeout > 0 { - _ = tlsConn.SetDeadline(time.Now().Add(d.timeout)) - } - if err := tlsConn.Handshake(); err != nil { - _ = conn.Close() - return nil, fmt.Errorf("IRC TLS handshake failed: %w", err) - } - _ = tlsConn.SetDeadline(time.Time{}) - return tlsConn, nil -} - -func eventTime(e girc.Event) int64 { - if e.Timestamp.IsZero() { - return time.Now().UnixMilli() - } - return e.Timestamp.UnixMilli() -} - -func eventRoom(e girc.Event) string { - if len(e.Params) == 0 { - return "" - } - return e.Params[0] -} - -func sourceName(e girc.Event) string { - if e.Source == nil { - return "" - } - return e.Source.Name -} - -func splitFirstArg(input string) (first string, rest string, ok bool) { - fields := strings.Fields(input) - if len(fields) == 0 { - return "", "", false - } - first = fields[0] - rest = strings.TrimSpace(input[len(first):]) - return first, rest, true -} - -func splitNames(input string) []string { - fields := strings.Fields(input) - names := make([]string, 0, len(fields)) - for _, name := range fields { - name = strings.TrimSpace(name) - if name != "" { - names = append(names, name) - } - } - return names -} diff --git a/internal/ircclient/ircclient_test.go b/internal/ircclient/ircclient_test.go deleted file mode 100644 index d050cd3..0000000 --- a/internal/ircclient/ircclient_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package ircclient - -import ( - "strings" - "testing" -) - -func TestRememberRoomMembershipTracksConfirmedChannels(t *testing.T) { - var updates []string - client := &Client{cfg: Config{ - OnRoomsChanged: func(rooms []string) { - updates = append(updates, strings.Join(rooms, ",")) - }, - }} - - client.rememberRoomMembership("#One", true) - client.rememberRoomMembership("#two", true) - client.rememberRoomMembership("#one", true) - client.rememberRoomMembership("#missing", false) - client.rememberRoomMembership("#ONE", false) - - if got, want := strings.Join(updates, "|"), "#One|#One,#two|#two"; got != want { - t.Fatalf("room updates = %q, want %q", got, want) - } - if got := strings.Join(client.roomNames, ","); got != "#two" { - t.Fatalf("remembered rooms = %q, want %q", got, "#two") - } -} diff --git a/internal/knownhosts/knownhosts.go b/internal/knownhosts/knownhosts.go deleted file mode 100644 index 928dbe1..0000000 --- a/internal/knownhosts/knownhosts.go +++ /dev/null @@ -1,98 +0,0 @@ -// Package knownhosts manages SSH known host entries, providing verification, -// storage, and management of host key fingerprints. -package knownhosts - -import ( - "fmt" - "time" - - "github.com/andydixon/link/internal/models" - "github.com/google/uuid" -) - -// KnownHostStorage is the persistence interface used by the Store. -type KnownHostStorage interface { - GetKnownHost(host string, port int) (*models.KnownHost, error) - SaveKnownHost(kh *models.KnownHost) error - ListKnownHosts() ([]*models.KnownHost, error) - DeleteKnownHost(id string) error -} - -// Store manages SSH known host entries. It provides verification against -// previously accepted host keys and allows new keys to be trusted. -type Store struct { - storage KnownHostStorage -} - -// NewStore creates a Store backed by the given storage implementation. -func NewStore(storage KnownHostStorage) *Store { - return &Store{storage: storage} -} - -// Verify checks whether the presented host key matches a previously accepted -// entry. Returns true if the fingerprint matches, false if there is no record, -// and an error if the key has changed (possible MITM). -func (s *Store) Verify(host string, port int, keyType string, fingerprint string, publicKey string) (bool, error) { - existing, err := s.storage.GetKnownHost(host, port) - if err != nil { - return false, fmt.Errorf("knownhosts: lookup failed: %w", err) - } - if existing == nil { - // No prior record — the caller should prompt the user. - return false, nil - } - - if existing.Fingerprint == fingerprint { - // Key matches — update the last-seen timestamp. - existing.LastSeen = time.Now() - _ = s.storage.SaveKnownHost(existing) - return true, nil - } - - // Fingerprint mismatch — the host key has changed. - return false, fmt.Errorf( - "knownhosts: host key for %s:%d has changed (was %s %s, now %s %s); "+ - "this could indicate a man-in-the-middle attack", - host, port, - existing.KeyType, existing.Fingerprint, - keyType, fingerprint, - ) -} - -// Accept stores a new host key as trusted. If an entry already exists for the -// host/port combination it is overwritten. -func (s *Store) Accept(host string, port int, keyType string, fingerprint string, publicKey string) error { - now := time.Now() - kh := &models.KnownHost{ - ID: uuid.New().String(), - Host: host, - Port: port, - KeyType: keyType, - Fingerprint: fingerprint, - PublicKey: publicKey, - FirstSeen: now, - LastSeen: now, - Trusted: true, - } - if err := s.storage.SaveKnownHost(kh); err != nil { - return fmt.Errorf("knownhosts: failed to accept key: %w", err) - } - return nil -} - -// List returns all stored known host entries. -func (s *Store) List() ([]*models.KnownHost, error) { - entries, err := s.storage.ListKnownHosts() - if err != nil { - return nil, fmt.Errorf("knownhosts: failed to list entries: %w", err) - } - return entries, nil -} - -// Delete removes a known host entry by ID. -func (s *Store) Delete(id string) error { - if err := s.storage.DeleteKnownHost(id); err != nil { - return fmt.Errorf("knownhosts: failed to delete entry: %w", err) - } - return nil -} diff --git a/internal/knownhosts/knownhosts_test.go b/internal/knownhosts/knownhosts_test.go deleted file mode 100644 index bf018d7..0000000 --- a/internal/knownhosts/knownhosts_test.go +++ /dev/null @@ -1,142 +0,0 @@ -package knownhosts - -import ( - "testing" - - "github.com/andydixon/link/internal/models" -) - -// --------------------------------------------------------------------------- -// Mock storage implementation -// --------------------------------------------------------------------------- - -type mockStorage struct { - hosts map[string]*models.KnownHost // keyed by "host:port" -} - -func newMockStorage() *mockStorage { - return &mockStorage{hosts: make(map[string]*models.KnownHost)} -} - -func (m *mockStorage) key(host string, port int) string { - return host + ":" + string(rune(port)) -} - -func (m *mockStorage) GetKnownHost(host string, port int) (*models.KnownHost, error) { - kh, ok := m.hosts[m.key(host, port)] - if !ok { - return nil, nil - } - return kh, nil -} - -func (m *mockStorage) SaveKnownHost(kh *models.KnownHost) error { - m.hosts[m.key(kh.Host, kh.Port)] = kh - return nil -} - -func (m *mockStorage) ListKnownHosts() ([]*models.KnownHost, error) { - result := make([]*models.KnownHost, 0, len(m.hosts)) - for _, kh := range m.hosts { - result = append(result, kh) - } - return result, nil -} - -func (m *mockStorage) DeleteKnownHost(id string) error { - for k, kh := range m.hosts { - if kh.ID == id { - delete(m.hosts, k) - return nil - } - } - return nil -} - -// --------------------------------------------------------------------------- -// Verify unknown host returns false (no error) -// --------------------------------------------------------------------------- - -func TestVerifyUnknownHost(t *testing.T) { - store := NewStore(newMockStorage()) - - trusted, err := store.Verify("unknown.example.com", 22, "ssh-ed25519", "SHA256:abc", "pubkey") - if err != nil { - t.Fatalf("Verify() unexpected error = %v", err) - } - if trusted { - t.Error("expected trusted = false for unknown host") - } -} - -// --------------------------------------------------------------------------- -// Accept then verify returns true -// --------------------------------------------------------------------------- - -func TestAcceptThenVerify(t *testing.T) { - store := NewStore(newMockStorage()) - - if err := store.Accept("server.example.com", 22, "ssh-ed25519", "SHA256:abc", "pubkey"); err != nil { - t.Fatalf("Accept() error = %v", err) - } - - trusted, err := store.Verify("server.example.com", 22, "ssh-ed25519", "SHA256:abc", "pubkey") - if err != nil { - t.Fatalf("Verify() error = %v", err) - } - if !trusted { - t.Error("expected trusted = true after Accept()") - } -} - -// --------------------------------------------------------------------------- -// Changed fingerprint returns error -// --------------------------------------------------------------------------- - -func TestVerifyChangedFingerprint(t *testing.T) { - store := NewStore(newMockStorage()) - - // Accept with fingerprint A. - if err := store.Accept("host.local", 22, "ssh-rsa", "SHA256:original", "pubkeyA"); err != nil { - t.Fatalf("Accept() error = %v", err) - } - - // Verify with fingerprint B — should return an error indicating a change. - trusted, err := store.Verify("host.local", 22, "ssh-rsa", "SHA256:changed", "pubkeyB") - if err == nil { - t.Fatal("expected error for changed fingerprint") - } - if trusted { - t.Error("expected trusted = false for changed fingerprint") - } -} - -// --------------------------------------------------------------------------- -// List and Delete operations -// --------------------------------------------------------------------------- - -func TestListAndDelete(t *testing.T) { - mock := newMockStorage() - store := NewStore(mock) - - _ = store.Accept("host-a.local", 22, "ssh-ed25519", "fp-a", "pk-a") - _ = store.Accept("host-b.local", 22, "ssh-rsa", "fp-b", "pk-b") - - entries, err := store.List() - if err != nil { - t.Fatalf("List() error = %v", err) - } - if len(entries) != 2 { - t.Fatalf("expected 2 entries, got %d", len(entries)) - } - - // Delete the first entry. - if err := store.Delete(entries[0].ID); err != nil { - t.Fatalf("Delete() error = %v", err) - } - - entries, _ = store.List() - if len(entries) != 1 { - t.Errorf("expected 1 entry after delete, got %d", len(entries)) - } -} diff --git a/internal/leviathan/analysis/carve.go b/internal/leviathan/analysis/carve.go deleted file mode 100644 index 31e50c7..0000000 --- a/internal/leviathan/analysis/carve.go +++ /dev/null @@ -1,876 +0,0 @@ -package analysis - -import ( - "bytes" - "compress/gzip" - "context" - "encoding/binary" - "io" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// Signature describes a carveable object. `validate` is run against -// the candidate start and returns the object length if it is a real -// object, or 0 to reject. `trailer`, if present, must appear before -// the object ends (for simple trailer-based terminations). -type Signature struct { - Kind string - Magic []byte - Extension string - // Anchor is how many bytes into the object the magic appears. It is 0 for - // almost every format (magic at offset 0), but a few — notably ISO-BMFF - // (MP4/MOV), whose `ftyp` magic follows a 4-byte box size — carry it. - Anchor int64 - Validate func(d *document.Document, start, max int64) (length int64, confidence float64) -} - -// carveSignatures is the phase-1 signature set. We stay conservative -// and only declare a carve when we can determine length reliably. -var carveSignatures = []Signature{ - {Kind: "png", Magic: []byte{0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a}, Extension: "png", Validate: validatePNG}, - {Kind: "jpeg", Magic: []byte{0xff, 0xd8, 0xff}, Extension: "jpg", Validate: validateJPEG}, - {Kind: "gif87a", Magic: []byte("GIF87a"), Extension: "gif", Validate: validateGIF}, - {Kind: "gif89a", Magic: []byte("GIF89a"), Extension: "gif", Validate: validateGIF}, - {Kind: "zip", Magic: []byte{'P', 'K', 0x03, 0x04}, Extension: "zip", Validate: validateZIP}, - {Kind: "pdf", Magic: []byte("%PDF-"), Extension: "pdf", Validate: validatePDF}, - {Kind: "elf", Magic: []byte{0x7f, 'E', 'L', 'F'}, Extension: "elf", Validate: validateELF}, - {Kind: "mz", Magic: []byte{'M', 'Z'}, Extension: "exe", Validate: validatePE}, - {Kind: "gzip", Magic: []byte{0x1f, 0x8b, 0x08}, Extension: "gz", Validate: validateGZIP}, - {Kind: "bzip2", Magic: []byte{'B', 'Z', 'h'}, Extension: "bz2", Validate: validateBzip2}, - {Kind: "sevenz", Magic: []byte{'7', 'z', 0xbc, 0xaf, 0x27, 0x1c}, Extension: "7z", Validate: validate7z}, - {Kind: "rar4", Magic: []byte{'R', 'a', 'r', '!', 0x1a, 0x07, 0x00}, Extension: "rar", Validate: validateRAR}, - {Kind: "rar5", Magic: []byte{'R', 'a', 'r', '!', 0x1a, 0x07, 0x01, 0x00}, Extension: "rar", Validate: validateRAR}, - {Kind: "wav", Magic: []byte("RIFF"), Extension: "wav", Validate: validateRIFF}, - {Kind: "mach-o-le32", Magic: []byte{0xce, 0xfa, 0xed, 0xfe}, Extension: "macho", Validate: validateMachO}, - {Kind: "mach-o-le64", Magic: []byte{0xcf, 0xfa, 0xed, 0xfe}, Extension: "macho", Validate: validateMachO}, - {Kind: "mach-o-be32", Magic: []byte{0xfe, 0xed, 0xfa, 0xce}, Extension: "macho", Validate: validateMachO}, - {Kind: "mach-o-be64", Magic: []byte{0xfe, 0xed, 0xfa, 0xcf}, Extension: "macho", Validate: validateMachO}, - {Kind: "sqlite", Magic: []byte("SQLite format 3\x00"), Extension: "sqlite", Validate: validateSQLite}, - {Kind: "mp3", Magic: []byte("ID3"), Extension: "mp3", Validate: validateID3}, - {Kind: "ogg", Magic: []byte("OggS"), Extension: "ogg", Validate: validateOgg}, - {Kind: "matroska", Magic: []byte{0x1a, 0x45, 0xdf, 0xa3}, Extension: "mkv", Validate: validateMatroska}, - {Kind: "flac", Magic: []byte("fLaC"), Extension: "flac", Validate: validateFLAC}, - {Kind: "bmp", Magic: []byte{'B', 'M'}, Extension: "bmp", Validate: validateBMP}, - {Kind: "mp4", Magic: []byte("ftyp"), Anchor: 4, Extension: "mp4", Validate: validateMP4}, -} - -// Carve scans the document for known signatures and returns a list of -// discovered objects with offset, length, kind, and confidence. -func Carve(d *document.Document, start, length int64, maxHits int) ([]map[string]any, error) { - return CarveContext(context.Background(), d, start, length, maxHits) -} - -func CarveContext(ctx context.Context, d *document.Document, start, length int64, maxHits int) ([]map[string]any, error) { - if length < 0 { - length = d.Size() - start - } - if maxHits <= 0 { - maxHits = 10_000 - } - results := make([]map[string]any, 0, 64) - - // Build a flat list of (magicBytes, sigIndex) for a simple first-pass scan. - // Phase 1 uses per-signature naive scan chunks. - for idx, sig := range carveSignatures { - if err := ctx.Err(); err != nil { - return nil, err - } - hits, err := SearchBytesContext(ctx, d, start, length, sig.Magic, maxHits) - if err != nil { - return nil, err - } - for _, h := range hits { - if err := ctx.Err(); err != nil { - return nil, err - } - // The object begins Anchor bytes before the magic (0 for most formats). - objStart := h.Offset - sig.Anchor - if objStart < start { - continue - } - ln, conf := int64(0), 0.4 - if sig.Validate != nil { - ln, conf = sig.Validate(d, objStart, start+length) - } - if ln <= 0 { - // record low-confidence magic hit only for kinds where length is not determinable - if sig.Kind == "mz" || sig.Kind == "elf" || sig.Kind == "mach-o-le32" || - sig.Kind == "mach-o-le64" || sig.Kind == "mach-o-be32" || sig.Kind == "mach-o-be64" || - sig.Kind == "sqlite" || sig.Kind == "mp3" || sig.Kind == "matroska" || - sig.Kind == "flac" || sig.Kind == "bzip2" || sig.Kind == "rar4" || sig.Kind == "rar5" { - results = append(results, map[string]any{ - "kind": sig.Kind, - "extension": sig.Extension, - "offset": objStart, - "length": int64(0), - "confidence": 0.3, - "status": "magic-only", - "sigIndex": idx, - }) - } - continue - } - results = append(results, map[string]any{ - "kind": sig.Kind, - "extension": sig.Extension, - "offset": objStart, - "length": ln, - "confidence": conf, - "status": "valid", - "sigIndex": idx, - }) - if len(results) >= maxHits { - return results, nil - } - } - } - return results, nil -} - -// --- validators --- - -func validatePNG(d *document.Document, start, max int64) (int64, float64) { - // Walk chunks until IEND - off := start + 8 - for off < max { - hdr, err := d.Range(off, 8) - if err != nil || len(hdr) < 8 { - return 0, 0 - } - size := int64(binary.BigEndian.Uint32(hdr[0:4])) - typ := string(hdr[4:8]) - // Skip payload + CRC (4 bytes) - off += 8 + size + 4 - if typ == "IEND" { - return off - start, 0.95 - } - if off > max { - return 0, 0 - } - } - return 0, 0 -} - -func validateJPEG(d *document.Document, start, max int64) (int64, float64) { - // Walk segments until EOI (0xFFD9) - off := start + 2 - for off < max-1 { - buf, err := d.Range(off, 2) - if err != nil || len(buf) < 2 { - return 0, 0 - } - if buf[0] != 0xff { - return 0, 0 - } - marker := buf[1] - off += 2 - // No-length markers - if marker == 0xd9 { // EOI - return off - start, 0.95 - } - if marker == 0xd8 || (marker >= 0xd0 && marker <= 0xd7) || marker == 0x00 { - continue - } - // Read length - lb, err := d.Range(off, 2) - if err != nil || len(lb) < 2 { - return 0, 0 - } - ln := int64(binary.BigEndian.Uint16(lb)) - off += ln - if marker == 0xda { // SOS, scan until next 0xff!=0 - for off < max { - b, err := d.Range(off, 2) - if err != nil || len(b) < 2 { - return 0, 0 - } - if b[0] == 0xff && b[1] != 0x00 && !(b[1] >= 0xd0 && b[1] <= 0xd7) { - break - } - off++ - } - } - } - return 0, 0 -} - -func validateGIF(d *document.Document, start, max int64) (int64, float64) { - // Minimal: locate trailer 0x3B - buf, err := d.Range(start, min64(max-start, 1<<20)) - if err != nil { - return 0, 0 - } - idx := bytes.IndexByte(buf[13:], 0x3b) - if idx < 0 { - return 0, 0 - } - return int64(idx + 13 + 1), 0.7 -} - -func validateZIP(d *document.Document, start, max int64) (int64, float64) { - // Search forward for End of Central Directory record 0x06054b50. - const sig = "\x50\x4b\x05\x06" - limit := min64(max-start, 64<<20) - buf, err := d.Range(start, limit) - if err != nil { - return 0, 0 - } - idx := bytes.LastIndex(buf, []byte(sig)) - if idx < 0 { - return 0, 0 - } - // EOCD record is at least 22 bytes; read comment length. - if idx+22 > len(buf) { - return 0, 0 - } - commentLen := int(binary.LittleEndian.Uint16(buf[idx+20 : idx+22])) - end := int64(idx + 22 + commentLen) - return end, 0.9 -} - -func validatePDF(d *document.Document, start, max int64) (int64, float64) { - limit := min64(max-start, 64<<20) - buf, err := d.Range(start, limit) - if err != nil { - return 0, 0 - } - idx := bytes.LastIndex(buf, []byte("%%EOF")) - if idx < 0 { - return 0, 0 - } - return int64(idx + 5), 0.85 -} - -func validateELF(d *document.Document, start, max int64) (int64, float64) { - hdr, err := d.Range(start, 64) - if err != nil || len(hdr) < 52 { - return 0, 0 - } - if hdr[4] != 1 && hdr[4] != 2 { - return 0, 0 - } - is64 := hdr[4] == 2 - little := hdr[5] == 1 - var end int64 - if is64 { - if len(hdr) < 64 { - return 0, 0 - } - // e_shoff at 40, e_shentsize at 58, e_shnum at 60 - off := binary.LittleEndian.Uint64(hdr[40:48]) - entSz := uint64(binary.LittleEndian.Uint16(hdr[58:60])) - num := uint64(binary.LittleEndian.Uint16(hdr[60:62])) - if !little { - off = binary.BigEndian.Uint64(hdr[40:48]) - entSz = uint64(binary.BigEndian.Uint16(hdr[58:60])) - num = uint64(binary.BigEndian.Uint16(hdr[60:62])) - } - end = int64(off + entSz*num) - } else { - off := binary.LittleEndian.Uint32(hdr[32:36]) - entSz := uint32(binary.LittleEndian.Uint16(hdr[46:48])) - num := uint32(binary.LittleEndian.Uint16(hdr[48:50])) - if !little { - off = binary.BigEndian.Uint32(hdr[32:36]) - entSz = uint32(binary.BigEndian.Uint16(hdr[46:48])) - num = uint32(binary.BigEndian.Uint16(hdr[48:50])) - } - end = int64(off) + int64(entSz)*int64(num) - } - if end <= 0 || start+end > max { - return 0, 0.5 - } - return end, 0.9 -} - -func validatePE(d *document.Document, start, max int64) (int64, float64) { - // e_lfanew at offset 0x3C points to PE header - hdr, err := d.Range(start, 64) - if err != nil || len(hdr) < 64 { - return 0, 0 - } - peOff := int64(binary.LittleEndian.Uint32(hdr[0x3c:0x40])) - if peOff < 0x40 || start+peOff+4 > max { - return 0, 0 - } - sig, err := d.Range(start+peOff, 4) - if err != nil || string(sig) != "PE\x00\x00" { - return 0, 0.3 - } - coff, err := d.Range(start+peOff+4, 20) - if err != nil || len(coff) < 20 { - return 0, 0.4 - } - numSections := int(binary.LittleEndian.Uint16(coff[2:4])) - sizeOpt := int64(binary.LittleEndian.Uint16(coff[16:18])) - if numSections <= 0 || numSections > 96 || sizeOpt < 0 || sizeOpt > 4096 { - return 0, 0.5 - } - sectOff := start + peOff + 24 + sizeOpt - end := peOff + 24 + sizeOpt + int64(numSections*40) - for i := 0; i < numSections; i++ { - sh, err := d.Range(sectOff+int64(i*40), 40) - if err != nil || len(sh) < 40 { - return 0, 0.5 - } - rawSize := int64(binary.LittleEndian.Uint32(sh[16:20])) - rawPtr := int64(binary.LittleEndian.Uint32(sh[20:24])) - if rawSize < 0 || rawPtr < 0 { - return 0, 0.5 - } - if rawSize > 0 && rawPtr+rawSize > end { - end = rawPtr + rawSize - } - } - if end <= peOff || start+end > max { - return 0, 0.5 - } - return end, 0.85 -} - -func validateGZIP(d *document.Document, start, max int64) (int64, float64) { - limit := min64(max-start, 64<<20) - buf, err := d.Range(start, limit) - if err != nil || len(buf) < 18 { - return 0, 0 - } - br := bytes.NewReader(buf) - zr, err := gzip.NewReader(br) - if err != nil { - return 0, 0 - } - zr.Multistream(false) - if _, err := io.Copy(io.Discard, io.LimitReader(zr, 512<<20)); err != nil { - _ = zr.Close() - return 0, 0 - } - if err := zr.Close(); err != nil { - return 0, 0 - } - consumed := int64(len(buf)) - int64(br.Len()) - if consumed <= 10 || start+consumed > max { - return 0, 0 - } - return consumed, 0.9 -} - -// validateBzip2 locates the bit-aligned end-of-stream marker (0x177245385090, -// the "sqrt(pi)" magic) and returns the stream length including the trailing -// 32-bit combined CRC. bzip2 carries no length in its header, so this is the -// only reliable way to bound the object. -func validateBzip2(d *document.Document, start, max int64) (int64, float64) { - if start+4 > max { - return 0, 0.4 - } - hdr, err := d.Range(start, 4) - if err != nil || len(hdr) < 4 || hdr[0] != 'B' || hdr[1] != 'Z' || hdr[2] != 'h' || hdr[3] < '1' || hdr[3] > '9' { - return 0, 0 - } - limit := min64(max-start, 64<<20) - buf, err := d.Range(start, limit) - if err != nil || len(buf) < 14 { - return 0, 0.4 - } - const eos uint64 = 0x177245385090 - const mask48 uint64 = (1 << 48) - 1 - var w uint64 - for i := 0; i < len(buf); i++ { - w = (w << 8) | uint64(buf[i]) - // Check all eight bit alignments ending at or before this byte. - for s := 0; s < 8; s++ { - if (w>>uint(s))&mask48 == eos { - endBit := (int64(i)+1)*8 - int64(s) + 32 // include stream CRC - endByte := (endBit + 7) / 8 - if endByte > 14 && start+endByte <= max { - return endByte, 0.85 - } - } - } - } - return 0, 0.4 -} - -func validate7z(d *document.Document, start, max int64) (int64, float64) { - hdr, err := d.Range(start, 32) - if err != nil || len(hdr) < 32 { - return 0, 0 - } - nextHdrOff := binary.LittleEndian.Uint64(hdr[12:20]) - nextHdrSz := binary.LittleEndian.Uint64(hdr[20:28]) - end := int64(32 + nextHdrOff + nextHdrSz) - if end <= 0 || start+end > max { - return 0, 0.5 - } - return end, 0.85 -} - -// validateRAR walks the block headers of a RAR4 or RAR5 archive to the -// end-of-archive marker. The two formats differ structurally, so the version -// is detected from the signature byte at offset 6. -func validateRAR(d *document.Document, start, max int64) (int64, float64) { - sig, err := d.Range(start, 8) - if err != nil || len(sig) < 8 { - return 0, 0 - } - if sig[6] == 0x01 { - return validateRAR5(d, start, max) - } - return validateRAR4(d, start, max) -} - -func validateRAR4(d *document.Document, start, max int64) (int64, float64) { - pos := start + 7 // 7-byte signature - for n := 0; n < 100000; n++ { - h, err := d.Range(pos, 11) - if err != nil || len(h) < 7 { - return 0, 0.4 - } - flags := binary.LittleEndian.Uint16(h[3:5]) - headSize := int64(binary.LittleEndian.Uint16(h[5:7])) - if headSize < 7 { - return 0, 0.4 - } - blockSize := headSize - if flags&0x8000 != 0 { // ADD_SIZE present - if len(h) < 11 { - return 0, 0.4 - } - blockSize += int64(binary.LittleEndian.Uint32(h[7:11])) - } - pos += blockSize - if h[2] == 0x7b { // terminator block - if pos > start && pos <= max { - return pos - start, 0.85 - } - return 0, 0.4 - } - if pos > max { - return 0, 0.4 - } - } - return 0, 0.4 -} - -func validateRAR5(d *document.Document, start, max int64) (int64, float64) { - pos := start + 8 // 8-byte signature - for n := 0; n < 100000; n++ { - // Each block: CRC32(4) + vint(header_size) + header_size bytes [+ data]. - h, err := d.Range(pos, 4+11) - if err != nil || len(h) < 5 { - return 0, 0.4 - } - headerSize, vlen := rar5Vint(h, 4) - if vlen == 0 || headerSize == 0 { - return 0, 0.4 - } - hdrStart := pos + 4 + int64(vlen) - body, err := d.Range(hdrStart, int64(headerSize)) - if err != nil || int64(len(body)) < int64(headerSize) { - return 0, 0.4 - } - htype, t1 := rar5Vint(body, 0) - hflags, t2 := rar5Vint(body, t1) - p := t1 + t2 - var dataSize uint64 - if hflags&0x0001 != 0 { // extra area present - _, te := rar5Vint(body, p) - p += te - } - if hflags&0x0002 != 0 { // data area present - dataSize, _ = rar5Vint(body, p) - } - pos = hdrStart + int64(headerSize) + int64(dataSize) - if htype == 5 { // end-of-archive header - if pos > start && pos <= max { - return pos - start, 0.85 - } - return 0, 0.4 - } - if pos > max { - return 0, 0.4 - } - } - return 0, 0.4 -} - -// rar5Vint decodes a RAR5 little-endian base-128 variable integer. -func rar5Vint(buf []byte, pos int) (uint64, int) { - var v uint64 - shift := uint(0) - for n := 0; pos+n < len(buf) && n < 10; n++ { - b := buf[pos+n] - v |= uint64(b&0x7f) << shift - if b&0x80 == 0 { - return v, n + 1 - } - shift += 7 - } - return 0, 0 -} - -func validateRIFF(d *document.Document, start, max int64) (int64, float64) { - hdr, err := d.Range(start, 12) - if err != nil || len(hdr) < 12 { - return 0, 0 - } - size := int64(binary.LittleEndian.Uint32(hdr[4:8])) - return size + 8, 0.9 -} - -func validateMachO(d *document.Document, start, max int64) (int64, float64) { - hdr, err := d.Range(start, 32) - if err != nil || len(hdr) < 28 { - return 0, 0 - } - var ( - bo binary.ByteOrder - is64 bool - hdrSize int64 = 28 - ) - switch binary.BigEndian.Uint32(hdr[0:4]) { - case 0xfeedface: - bo = binary.BigEndian - case 0xfeedfacf: - bo = binary.BigEndian - is64 = true - hdrSize = 32 - case 0xcefaedfe: - bo = binary.LittleEndian - case 0xcffaedfe: - bo = binary.LittleEndian - is64 = true - hdrSize = 32 - default: - return 0, 0 - } - if is64 && len(hdr) < 32 { - return 0, 0 - } - ncmds := bo.Uint32(hdr[16:20]) - sizeofcmds := bo.Uint32(hdr[20:24]) - if ncmds == 0 || ncmds > 10000 || sizeofcmds > 128<<20 { - return 0, 0 - } - cmdBytes, err := d.Range(start+hdrSize, int64(sizeofcmds)) - if err != nil || len(cmdBytes) < int(sizeofcmds) { - return 0, 0 - } - end := hdrSize + int64(sizeofcmds) - off := 0 - for i := uint32(0); i < ncmds; i++ { - if off+8 > len(cmdBytes) { - return 0, 0 - } - cmd := bo.Uint32(cmdBytes[off : off+4]) - cmdSize := int(bo.Uint32(cmdBytes[off+4 : off+8])) - if cmdSize < 8 || off+cmdSize > len(cmdBytes) { - return 0, 0 - } - switch cmd { - case 0x1: // LC_SEGMENT - if cmdSize >= 56 { - fileOff := int64(bo.Uint32(cmdBytes[off+32 : off+36])) - fileSize := int64(bo.Uint32(cmdBytes[off+36 : off+40])) - if fileOff >= 0 && fileSize >= 0 && fileOff+fileSize > end { - end = fileOff + fileSize - } - } - case 0x19: // LC_SEGMENT_64 - if cmdSize >= 72 { - fileOff := int64(bo.Uint64(cmdBytes[off+40 : off+48])) - fileSize := int64(bo.Uint64(cmdBytes[off+48 : off+56])) - if fileOff >= 0 && fileSize >= 0 && fileOff+fileSize > end { - end = fileOff + fileSize - } - } - } - off += cmdSize - } - if end <= hdrSize || start+end > max { - return 0, 0 - } - return end, 0.85 -} - -func validateID3(d *document.Document, start, max int64) (int64, float64) { - // ID3v2 header: "ID3" + ver(2) + flags(1) + size(4, synchsafe). - hdr, err := d.Range(start, 10) - if err != nil || len(hdr) < 10 { - return 0, 0.3 - } - if hdr[3] == 0xff || hdr[4] == 0xff { - return 0, 0.3 - } - // Synchsafe 28-bit size (7 bits per byte). - sz := (int64(hdr[6]&0x7f) << 21) | (int64(hdr[7]&0x7f) << 14) | - (int64(hdr[8]&0x7f) << 7) | int64(hdr[9]&0x7f) - if sz < 0 || sz > 32*1024*1024 { - return 0, 0.4 - } - // Skip the ID3v2 tag (header + synchsafe body, plus a 10-byte footer if the - // footer-present flag is set), then walk MPEG audio frames to the end. - audioStart := start + 10 + sz - if hdr[5]&0x10 != 0 { - audioStart += 10 - } - if end := walkMP3Frames(d, audioStart, max); end > audioStart { - return end - start, 0.85 - } - return 0, 0.7 -} - -// walkMP3Frames follows MPEG-1/2/2.5 Layer I/II/III frame headers from start -// until a byte that is not a valid frame sync, returning the offset just past -// the last frame. APE/Lyrics3 trailers are not stripped — the audio extent is -// what matters for carving. -func walkMP3Frames(d *document.Document, start, max int64) int64 { - pos := start - frames := 0 - for pos+4 <= max && frames < 5_000_000 { - h, err := d.Range(pos, 4) - if err != nil || len(h) < 4 { - break - } - ln := mp3FrameLen(h) - if ln <= 0 { - break - } - pos += ln - frames++ - } - if frames == 0 { - return 0 - } - if pos > max { - return max - } - return pos -} - -var mp3BitrateV1 = [16]int{0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0} -var mp3BitrateV2 = [16]int{0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0} -var mp3SampleRate = map[int][3]int{ // [version]{rate index 0,1,2} - 3: {44100, 48000, 32000}, // MPEG1 - 2: {22050, 24000, 16000}, // MPEG2 - 0: {11025, 12000, 8000}, // MPEG2.5 -} - -// mp3FrameLen returns the byte length of the MPEG audio frame whose 4-byte -// header is h, or 0 if h is not a valid frame header. -func mp3FrameLen(h []byte) int64 { - if h[0] != 0xff || h[1]&0xe0 != 0xe0 { - return 0 - } - version := int(h[1] >> 3 & 0x03) // 3=MPEG1, 2=MPEG2, 0=MPEG2.5 - layer := int(h[1] >> 1 & 0x03) // 3=I, 2=II, 1=III - if version == 1 || layer == 0 { - return 0 // reserved - } - brIndex := int(h[2] >> 4 & 0x0f) - srIndex := int(h[2] >> 2 & 0x03) - padding := int(h[2] >> 1 & 0x01) - if brIndex == 0 || brIndex == 15 || srIndex == 3 { - return 0 - } - var bitrate int - if version == 3 { - bitrate = mp3BitrateV1[brIndex] - } else { - bitrate = mp3BitrateV2[brIndex] - } - rates, ok := mp3SampleRate[version] - if !ok || bitrate == 0 { - return 0 - } - sampleRate := rates[srIndex] - if sampleRate == 0 { - return 0 - } - bps := bitrate * 1000 - if layer == 3 { // Layer I - return int64(((12 * bps / sampleRate) + padding) * 4) - } - // Layer II & III. MPEG2/2.5 Layer III uses 72 samples-per-byte factor. - coef := 144 - if version != 3 && layer == 1 { - coef = 72 - } - return int64(coef*bps/sampleRate + padding) -} - -func validateOgg(d *document.Document, start, max int64) (int64, float64) { - off := start - first := true - for off+27 <= max { - hdr, err := d.Range(off, 27) - if err != nil || len(hdr) < 27 || string(hdr[:4]) != "OggS" || hdr[4] != 0 { - return 0, 0 - } - headerType := hdr[5] - if first && headerType&0x02 == 0 { - return 0, 0.4 - } - segCount := int(hdr[26]) - lacing, err := d.Range(off+27, int64(segCount)) - if err != nil || len(lacing) < segCount { - return 0, 0 - } - payload := int64(0) - for _, v := range lacing { - payload += int64(v) - } - pageLen := int64(27 + segCount) - if payload < 0 || off+pageLen+payload > max { - return 0, 0 - } - off += pageLen + payload - if headerType&0x04 != 0 { - return off - start, 0.9 - } - first = false - } - return 0, 0.6 -} - -// validateMatroska reads the EBML header element and the following Segment -// element's size to bound a Matroska/WebM stream. A Segment with "unknown size" -// (all size bits set, as live-streamed files use) cannot be bounded this way. -func validateMatroska(d *document.Document, start, max int64) (int64, float64) { - buf, err := d.Range(start, min64(max-start, 4096)) - if err != nil || len(buf) < 8 { - return 0, 0 - } - // EBML header: ID 0x1A45DFA3, then a vint size. - if !(buf[0] == 0x1a && buf[1] == 0x45 && buf[2] == 0xdf && buf[3] == 0xa3) { - return 0, 0 - } - hsz, n, unknown := ebmlSize(buf, 4) - if n == 0 || unknown { - return 0, 0.6 - } - pos := 4 + n + int(hsz) // start of the Segment element - if pos+4 > len(buf) { - return 0, 0.6 - } - // Segment: ID 0x18538067, then a vint size. - if !(buf[pos] == 0x18 && buf[pos+1] == 0x53 && buf[pos+2] == 0x80 && buf[pos+3] == 0x67) { - return 0, 0.6 - } - segSz, sn, segUnknown := ebmlSize(buf, pos+4) - if sn == 0 || segUnknown { - return 0, 0.6 // live/streamed segment without a known size - } - end := int64(pos+4+sn) + segSz - if end <= 0 || start+end > max { - return 0, 0.6 - } - return end, 0.9 -} - -// ebmlSize decodes an EBML variable-length integer (data size form). The length -// is the number of leading zero bits in the first byte plus one; the value is -// the remaining bits. unknown is true when every value bit is set. -func ebmlSize(buf []byte, pos int) (val int64, n int, unknown bool) { - if pos >= len(buf) || buf[pos] == 0 { - return 0, 0, false - } - first := buf[pos] - mask := byte(0x80) - length := 1 - for first&mask == 0 { - mask >>= 1 - length++ - } - if length > 8 || pos+length > len(buf) { - return 0, 0, false - } - low := mask - 1 - v := int64(first & low) - allOnes := (first & low) == low - for i := 1; i < length; i++ { - b := buf[pos+i] - v = (v << 8) | int64(b) - if b != 0xff { - allOnes = false - } - } - return v, length, allOnes -} - -func validateFLAC(d *document.Document, start, max int64) (int64, float64) { - return 0, 0.8 -} - -// validateBMP reads the BITMAPFILEHEADER size field (bfSize), sanity-checking -// the embedded pixel-data offset against the declared length. -func validateBMP(d *document.Document, start, max int64) (int64, float64) { - hdr, err := d.Range(start, 14) - if err != nil || len(hdr) < 14 { - return 0, 0 - } - size := int64(binary.LittleEndian.Uint32(hdr[2:6])) - dataOff := int64(binary.LittleEndian.Uint32(hdr[10:14])) - if size < 26 || dataOff < 26 || dataOff > size || start+size > max { - return 0, 0 - } - return size, 0.8 -} - -// validateMP4 walks the top-level boxes of an ISO base-media file (MP4/MOV/M4A) -// from the file start (the ftyp box). 64-bit box sizes are honoured. -func validateMP4(d *document.Document, start, max int64) (int64, float64) { - pos := start - boxes := 0 - for pos+8 <= max && boxes < 100000 { - h, err := d.Range(pos, 16) - if err != nil || len(h) < 8 { - break - } - size := int64(binary.BigEndian.Uint32(h[0:4])) - switch { - case size == 1: // 64-bit largesize follows the type - if len(h) < 16 { - return 0, 0.4 - } - size = int64(binary.BigEndian.Uint64(h[8:16])) - case size == 0: // box extends to end of file - return max - start, 0.8 - } - if size < 8 { - break - } - pos += size - boxes++ - if pos > max { - return 0, 0.4 - } - } - if pos > start && pos <= max && boxes > 0 { - return pos - start, 0.85 - } - return 0, 0.4 -} - -func validateSQLite(d *document.Document, start, max int64) (int64, float64) { - hdr, err := d.Range(start, 32) - if err != nil || len(hdr) < 32 { - return 0, 0 - } - pageSize := int64(binary.BigEndian.Uint16(hdr[16:18])) - if pageSize == 1 { - pageSize = 65536 - } - pages := int64(binary.BigEndian.Uint32(hdr[28:32])) - if pages == 0 || pageSize == 0 { - return 0, 0.5 - } - return pageSize * pages, 0.9 -} - -func min64(a, b int64) int64 { - if a < b { - return a - } - return b -} diff --git a/internal/leviathan/analysis/diff.go b/internal/leviathan/analysis/diff.go deleted file mode 100644 index 0b412b8..0000000 --- a/internal/leviathan/analysis/diff.go +++ /dev/null @@ -1,244 +0,0 @@ -package analysis - -import ( - "context" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// DiffRegion describes a contiguous run of differing bytes. -type DiffRegion struct { - Offset int64 `json:"offset"` - Length int64 `json:"length"` - // kinds: "edit" (both sides have bytes), "add" (only B), "remove" (only A) - Kind string `json:"kind"` -} - -type DiffBlock struct { - Offset int64 `json:"offset"` - Length int64 `json:"length"` - ChangedBytes int64 `json:"changedBytes"` - EditBytes int64 `json:"editBytes"` - AddBytes int64 `json:"addBytes"` - RemoveBytes int64 `json:"removeBytes"` - Similarity float64 `json:"similarity"` -} - -// Diff compares two documents by byte. It handles documents of -// different sizes by treating the tail of the longer as add/remove. -// Chunks at both sides. Returns a compact list of regions. -func Diff(a, b *document.Document, start, length int64) (map[string]any, error) { - return DiffContext(context.Background(), a, b, start, length) -} - -func DiffContext(ctx context.Context, a, b *document.Document, start, length int64) (map[string]any, error) { - aSize := a.Size() - bSize := b.Size() - maxSize := aSize - if bSize > maxSize { - maxSize = bSize - } - if length < 0 { - length = maxSize - start - } - end := start + length - if end > maxSize { - end = maxSize - } - - const chunk = 1 << 20 - regions := make([]DiffRegion, 0, 64) - - var inRun bool - var runStart int64 - var runKind string - - flush := func(off int64) { - if inRun { - regions = append(regions, DiffRegion{Offset: runStart, Length: off - runStart, Kind: runKind}) - inRun = false - } - } - - for off := start; off < end; { - if err := ctx.Err(); err != nil { - return nil, err - } - winEnd := off + chunk - if winEnd > end { - winEnd = end - } - // Read both sides, tolerating short reads for the shorter file. - var ab, bb []byte - if off < aSize { - n := winEnd - off - if off+n > aSize { - n = aSize - off - } - var err error - ab, err = a.Range(off, n) - if err != nil { - return nil, err - } - } - if off < bSize { - n := winEnd - off - if off+n > bSize { - n = bSize - off - } - var err error - bb, err = b.Range(off, n) - if err != nil { - return nil, err - } - } - for i := int64(0); i < winEnd-off; i++ { - if i%4096 == 0 { - if err := ctx.Err(); err != nil { - return nil, err - } - } - curOff := off + i - var av, bv byte - var av_ok, bv_ok bool - if curOff < aSize && int64(len(ab)) > i { - av = ab[i] - av_ok = true - } - if curOff < bSize && int64(len(bb)) > i { - bv = bb[i] - bv_ok = true - } - kind := "" - if av_ok && bv_ok { - if av != bv { - kind = "edit" - } - } else if av_ok && !bv_ok { - kind = "remove" - } else if !av_ok && bv_ok { - kind = "add" - } - if kind == "" { - flush(curOff) - continue - } - if !inRun { - inRun = true - runStart = curOff - runKind = kind - } else if kind != runKind { - flush(curOff) - inRun = true - runStart = curOff - runKind = kind - } - } - off = winEnd - } - flush(end) - - // Convert to JS-friendly output + summary - out := make([]map[string]any, len(regions)) - editBytes := int64(0) - addBytes := int64(0) - removeBytes := int64(0) - for i, r := range regions { - out[i] = map[string]any{ - "offset": r.Offset, - "length": r.Length, - "kind": r.Kind, - } - switch r.Kind { - case "edit": - editBytes += r.Length - case "add": - addBytes += r.Length - case "remove": - removeBytes += r.Length - } - } - const blockSize int64 = 4096 - blocks := diffBlocksFromRegions(regions, start, end, blockSize) - blockOut := make([]map[string]any, len(blocks)) - for i, b := range blocks { - blockOut[i] = map[string]any{ - "offset": b.Offset, - "length": b.Length, - "changedBytes": b.ChangedBytes, - "editBytes": b.EditBytes, - "addBytes": b.AddBytes, - "removeBytes": b.RemoveBytes, - "similarity": b.Similarity, - } - } - return map[string]any{ - "regions": out, - "regionCount": len(regions), - "blocks": blockOut, - "blockCount": len(blocks), - "blockSize": blockSize, - "editBytes": editBytes, - "addBytes": addBytes, - "removeBytes": removeBytes, - "aSize": aSize, - "bSize": bSize, - }, nil -} - -func diffBlocksFromRegions(regions []DiffRegion, start, end, blockSize int64) []DiffBlock { - if blockSize <= 0 || end <= start || len(regions) == 0 { - return nil - } - out := []DiffBlock{} - for alignedStart := start - start%blockSize; alignedStart < end; alignedStart += blockSize { - blockStart := alignedStart - blockEnd := alignedStart + blockSize - if blockEnd > end { - blockEnd = end - } - if blockEnd <= start { - continue - } - if blockStart < start { - blockStart = start - } - b := DiffBlock{Offset: blockStart, Length: blockEnd - blockStart} - for _, r := range regions { - rEnd := r.Offset + r.Length - if rEnd <= blockStart { - continue - } - if r.Offset >= blockEnd { - break - } - ovStart := r.Offset - if ovStart < blockStart { - ovStart = blockStart - } - ovEnd := rEnd - if ovEnd > blockEnd { - ovEnd = blockEnd - } - changed := ovEnd - ovStart - if changed <= 0 { - continue - } - b.ChangedBytes += changed - switch r.Kind { - case "edit": - b.EditBytes += changed - case "add": - b.AddBytes += changed - case "remove": - b.RemoveBytes += changed - } - } - if b.ChangedBytes == 0 { - continue - } - b.Similarity = 1 - float64(b.ChangedBytes)/float64(b.Length) - out = append(out, b) - } - return out -} diff --git a/internal/leviathan/analysis/entropy.go b/internal/leviathan/analysis/entropy.go deleted file mode 100644 index 905493f..0000000 --- a/internal/leviathan/analysis/entropy.go +++ /dev/null @@ -1,125 +0,0 @@ -package analysis - -import ( - "context" - "math" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// Entropy computes Shannon entropy per sliding window over the range. -// -// The returned map has: -// -// windowSize: int -// step: int -// samples: []float64 — entropy per window (0..8) -// offsets: []int64 — start offset of each window -// mean/min/max: summary stats -// highCount: number of windows with entropy >= 7.5 (candidate compressed/encrypted) -func Entropy(d *document.Document, start, length, windowSize, step int64) (map[string]any, error) { - return EntropyContext(context.Background(), d, start, length, windowSize, step) -} - -func EntropyContext(ctx context.Context, d *document.Document, start, length, windowSize, step int64) (map[string]any, error) { - if windowSize <= 0 { - windowSize = 4096 - } - if step <= 0 { - step = windowSize - } - if length < 0 { - length = d.Size() - start - } - if length <= 0 { - return map[string]any{ - "windowSize": windowSize, "step": step, - "samples": []float64{}, "offsets": []int64{}, - "mean": 0.0, "min": 0.0, "max": 0.0, "highCount": 0, - }, nil - } - - // We run in a streaming fashion: advance through chunks, refill - // a rolling window, and emit a sample whenever the cursor hits - // a step boundary. - samples := make([]float64, 0, length/step+1) - offsets := make([]int64, 0, length/step+1) - - // For bounded memory we read the document range chunk by chunk - // but we still need a window of size `windowSize`. Windows can be - // as small as 256 or as large as 1MB; we cap to length. - if windowSize > length { - windowSize = length - } - - // Simple approach: iterate in step-sized hops, reading window - // bytes at each hop. This scales O(n * (w/step)) but window and - // step are usually the same for UI. - mean := 0.0 - minE := math.Inf(1) - maxE := math.Inf(-1) - high := 0 - - for off := start; off < start+length; off += step { - if err := ctx.Err(); err != nil { - return nil, err - } - winLen := windowSize - if off+winLen > start+length { - winLen = start + length - off - } - buf, err := d.Range(off, winLen) - if err != nil { - return nil, err - } - e := shannon(buf) - samples = append(samples, e) - offsets = append(offsets, off) - mean += e - if e < minE { - minE = e - } - if e > maxE { - maxE = e - } - if e >= 7.5 { - high++ - } - } - if len(samples) > 0 { - mean /= float64(len(samples)) - } else { - minE, maxE = 0, 0 - } - - return map[string]any{ - "windowSize": windowSize, - "step": step, - "samples": samples, - "offsets": offsets, - "mean": mean, - "min": minE, - "max": maxE, - "highCount": high, - }, nil -} - -func shannon(buf []byte) float64 { - if len(buf) == 0 { - return 0 - } - var counts [256]int - for _, b := range buf { - counts[b]++ - } - n := float64(len(buf)) - e := 0.0 - for _, c := range counts { - if c == 0 { - continue - } - p := float64(c) / n - e -= p * math.Log2(p) - } - return e -} diff --git a/internal/leviathan/analysis/fuzzy.go b/internal/leviathan/analysis/fuzzy.go deleted file mode 100644 index 076f9b5..0000000 --- a/internal/leviathan/analysis/fuzzy.go +++ /dev/null @@ -1,224 +0,0 @@ -package analysis - -import ( - "fmt" - "strconv" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// fuzzy.go implements context-triggered piecewise hashing (CTPH) compatible -// with ssdeep. CTPH produces a short signature of the form -// "blocksize:hash1:hash2" that stays similar when the input changes only a -// little, so investigators can cluster related samples or match a file -// against a known-bad ssdeep hash even after minor edits. -// -// The signature format and the rolling/sum hashes follow Andrew Tridgell's -// spamsum / Jesse Kornblum's ssdeep so the output interoperates with the -// standard tooling (e.g. an empty input hashes to "3::"). - -const ( - spamsumLength = 64 - rollingWindow = 7 - minBlocksize = 3 - hashInit = uint32(0x28021967) - hashPrime = uint32(0x01000193) -) - -const b64alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" - -type rollState struct { - h1, h2, h3 uint32 - window [rollingWindow]byte - n int -} - -func (r *rollState) roll(c byte) uint32 { - r.h2 -= r.h1 - r.h2 += rollingWindow * uint32(c) - r.h1 += uint32(c) - r.h1 -= uint32(r.window[r.n%rollingWindow]) - r.window[r.n%rollingWindow] = c - r.n++ - r.h3 = r.h3 << 5 - r.h3 ^= uint32(c) - return r.h1 + r.h2 + r.h3 -} - -func sumHash(c byte, h uint32) uint32 { return (h * hashPrime) ^ uint32(c) } - -// FuzzyDigest computes the ssdeep-compatible signature for data. -func FuzzyDigest(data []byte) string { - bs := uint32(minBlocksize) - for uint64(bs)*spamsumLength < uint64(len(data)) { - bs *= 2 - } - for { - sig1, sig2 := ctphSigs(data, bs) - if bs > minBlocksize && len(sig1) < spamsumLength/2 { - bs /= 2 - continue - } - return fmt.Sprintf("%d:%s:%s", bs, sig1, sig2) - } -} - -func ctphSigs(data []byte, bs uint32) (string, string) { - var rs rollState - h1, h2 := hashInit, hashInit - var sig1, sig2 strings.Builder - var lastRoll uint32 - - for _, c := range data { - h1 = sumHash(c, h1) - h2 = sumHash(c, h2) - rh := rs.roll(c) - lastRoll = rh - - if rh%bs == bs-1 && sig1.Len() < spamsumLength-1 { - sig1.WriteByte(b64alphabet[h1%64]) - h1 = hashInit - } - if rh%(bs*2) == bs*2-1 && sig2.Len() < spamsumLength/2-1 { - sig2.WriteByte(b64alphabet[h2%64]) - h2 = hashInit - } - } - if lastRoll != 0 { - sig1.WriteByte(b64alphabet[h1%64]) - sig2.WriteByte(b64alphabet[h2%64]) - } - return sig1.String(), sig2.String() -} - -// FuzzyHash computes the CTPH signature over a document range. -func FuzzyHash(d *document.Document, start, length int64) (map[string]any, error) { - if length < 0 { - length = d.Size() - start - } - buf, err := d.Range(start, length) - if err != nil { - return nil, err - } - sig := FuzzyDigest(buf) - bs := strings.SplitN(sig, ":", 2)[0] - return map[string]any{ - "algo": "ssdeep", - "signature": sig, - "blocksize": bs, - "length": length, - }, nil -} - -// FuzzyCompare returns a 0-100 similarity score between two ssdeep -// signatures. 0 means unrelated (or incomparable block sizes); 100 means the -// signatures are identical. -func FuzzyCompare(a, b string) int { - bs1, a1, a2, ok1 := parseSig(a) - bs2, b1, b2, ok2 := parseSig(b) - if !ok1 || !ok2 { - return 0 - } - if bs1 != bs2 && bs1 != bs2*2 && bs2 != bs1*2 { - return 0 - } - switch { - case bs1 == bs2: - s1 := scoreStrings(a1, b1, bs1) - s2 := scoreStrings(a2, b2, bs1*2) - if s2 > s1 { - return s2 - } - return s1 - case bs1 == bs2*2: - return scoreStrings(a1, b2, bs1) - default: // bs2 == bs1*2 - return scoreStrings(a2, b1, bs2) - } -} - -func parseSig(s string) (uint32, string, string, bool) { - parts := strings.SplitN(s, ":", 3) - if len(parts) != 3 { - return 0, "", "", false - } - bs, err := strconv.ParseUint(parts[0], 10, 32) - if err != nil || bs == 0 { - return 0, "", "", false - } - return uint32(bs), parts[1], parts[2], true -} - -func scoreStrings(s1, s2 string, blocksize uint32) int { - if !hasCommonSubstring(s1, s2, rollingWindow) { - return 0 - } - if s1 == s2 { - return 100 - } - d := editDistance(s1, s2) - if len(s1)+len(s2) == 0 { - return 0 - } - score := (d * spamsumLength) / (len(s1) + len(s2)) - score = (100 * score) / spamsumLength - score = 100 - score - // Small block sizes describe little data, so cap the score they can claim. - cap := int(blocksize) / minBlocksize * minInt(len(s1), len(s2)) - if score > cap { - score = cap - } - if score > 100 { - score = 100 - } - if score < 0 { - score = 0 - } - return score -} - -func hasCommonSubstring(s1, s2 string, n int) bool { - if len(s1) < n || len(s2) < n { - return false - } - seen := make(map[string]struct{}, len(s1)) - for i := 0; i+n <= len(s1); i++ { - seen[s1[i:i+n]] = struct{}{} - } - for i := 0; i+n <= len(s2); i++ { - if _, ok := seen[s2[i:i+n]]; ok { - return true - } - } - return false -} - -// editDistance is classic Levenshtein with unit insert/delete/substitute cost. -func editDistance(s1, s2 string) int { - m, n := len(s1), len(s2) - prev := make([]int, n+1) - cur := make([]int, n+1) - for j := 0; j <= n; j++ { - prev[j] = j - } - for i := 1; i <= m; i++ { - cur[0] = i - for j := 1; j <= n; j++ { - cost := 1 - if s1[i-1] == s2[j-1] { - cost = 0 - } - cur[j] = minInt(minInt(cur[j-1]+1, prev[j]+1), prev[j-1]+cost) - } - prev, cur = cur, prev - } - return prev[n] -} - -func minInt(a, b int) int { - if a < b { - return a - } - return b -} diff --git a/internal/leviathan/analysis/hash.go b/internal/leviathan/analysis/hash.go deleted file mode 100644 index 47a3740..0000000 --- a/internal/leviathan/analysis/hash.go +++ /dev/null @@ -1,63 +0,0 @@ -package analysis - -import ( - "crypto/md5" - "crypto/sha1" - "crypto/sha256" - "crypto/sha512" - "encoding/hex" - "hash" - "hash/crc32" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// HashSet contains CRC32/MD5/SHA-1/SHA-256/SHA-512 for a range. -type HashSet struct { - Length int64 `json:"length"` - CRC32 string `json:"crc32"` - MD5 string `json:"md5"` - SHA1 string `json:"sha1"` - SHA256 string `json:"sha256"` - SHA512 string `json:"sha512"` -} - -// Hashes computes all supported hashes over the document range. -// The computation runs chunk-by-chunk so large files do not force a -// full-buffer duplication. -func Hashes(d *document.Document, start, length int64) (map[string]any, error) { - if length < 0 { - length = d.Size() - start - } - - crc := crc32.NewIEEE() - mh := md5.New() - s1 := sha1.New() - s256 := sha256.New() - s512 := sha512.New() - - writers := []hash.Hash{mh, s1, s256, s512} - - total := int64(0) - err := d.ForEachChunk(start, length, func(_ int64, chunk []byte) error { - crc.Write(chunk) - for _, w := range writers { - w.Write(chunk) - } - total += int64(len(chunk)) - return nil - }) - if err != nil { - return nil, err - } - return map[string]any{ - "length": total, - "crc32": toHex(crc.Sum(nil)), - "md5": toHex(mh.Sum(nil)), - "sha1": toHex(s1.Sum(nil)), - "sha256": toHex(s256.Sum(nil)), - "sha512": toHex(s512.Sum(nil)), - }, nil -} - -func toHex(b []byte) string { return hex.EncodeToString(b) } diff --git a/internal/leviathan/analysis/inspector.go b/internal/leviathan/analysis/inspector.go deleted file mode 100644 index 298aa83..0000000 --- a/internal/leviathan/analysis/inspector.go +++ /dev/null @@ -1,162 +0,0 @@ -package analysis - -import ( - "encoding/binary" - "fmt" - "math" - "time" - "unicode/utf16" - "unicode/utf8" -) - -// Inspect interprets bytes starting at `buf[0]` as many candidate -// types. It is resilient: types that don't have enough bytes simply -// return empty strings. -func Inspect(buf []byte, offset int64) map[string]any { - out := map[string]any{ - "offset": offset, - "length": len(buf), - } - - // 8-bit - if len(buf) >= 1 { - out["u8"] = int(buf[0]) - out["i8"] = int(int8(buf[0])) - out["char"] = printable(buf[0]) - out["bin8"] = fmt.Sprintf("%08b", buf[0]) - } - - // 16-bit - if len(buf) >= 2 { - le16 := binary.LittleEndian.Uint16(buf) - be16 := binary.BigEndian.Uint16(buf) - out["u16_le"] = int(le16) - out["u16_be"] = int(be16) - out["i16_le"] = int(int16(le16)) - out["i16_be"] = int(int16(be16)) - } - - // 32-bit - if len(buf) >= 4 { - le32 := binary.LittleEndian.Uint32(buf) - be32 := binary.BigEndian.Uint32(buf) - out["u32_le"] = uint(le32) - out["u32_be"] = uint(be32) - out["i32_le"] = int(int32(le32)) - out["i32_be"] = int(int32(be32)) - out["f32_le"] = float64(math.Float32frombits(le32)) - out["f32_be"] = float64(math.Float32frombits(be32)) - } - - // 64-bit - if len(buf) >= 8 { - le64 := binary.LittleEndian.Uint64(buf) - be64 := binary.BigEndian.Uint64(buf) - out["u64_le"] = float64(le64) - out["u64_be"] = float64(be64) - out["i64_le"] = float64(int64(le64)) - out["i64_be"] = float64(int64(be64)) - out["f64_le"] = math.Float64frombits(le64) - out["f64_be"] = math.Float64frombits(be64) - } - - // Timestamp guesses - if len(buf) >= 4 { - le := binary.LittleEndian.Uint32(buf) - out["unix_le"] = formatUnix(int64(le)) - } - if len(buf) >= 8 { - le := binary.LittleEndian.Uint64(buf) - out["filetime_le"] = formatFiletime(le) - out["webkit_le"] = formatWebkit(le) - out["oletime_le"] = formatOLEDate(math.Float64frombits(le)) - } - - // ASCII preview (up to 32 bytes) - n := len(buf) - if n > 32 { - n = 32 - } - ascii := make([]byte, n) - for i := 0; i < n; i++ { - if buf[i] >= 0x20 && buf[i] <= 0x7e { - ascii[i] = buf[i] - } else { - ascii[i] = '.' - } - } - out["ascii"] = string(ascii) - - // UTF-8 attempt - if utf8.Valid(buf[:n]) { - out["utf8"] = string(buf[:n]) - } - - // UTF-16 LE/BE preview - if len(buf) >= 2 { - lu := n / 2 - if lu > 0 { - u := make([]uint16, lu) - for i := 0; i < lu; i++ { - u[i] = uint16(buf[i*2]) | uint16(buf[i*2+1])<<8 - } - out["utf16le"] = string(utf16.Decode(u)) - for i := 0; i < lu; i++ { - u[i] = uint16(buf[i*2])<<8 | uint16(buf[i*2+1]) - } - out["utf16be"] = string(utf16.Decode(u)) - } - } - - return out -} - -func printable(b byte) string { - if b >= 0x20 && b <= 0x7e { - return string([]byte{b}) - } - return "." -} - -func formatUnix(sec int64) string { - if sec < 0 || sec > 4102444800 { // past 2100 - return "" - } - return time.Unix(sec, 0).UTC().Format(time.RFC3339) -} - -// Windows FILETIME epoch 1601-01-01 in 100ns ticks. -const filetimeEpochDelta = 11644473600 - -func formatFiletime(v uint64) string { - if v == 0 { - return "" - } - sec := int64(v/10_000_000) - filetimeEpochDelta - if sec < 0 || sec > 4102444800 { - return "" - } - return time.Unix(sec, 0).UTC().Format(time.RFC3339) -} - -// WebKit/Chrome epoch 1601-01-01 in microseconds. -func formatWebkit(v uint64) string { - if v == 0 { - return "" - } - sec := int64(v/1_000_000) - filetimeEpochDelta - if sec < 0 || sec > 4102444800 { - return "" - } - return time.Unix(sec, 0).UTC().Format(time.RFC3339) -} - -// OLE Automation date: days since 1899-12-30, as float64. -func formatOLEDate(days float64) string { - if days < 2 || days > 75000 { - return "" - } - base := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC) - sec := int64(days * 86400) - return base.Add(time.Duration(sec) * time.Second).Format(time.RFC3339) -} diff --git a/internal/leviathan/analysis/ioc.go b/internal/leviathan/analysis/ioc.go deleted file mode 100644 index dc4eb26..0000000 --- a/internal/leviathan/analysis/ioc.go +++ /dev/null @@ -1,89 +0,0 @@ -package analysis - -import ( - "context" - "regexp" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// ioc regexes. They run against printable ASCII strings extracted -// from the document, which is the standard approach for IOC hunting -// and avoids matching noise in raw binary data. -var iocRegexes = map[string]*regexp.Regexp{ - "url": regexp.MustCompile(`(?i)\b(?:https?|ftp)://[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]+`), - "domain": regexp.MustCompile(`(?i)\b(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\.)+(?:com|net|org|io|co|uk|de|fr|ru|cn|info|xyz|app|dev|sh|me|gov|mil|edu|biz)\b`), - "email": regexp.MustCompile(`(?i)\b[A-Z0-9._%+\-]+@[A-Z0-9.\-]+\.[A-Z]{2,}\b`), - "ipv4": regexp.MustCompile(`\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b`), - "ipv6": regexp.MustCompile(`\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b`), - "guid": regexp.MustCompile(`\b[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\b`), - "md5": regexp.MustCompile(`\b[a-fA-F0-9]{32}\b`), - "sha1": regexp.MustCompile(`\b[a-fA-F0-9]{40}\b`), - "sha256": regexp.MustCompile(`\b[a-fA-F0-9]{64}\b`), - "winpath": regexp.MustCompile(`[A-Za-z]:\\(?:[^\\/:*?"<>|\r\n]+\\)*[^\\/:*?"<>|\r\n]*`), - "unc": regexp.MustCompile(`\\\\[A-Za-z0-9._\-]+(?:\\[^\\/:*?"<>|\r\n]+)+`), - "unixpath": regexp.MustCompile(`(?:/(?:etc|var|usr|opt|home|tmp|root|srv|bin|sbin|lib|lib64)(?:/[A-Za-z0-9._\-]+)+)`), - "regkey": regexp.MustCompile(`(?i)\b(?:HKLM|HKCU|HKCR|HKU|HKCC|HKEY_[A-Z_]+)\\[^\s"<>]+`), - "onion": regexp.MustCompile(`(?i)\b[a-z2-7]{16,56}\.onion\b`), - "btc": regexp.MustCompile(`\b(?:[13][a-km-zA-HJ-NP-Z1-9]{25,34}|bc1[a-z0-9]{20,90})\b`), -} - -// ExtractIOCs runs the IOC regexes over ASCII strings first and then -// UTF-16 LE/BE strings. Returns a map of category -> slice of hits -// with offsets into the original file. -func ExtractIOCs(d *document.Document, start, length int64, minLen int) (map[string]any, error) { - return ExtractIOCsContext(context.Background(), d, start, length, minLen) -} - -func ExtractIOCsContext(ctx context.Context, d *document.Document, start, length int64, minLen int) (map[string]any, error) { - if length < 0 { - length = d.Size() - start - } - if minLen <= 0 { - minLen = 5 - } - - strings, err := ExtractStringsContext(ctx, d, start, length, minLen, []string{"ascii", "utf16le", "utf16be"}, 200_000) - if err != nil { - return nil, err - } - - result := map[string]any{} - totals := map[string]int{} - for category, re := range iocRegexes { - if err := ctx.Err(); err != nil { - return nil, err - } - hits := make([]map[string]any, 0) - for _, s := range strings { - if err := ctx.Err(); err != nil { - return nil, err - } - str, _ := s["value"].(string) - base, _ := s["offset"].(int64) - for _, loc := range re.FindAllStringIndex(str, -1) { - hits = append(hits, map[string]any{ - "category": category, - "offset": base + int64(loc[0]), - "length": loc[1] - loc[0], - "value": str[loc[0]:loc[1]], - }) - } - } - result[category] = hits - totals[category] = len(hits) - } - // flat list for UI convenience - all := []map[string]any{} - for _, v := range result { - if s, ok := v.([]map[string]any); ok { - all = append(all, s...) - } - } - return map[string]any{ - "byCategory": result, - "all": all, - "totals": totals, - "totalStrings": len(strings), - }, nil -} diff --git a/internal/leviathan/analysis/keys.go b/internal/leviathan/analysis/keys.go deleted file mode 100644 index 12f14b3..0000000 --- a/internal/leviathan/analysis/keys.go +++ /dev/null @@ -1,367 +0,0 @@ -package analysis - -import ( - "bytes" - "crypto/ecdsa" - "crypto/ed25519" - "crypto/rsa" - "crypto/x509" - "encoding/pem" - "fmt" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// KeyArtefact is a single key-ish object we've found in the raw file. -// Length is the span we recognised; Note carries any decoding detail. -type KeyArtefact struct { - Kind string `json:"kind"` - Format string `json:"format"` // "pem" | "der" | "ssh" | "p12" - Offset int64 `json:"offset"` - Length int64 `json:"length"` - Summary string `json:"summary"` - Note string `json:"note,omitempty"` -} - -// ScanKeys walks the whole document looking for cryptographic artefacts: -// - PEM blocks with common key-related labels -// - OpenSSH keys (authorized_keys lines and the "openssh-key-v1" private-key envelope) -// - PKCS#12 magic (1.2.840.113549.1.12.1 appears in headers) -// - Loose DER SEQUENCEs that parse as RSA/EC/PKCS#8 keys -// -// We cap the scan at 64 MiB. Users with larger blobs can select a range -// and call this against that subset via a future "keys.scanRange" bridge. -func ScanKeys(d *document.Document) ([]KeyArtefact, error) { - size := d.Size() - if size > 64*1024*1024 { - return nil, fmt.Errorf("keys.scan: file > 64 MiB; use selection") - } - buf, err := d.Range(0, size) - if err != nil { - return nil, err - } - out := []KeyArtefact{} - out = append(out, scanPEMKeys(buf)...) - out = append(out, scanOpenSSHPubKeys(buf)...) - out = append(out, scanOpenSSHPrivKeys(buf)...) - out = append(out, scanPKCS12(buf)...) - // Loose DER pass — for raw PKCS#8 / PKCS#1 blobs that aren't wrapped in PEM. - out = append(out, scanLooseDERKeys(buf)...) - return out, nil -} - -// Known PEM labels that indicate key-ish content. -var pemKeyLabels = map[string]string{ - "RSA PRIVATE KEY": "rsa-private", - "RSA PUBLIC KEY": "rsa-public", - "EC PRIVATE KEY": "ec-private", - "EC PUBLIC KEY": "ec-public", - "DSA PRIVATE KEY": "dsa-private", - "PRIVATE KEY": "pkcs8-private", - "ENCRYPTED PRIVATE KEY": "pkcs8-encrypted", - "PUBLIC KEY": "pkix-public", - "OPENSSH PRIVATE KEY": "openssh-private", - "SSH2 PUBLIC KEY": "ssh2-public", - "PGP PRIVATE KEY BLOCK": "pgp-private", - "PGP PUBLIC KEY BLOCK": "pgp-public", - "CERTIFICATE REQUEST": "csr", - "NEW CERTIFICATE REQUEST": "csr", - "DH PARAMETERS": "dh-params", - "PARAMETERS": "params", -} - -func scanPEMKeys(buf []byte) []KeyArtefact { - out := []KeyArtefact{} - rest := buf - origLen := int64(len(buf)) - for { - block, next := pem.Decode(rest) - if block == nil { - break - } - beginIdx := bytes.Index(rest, []byte("-----BEGIN")) - if beginIdx < 0 { - break - } - start := origLen - int64(len(rest)) + int64(beginIdx) - end := origLen - int64(len(next)) - kind, match := pemKeyLabels[block.Type] - if !match { - rest = next - continue - } - art := KeyArtefact{ - Kind: kind, Format: "pem", - Offset: start, Length: end - start, - } - art.Summary = describePEMBlock(block) - out = append(out, art) - rest = next - } - return out -} - -func describePEMBlock(b *pem.Block) string { - name := b.Type - switch b.Type { - case "RSA PRIVATE KEY": - if k, err := x509.ParsePKCS1PrivateKey(b.Bytes); err == nil { - return fmt.Sprintf("%s — RSA %d-bit", name, k.N.BitLen()) - } - case "RSA PUBLIC KEY": - if k, err := x509.ParsePKCS1PublicKey(b.Bytes); err == nil { - return fmt.Sprintf("%s — RSA %d-bit", name, k.N.BitLen()) - } - case "EC PRIVATE KEY": - if k, err := x509.ParseECPrivateKey(b.Bytes); err == nil { - return fmt.Sprintf("%s — curve %s", name, k.Curve.Params().Name) - } - case "PRIVATE KEY": - if k, err := x509.ParsePKCS8PrivateKey(b.Bytes); err == nil { - return fmt.Sprintf("%s — %s", name, describePrivateKey(k)) - } - case "PUBLIC KEY": - if k, err := x509.ParsePKIXPublicKey(b.Bytes); err == nil { - return fmt.Sprintf("%s — %s", name, describePublicKey(k)) - } - case "OPENSSH PRIVATE KEY": - return fmt.Sprintf("%s (%d B payload)", name, len(b.Bytes)) - case "ENCRYPTED PRIVATE KEY": - return fmt.Sprintf("%s — PKCS#8 encrypted (%d B)", name, len(b.Bytes)) - } - return fmt.Sprintf("%s (%d B)", name, len(b.Bytes)) -} - -func describePrivateKey(k any) string { - switch key := k.(type) { - case *rsa.PrivateKey: - return fmt.Sprintf("RSA %d-bit", key.N.BitLen()) - case *ecdsa.PrivateKey: - return fmt.Sprintf("ECDSA %s", key.Curve.Params().Name) - case ed25519.PrivateKey: - return "Ed25519" - } - return fmt.Sprintf("%T", k) -} - -func describePublicKey(k any) string { - switch key := k.(type) { - case *rsa.PublicKey: - return fmt.Sprintf("RSA %d-bit", key.N.BitLen()) - case *ecdsa.PublicKey: - return fmt.Sprintf("ECDSA %s", key.Curve.Params().Name) - case ed25519.PublicKey: - return "Ed25519" - } - return fmt.Sprintf("%T", k) -} - -// OpenSSH authorized_keys / id_*.pub format is one line per key: -// " [comment]\n". We search for well-known algos and -// delimit by the next newline. False positives on text logs are possible, -// so we validate by base64-decoding. -var sshAlgos = []string{ - "ssh-rsa", "ssh-dss", "ssh-ed25519", - "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521", - "sk-ssh-ed25519@openssh.com", "sk-ecdsa-sha2-nistp256@openssh.com", -} - -func scanOpenSSHPubKeys(buf []byte) []KeyArtefact { - out := []KeyArtefact{} - for _, algo := range sshAlgos { - needle := []byte(algo + " ") - off := 0 - for { - idx := bytes.Index(buf[off:], needle) - if idx < 0 { - break - } - absStart := off + idx - // Must be at start of line or after whitespace. - if absStart > 0 { - prev := buf[absStart-1] - if prev != '\n' && prev != '\r' && prev != ' ' && prev != '\t' { - off = absStart + 1 - continue - } - } - // End of line. - endOfLine := absStart + len(needle) - for endOfLine < len(buf) && buf[endOfLine] != '\n' && buf[endOfLine] != '\r' { - endOfLine++ - } - line := buf[absStart:endOfLine] - // Expect at least one space-separated payload that decodes. - parts := bytes.SplitN(line, []byte(" "), 3) - if len(parts) < 2 || len(parts[1]) < 16 || !isBase64Like(parts[1]) { - off = absStart + 1 - continue - } - art := KeyArtefact{ - Kind: "openssh-public", Format: "ssh", - Offset: int64(absStart), Length: int64(len(line)), - Summary: fmt.Sprintf("%s (%d-byte payload b64)", algo, len(parts[1])), - } - if len(parts) == 3 && len(parts[2]) > 0 { - art.Note = "comment: " + string(parts[2]) - } - out = append(out, art) - off = endOfLine - } - } - return out -} - -func scanOpenSSHPrivKeys(buf []byte) []KeyArtefact { - // Already caught by PEM scan (label "OPENSSH PRIVATE KEY"). We also - // look for the magic string inside the PEM body — "openssh-key-v1\0". - out := []KeyArtefact{} - needle := []byte("openssh-key-v1\x00") - off := 0 - for { - idx := bytes.Index(buf[off:], needle) - if idx < 0 { - break - } - absStart := off + idx - out = append(out, KeyArtefact{ - Kind: "openssh-private-magic", Format: "ssh", - Offset: int64(absStart), Length: int64(len(needle)), - Summary: "OpenSSH v1 private-key magic", - Note: "magic only; no envelope length inferred", - }) - off = absStart + len(needle) - } - return out -} - -// PKCS#12 (.p12 / .pfx) files begin with an ASN.1 SEQUENCE; the second -// element is an OID for the bag type. Its encoded form is reasonably -// distinctive: "\x06\x0b\x2a\x86\x48\x86\xf7\x0d\x01\x07\x01" (data). -// The simplest reliable magic for our purposes is the OID for -// pkcs-12 (1.2.840.113549.1.12.1): "\x06\x0a\x2a\x86\x48\x86\xf7\x0d\x01\x0c\x01". -func scanPKCS12(buf []byte) []KeyArtefact { - out := []KeyArtefact{} - needle := []byte{0x06, 0x0a, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x0c, 0x01} - off := 0 - for { - idx := bytes.Index(buf[off:], needle) - if idx < 0 { - break - } - absStart := off + idx - out = append(out, KeyArtefact{ - Kind: "pkcs12-oid", Format: "p12", - Offset: int64(absStart), Length: int64(len(needle)), - Summary: "pkcs-12 OID (1.2.840.113549.1.12.1)", - Note: "bag-type marker; may indicate a .p12 or .pfx container", - }) - off = absStart + len(needle) - } - return out -} - -// scanLooseDERKeys walks DER SEQUENCE headers and tries to parse each -// candidate as a known key format. False negatives are fine; we only -// emit an artefact on a positive parse. -func scanLooseDERKeys(buf []byte) []KeyArtefact { - out := []KeyArtefact{} - // Use the same SEQUENCE 0x30 0x82 xx yy (long-form, 2 length bytes) - // pattern as the cert scanner. Minimum useful key length ~200 bytes. - // - // Each candidate runs up to six x509 parse attempts over a blob up to - // 64 KiB, so an unfiltered scan of a buffer dense with "30 82" pairs is - // O(n) expensive parses and could freeze the UI. A cheap structural - // pre-filter (a DER key body begins with a SEQUENCE 0x30 or an INTEGER - // 0x02) rejects almost all false candidates without parsing, and a hard - // cap bounds adversarial input that defeats the pre-filter. - const maxDERAttempts = 1 << 16 - derAttempts := 0 - for i := 0; i+4 < len(buf); i++ { - if buf[i] != 0x30 || buf[i+1] != 0x82 { - continue - } - if c := buf[i+4]; c != 0x30 && c != 0x02 { - continue - } - total := int(buf[i+2])<<8 | int(buf[i+3]) - if total < 80 || i+4+total > len(buf) { - continue - } - if derAttempts++; derAttempts > maxDERAttempts { - break - } - blob := buf[i : i+4+total] - // Skip anything that looks like a certificate (already caught by certs panel). - if _, err := x509.ParseCertificate(blob); err == nil { - i += 4 + total - 1 - continue - } - if _, err := x509.ParsePKCS1PrivateKey(blob); err == nil { - out = append(out, KeyArtefact{ - Kind: "rsa-private", Format: "der", - Offset: int64(i), Length: int64(4 + total), - Summary: "RSA private key (PKCS#1 DER)", - }) - i += 4 + total - 1 - continue - } - if _, err := x509.ParsePKCS1PublicKey(blob); err == nil { - out = append(out, KeyArtefact{ - Kind: "rsa-public", Format: "der", - Offset: int64(i), Length: int64(4 + total), - Summary: "RSA public key (PKCS#1 DER)", - }) - i += 4 + total - 1 - continue - } - if k, err := x509.ParseECPrivateKey(blob); err == nil { - out = append(out, KeyArtefact{ - Kind: "ec-private", Format: "der", - Offset: int64(i), Length: int64(4 + total), - Summary: "EC private key (SEC1 DER) curve=" + k.Curve.Params().Name, - }) - i += 4 + total - 1 - continue - } - if k, err := x509.ParsePKCS8PrivateKey(blob); err == nil { - out = append(out, KeyArtefact{ - Kind: "pkcs8-private", Format: "der", - Offset: int64(i), Length: int64(4 + total), - Summary: "PKCS#8 private key — " + describePrivateKey(k), - }) - i += 4 + total - 1 - continue - } - if k, err := x509.ParsePKIXPublicKey(blob); err == nil { - out = append(out, KeyArtefact{ - Kind: "pkix-public", Format: "der", - Offset: int64(i), Length: int64(4 + total), - Summary: "PKIX public key — " + describePublicKey(k), - }) - i += 4 + total - 1 - continue - } - } - return out -} - -func isBase64Like(b []byte) bool { - if len(b) == 0 { - return false - } - for _, c := range b { - if (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || - c == '+' || c == '/' || c == '=' { - continue - } - return false - } - return true -} - -// Stringer helper used by the bridge; avoids leaking internal types to JS. -func (a KeyArtefact) String() string { - return strings.TrimSpace(fmt.Sprintf("%s %s @0x%x %s", a.Kind, a.Format, a.Offset, a.Summary)) -} diff --git a/internal/leviathan/analysis/multisearch.go b/internal/leviathan/analysis/multisearch.go deleted file mode 100644 index 9a12677..0000000 --- a/internal/leviathan/analysis/multisearch.go +++ /dev/null @@ -1,113 +0,0 @@ -package analysis - -import ( - "encoding/hex" - "errors" - "fmt" - "regexp" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -type MultiSearchQuery struct { - Mode string - Query string -} - -func ParseMultiSearchSpec(spec string) ([]MultiSearchQuery, error) { - spec = strings.ReplaceAll(spec, ";", "\n") - out := []MultiSearchQuery{} - for _, line := range strings.Split(spec, "\n") { - line = strings.TrimSpace(line) - if line == "" || strings.HasPrefix(line, "#") { - continue - } - parts := strings.Fields(line) - if len(parts) < 2 { - return nil, errors.New("multi-search lines require: text|hex|regex ") - } - mode := strings.ToLower(parts[0]) - query := strings.TrimSpace(strings.TrimPrefix(line, parts[0])) - switch mode { - case "text", "hex", "regex": - default: - return nil, fmt.Errorf("unknown multi-search mode %q", mode) - } - if query == "" { - return nil, errors.New("empty multi-search query") - } - out = append(out, MultiSearchQuery{Mode: mode, Query: query}) - } - if len(out) == 0 { - return nil, errors.New("no multi-search queries") - } - return out, nil -} - -func MultiSearch(d *document.Document, start, length int64, spec string, maxPerQuery int) (map[string]any, error) { - queries, err := ParseMultiSearchSpec(spec) - if err != nil { - return nil, err - } - if maxPerQuery <= 0 { - maxPerQuery = 1000 - } - results := make([]map[string]any, 0, len(queries)) - all := []map[string]any{} - for i, q := range queries { - var hits []SearchHit - switch q.Mode { - case "text": - hits, err = SearchBytes(d, start, length, []byte(q.Query), maxPerQuery) - case "hex": - var needle []byte - needle, err = parseMultiSearchHex(q.Query) - if err == nil { - hits, err = SearchBytes(d, start, length, needle, maxPerQuery) - } - case "regex": - if _, err = regexp.Compile(q.Query); err == nil { - hits, err = SearchRegex(d, start, length, q.Query, maxPerQuery) - } - } - if err != nil { - return nil, fmt.Errorf("query %d: %w", i+1, err) - } - maps := HitsToMaps(hits) - name := fmt.Sprintf("%s:%s", q.Mode, q.Query) - for _, h := range maps { - h["mode"] = q.Mode - h["query"] = q.Query - h["name"] = name - all = append(all, h) - } - results = append(results, map[string]any{ - "mode": q.Mode, - "query": q.Query, - "count": len(hits), - "hits": maps, - }) - } - return map[string]any{ - "queries": results, - "all": all, - "total": len(all), - }, nil -} - -func parseMultiSearchHex(s string) ([]byte, error) { - s = strings.Map(func(r rune) rune { - switch r { - case ' ', '\t', '\n', '\r', ':', '-': - return -1 - default: - return r - } - }, strings.TrimSpace(s)) - s = strings.TrimPrefix(strings.ToLower(s), "0x") - if len(s)%2 != 0 { - s = "0" + s - } - return hex.DecodeString(s) -} diff --git a/internal/leviathan/analysis/overview.go b/internal/leviathan/analysis/overview.go deleted file mode 100644 index 4a626f4..0000000 --- a/internal/leviathan/analysis/overview.go +++ /dev/null @@ -1,88 +0,0 @@ -package analysis - -import ( - "github.com/andydixon/link/internal/leviathan/document" -) - -// Overview returns a compact first-pass report intended for both UI -// surfaces. It favours fast, bounded summaries over exhaustive output. -func Overview(d *document.Document) (map[string]any, error) { - size := d.Size() - hashes, err := Hashes(d, 0, -1) - if err != nil { - return nil, err - } - entropy, err := Entropy(d, 0, min64(size, 8<<20), 4096, 0) - if err != nil { - return nil, err - } - stringsOut, err := ExtractStrings(d, 0, min64(size, 16<<20), 4, []string{"ascii", "utf16le", "utf16be"}, 5000) - if err != nil { - return nil, err - } - iocs, err := ExtractIOCs(d, 0, min64(size, 16<<20), 5) - if err != nil { - return nil, err - } - carves, err := Carve(d, 0, min64(size, 64<<20), 128) - if err != nil { - return nil, err - } - return map[string]any{ - "name": d.Name(), - "size": size, - "hashes": hashes, - "entropy": overviewEntropy(entropy), - "strings": overviewStrings(stringsOut), - "iocs": iocs["totals"], - "carve": overviewCarve(carves), - "limits": map[string]any{ - "entropyBytes": min64(size, 8<<20), - "stringsBytes": min64(size, 16<<20), - "iocBytes": min64(size, 16<<20), - "carveBytes": min64(size, 64<<20), - }, - }, nil -} - -func overviewEntropy(in map[string]any) map[string]any { - return map[string]any{ - "windowSize": in["windowSize"], - "step": in["step"], - "mean": in["mean"], - "min": in["min"], - "max": in["max"], - "highCount": in["highCount"], - } -} - -func overviewStrings(items []map[string]any) map[string]any { - counts := map[string]int{} - for _, item := range items { - if enc, ok := item["encoding"].(string); ok { - counts[enc]++ - } - } - return map[string]any{ - "total": len(items), - "byEncoding": counts, - } -} - -func overviewCarve(items []map[string]any) map[string]any { - counts := map[string]int{} - valid := 0 - for _, item := range items { - if kind, ok := item["kind"].(string); ok { - counts[kind]++ - } - if status, ok := item["status"].(string); ok && status == "valid" { - valid++ - } - } - return map[string]any{ - "total": len(items), - "valid": valid, - "byKind": counts, - } -} diff --git a/internal/leviathan/analysis/rules.go b/internal/leviathan/analysis/rules.go deleted file mode 100644 index 7381788..0000000 --- a/internal/leviathan/analysis/rules.go +++ /dev/null @@ -1,648 +0,0 @@ -package analysis - -import ( - "context" - "encoding/hex" - "errors" - "regexp" - "strconv" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// Rule represents a lightweight YARA-like rule. A rule matches when -// Condition evaluates to true for the document. Phase 1 supports: -// - "all of them" (every string pattern must appear) -// - "any of them" (at least one pattern must appear) -// - " of them" (at least N patterns) -// - named identifiers referencing specific strings with $x or !$x -type Rule struct { - Name string - Tags []string - Strings []RuleString - Condition string -} - -// RuleString is one string directive inside a rule. -type RuleString struct { - ID string - Kind string // "ascii", "hex", "regex" - Value string // original source text - // compiled: - raw []byte - regex *regexp.Regexp - nocase bool - ascii bool - wide bool - fullword bool -} - -// ParseRules parses a simplified YARA-like source: -// -// rule Name : tag1 tag2 { -// strings: -// $a = "hello" nocase -// $b = { 4d 5a ?? ?? } -// $c = /foo[0-9]+/ -// condition: -// any of them -// } -// -// Hex patterns support `??` wildcards by being converted to a regex. -func ParseRules(src string) ([]Rule, error) { - rules := []Rule{} - s := strings.TrimSpace(src) - if s == "" { - return rules, nil - } - // Split on "rule " keywords from start of line. - entries := splitRules(s) - for _, e := range entries { - r, err := parseRule(e) - if err != nil { - return rules, err - } - rules = append(rules, r) - } - return rules, nil -} - -func splitRules(src string) []string { - out := []string{} - i := 0 - for i < len(src) { - idx := strings.Index(src[i:], "rule ") - if idx < 0 { - break - } - j := i + idx - // find matching close brace (naïve but workable for phase 1) - depth := 0 - start := j - k := j - for k < len(src) { - c := src[k] - if c == '{' { - depth++ - } else if c == '}' { - depth-- - if depth == 0 { - out = append(out, src[start:k+1]) - k++ - break - } - } - k++ - } - i = k - } - return out -} - -var ruleHeaderRe = regexp.MustCompile(`(?s)^\s*rule\s+([A-Za-z_][A-Za-z0-9_]*)(?:\s*:\s*([^{]*))?\s*\{(.*)\}\s*$`) - -func parseRule(src string) (Rule, error) { - m := ruleHeaderRe.FindStringSubmatch(src) - if m == nil { - return Rule{}, errors.New("malformed rule") - } - r := Rule{Name: m[1]} - if tags := strings.TrimSpace(m[2]); tags != "" { - r.Tags = strings.Fields(tags) - } - body := m[3] - if i := strings.Index(body, "strings:"); i >= 0 { - sEnd := strings.Index(body, "condition:") - sect := body[i+len("strings:") : sEnd] - for _, line := range strings.Split(sect, "\n") { - line = strings.TrimSpace(line) - if line == "" || strings.HasPrefix(line, "//") { - continue - } - rs, err := parseRuleString(line) - if err != nil { - return r, err - } - r.Strings = append(r.Strings, rs) - } - } - if i := strings.Index(body, "condition:"); i >= 0 { - r.Condition = strings.TrimSpace(body[i+len("condition:"):]) - } else { - return r, errors.New("rule missing condition") - } - return r, nil -} - -var ruleStringRe = regexp.MustCompile(`^\s*(\$[A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.+?)\s*$`) - -func parseRuleString(line string) (RuleString, error) { - m := ruleStringRe.FindStringSubmatch(line) - if m == nil { - return RuleString{}, errors.New("bad string line: " + line) - } - id := m[1] - rest := strings.TrimSpace(m[2]) - value, modifiers, err := splitRuleValueModifiers(rest) - if err != nil { - return RuleString{}, err - } - rs := RuleString{ID: id, Value: value} - for _, mod := range modifiers { - switch strings.ToLower(mod) { - case "nocase": - rs.nocase = true - case "ascii": - rs.ascii = true - case "wide": - rs.wide = true - case "fullword": - rs.fullword = true - default: - return rs, errors.New("unknown string modifier: " + mod) - } - } - switch { - case strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`) && len(value) >= 2: - rs.Kind = "ascii" - rs.raw = []byte(value[1 : len(value)-1]) - if !rs.ascii && !rs.wide { - rs.ascii = true - } - case strings.HasPrefix(value, "{") && strings.HasSuffix(value, "}"): - rs.Kind = "hex" - pattern := regexFromHexPattern(value[1 : len(value)-1]) - re, err := regexp.Compile("(?s)" + pattern) - if err != nil { - return rs, err - } - rs.regex = re - case strings.HasPrefix(value, "/") && strings.HasSuffix(value, "/"): - rs.Kind = "regex" - pat := value[1 : len(value)-1] - if rs.nocase { - pat = "(?i)" + pat - } - re, err := regexp.Compile(pat) - if err != nil { - return rs, err - } - rs.regex = re - default: - return rs, errors.New("unknown string form: " + value) - } - return rs, nil -} - -func splitRuleValueModifiers(rest string) (string, []string, error) { - rest = strings.TrimSpace(rest) - if rest == "" { - return "", nil, errors.New("empty string value") - } - var end int - switch rest[0] { - case '"': - escaped := false - end = -1 - for i := 1; i < len(rest); i++ { - c := rest[i] - if escaped { - escaped = false - continue - } - if c == '\\' { - escaped = true - continue - } - if c == '"' { - end = i + 1 - break - } - } - if end < 0 { - return "", nil, errors.New("unterminated string literal") - } - case '{': - idx := strings.IndexByte(rest, '}') - if idx < 0 { - return "", nil, errors.New("unterminated hex string") - } - end = idx + 1 - case '/': - escaped := false - end = -1 - for i := 1; i < len(rest); i++ { - c := rest[i] - if escaped { - escaped = false - continue - } - if c == '\\' { - escaped = true - continue - } - if c == '/' { - end = i + 1 - break - } - } - if end < 0 { - return "", nil, errors.New("unterminated regex") - } - default: - return "", nil, errors.New("unknown string form: " + rest) - } - return strings.TrimSpace(rest[:end]), strings.Fields(strings.TrimSpace(rest[end:])), nil -} - -func regexFromHexPattern(hexPat string) string { - var sb strings.Builder - hexPat = strings.ReplaceAll(hexPat, " ", "") - for i := 0; i < len(hexPat); { - if i+1 >= len(hexPat) { - break - } - a, b := hexPat[i], hexPat[i+1] - if a == '?' && b == '?' { - sb.WriteString(`[\x00-\xff]`) - } else { - decoded, err := hex.DecodeString(string([]byte{a, b})) - if err != nil { - // bail to literal to avoid panic - sb.WriteString(regexp.QuoteMeta(string([]byte{a, b}))) - } else { - sb.WriteString(regexp.QuoteMeta(string(decoded))) - } - } - i += 2 - } - return sb.String() -} - -// RuleMatch is a per-rule result. -type RuleMatch struct { - Name string - Tags []string - Matched bool - Hits map[string][]SearchHit -} - -// RunRules scans rules against the document range and returns matches. -func RunRules(d *document.Document, start, length int64, rules []Rule, maxPerString int) ([]map[string]any, error) { - return RunRulesContext(context.Background(), d, start, length, rules, maxPerString) -} - -func RunRulesContext(ctx context.Context, d *document.Document, start, length int64, rules []Rule, maxPerString int) ([]map[string]any, error) { - if maxPerString <= 0 { - maxPerString = 1000 - } - out := make([]map[string]any, 0, len(rules)) - for _, r := range rules { - if err := ctx.Err(); err != nil { - return nil, err - } - hits := map[string][]SearchHit{} - for _, s := range r.Strings { - if err := ctx.Err(); err != nil { - return nil, err - } - switch s.Kind { - case "ascii": - found, err := searchRuleTextContext(ctx, d, start, length, s, maxPerString) - if err != nil { - return nil, err - } - hits[s.ID] = found - case "hex", "regex": - found, err := SearchRegexContext(ctx, d, start, length, s.regex.String(), maxPerString) - if err != nil { - return nil, err - } - if s.fullword { - found = filterFullWordHits(d, found, false) - } - hits[s.ID] = found - } - } - matched := evalCondition(r, hits) - var hitFlat []map[string]any - for id, hs := range hits { - for _, h := range hs { - hitFlat = append(hitFlat, map[string]any{ - "id": id, - "offset": h.Offset, - "length": h.Length, - "sample": h.Sample, - }) - } - } - out = append(out, map[string]any{ - "name": r.Name, - "tags": toAnySlice(r.Tags), - "matched": matched, - "hits": hitFlat, - }) - } - return out, nil -} - -func searchRuleText(d *document.Document, start, length int64, s RuleString, max int) ([]SearchHit, error) { - return searchRuleTextContext(context.Background(), d, start, length, s, max) -} - -func searchRuleTextContext(ctx context.Context, d *document.Document, start, length int64, s RuleString, max int) ([]SearchHit, error) { - out := []SearchHit{} - if s.ascii { - var ( - found []SearchHit - err error - ) - if s.nocase { - found, err = SearchRegexContext(ctx, d, start, length, "(?i)"+regexp.QuoteMeta(string(s.raw)), max) - } else { - found, err = SearchBytesContext(ctx, d, start, length, s.raw, max) - } - if err != nil { - return nil, err - } - if s.fullword { - found = filterFullWordHits(d, found, false) - } - out = append(out, found...) - } - if s.wide { - wide := utf16LEBytes(s.raw) - var ( - found []SearchHit - err error - ) - if s.nocase { - pat := wideNoCasePattern(s.raw) - found, err = SearchRegexContext(ctx, d, start, length, pat, max) - } else { - found, err = SearchBytesContext(ctx, d, start, length, wide, max) - } - if err != nil { - return nil, err - } - if s.fullword { - found = filterFullWordHits(d, found, true) - } - out = append(out, found...) - } - if len(out) > max { - out = out[:max] - } - return out, nil -} - -func utf16LEBytes(in []byte) []byte { - out := make([]byte, 0, len(in)*2) - for _, b := range in { - out = append(out, b, 0) - } - return out -} - -func wideNoCasePattern(in []byte) string { - var sb strings.Builder - sb.WriteString("(?i)") - for _, b := range in { - sb.WriteString(regexp.QuoteMeta(string([]byte{b, 0}))) - } - return sb.String() -} - -func filterFullWordHits(d *document.Document, hits []SearchHit, wide bool) []SearchHit { - out := hits[:0] - for _, h := range hits { - if isFullWordHit(d, h, wide) { - out = append(out, h) - } - } - return out -} - -func isFullWordHit(d *document.Document, h SearchHit, wide bool) bool { - if wide { - beforeOff := h.Offset - 2 - afterOff := h.Offset + int64(h.Length) - beforeWord := false - afterWord := false - if beforeOff >= 0 { - if b, err := d.Range(beforeOff, 2); err == nil && len(b) == 2 && b[1] == 0 { - beforeWord = isWordByte(b[0]) - } - } - if afterOff+1 < d.Size() { - if b, err := d.Range(afterOff, 2); err == nil && len(b) == 2 && b[1] == 0 { - afterWord = isWordByte(b[0]) - } - } - return !beforeWord && !afterWord - } - beforeWord := false - afterWord := false - if h.Offset > 0 { - if b, err := d.At(h.Offset - 1); err == nil { - beforeWord = isWordByte(b) - } - } - if h.Offset+int64(h.Length) < d.Size() { - if b, err := d.At(h.Offset + int64(h.Length)); err == nil { - afterWord = isWordByte(b) - } - } - return !beforeWord && !afterWord -} - -func isWordByte(b byte) bool { - return (b >= 'A' && b <= 'Z') || (b >= 'a' && b <= 'z') || (b >= '0' && b <= '9') || b == '_' -} - -func toAnySlice(in []string) []any { - out := make([]any, len(in)) - for i, s := range in { - out[i] = s - } - return out -} - -func evalCondition(r Rule, hits map[string][]SearchHit) bool { - cond := strings.TrimSpace(r.Condition) - condLower := strings.ToLower(cond) - present := 0 - for _, s := range r.Strings { - if len(hits[s.ID]) > 0 { - present++ - } - } - switch { - case condLower == "any of them": - return present > 0 - case condLower == "all of them": - return present == len(r.Strings) - case strings.HasSuffix(condLower, "of them"): - // " of them" - parts := strings.Fields(condLower) - if len(parts) >= 3 { - n, ok := parseRuleCount(parts[0]) - return ok && present >= n - } - } - if ok, matched := evalRuleSetCondition(cond, hits); ok { - return matched - } - p := conditionParser{tokens: tokenizeCondition(cond), hits: hits} - return p.parse() -} - -func evalRuleSetCondition(cond string, hits map[string][]SearchHit) (bool, bool) { - tokens := tokenizeCondition(cond) - if len(tokens) < 4 || strings.ToLower(tokens[1]) != "of" || tokens[2] != "(" || tokens[len(tokens)-1] != ")" { - return false, false - } - ids := []string{} - for _, tok := range tokens[3 : len(tokens)-1] { - if tok == "," { - continue - } - if strings.HasPrefix(tok, "$") { - ids = append(ids, tok) - } - } - if len(ids) == 0 { - return true, false - } - count := 0 - for _, id := range ids { - if len(hits[id]) > 0 { - count++ - } - } - switch strings.ToLower(tokens[0]) { - case "any": - return true, count > 0 - case "all": - return true, count == len(ids) - default: - n, ok := parseRuleCount(strings.ToLower(tokens[0])) - return true, ok && count >= n - } -} - -func parseRuleCount(s string) (int, bool) { - switch s { - case "1", "one": - return 1, true - case "2", "two": - return 2, true - case "3", "three": - return 3, true - default: - n, err := strconv.Atoi(s) - return n, err == nil - } -} - -type conditionParser struct { - tokens []string - pos int - hits map[string][]SearchHit -} - -func (p *conditionParser) parse() bool { - v := p.parseOr() - return v -} - -func (p *conditionParser) parseOr() bool { - v := p.parseAnd() - for strings.EqualFold(p.peek(), "or") { - p.next() - v = p.parseAnd() || v - } - return v -} - -func (p *conditionParser) parseAnd() bool { - v := p.parseNot() - for strings.EqualFold(p.peek(), "and") { - p.next() - v = p.parseNot() && v - } - return v -} - -func (p *conditionParser) parseNot() bool { - if strings.EqualFold(p.peek(), "not") || p.peek() == "!" { - p.next() - return !p.parseNot() - } - return p.parsePrimary() -} - -func (p *conditionParser) parsePrimary() bool { - tok := p.next() - switch { - case tok == "": - return false - case tok == "(": - v := p.parseOr() - if p.peek() == ")" { - p.next() - } - return v - case strings.EqualFold(tok, "true"): - return true - case strings.EqualFold(tok, "false"): - return false - case strings.HasPrefix(tok, "$"): - return len(p.hits[tok]) > 0 - default: - return false - } -} - -func (p *conditionParser) peek() string { - if p.pos >= len(p.tokens) { - return "" - } - return p.tokens[p.pos] -} - -func (p *conditionParser) next() string { - if p.pos >= len(p.tokens) { - return "" - } - tok := p.tokens[p.pos] - p.pos++ - return tok -} - -func tokenizeCondition(cond string) []string { - out := []string{} - for i := 0; i < len(cond); { - c := cond[i] - if c == ' ' || c == '\t' || c == '\n' || c == '\r' { - i++ - continue - } - if strings.ContainsRune("(),!", rune(c)) { - out = append(out, string(c)) - i++ - continue - } - start := i - for i < len(cond) { - c = cond[i] - if c == ' ' || c == '\t' || c == '\n' || c == '\r' || strings.ContainsRune("(),!", rune(c)) { - break - } - i++ - } - out = append(out, cond[start:i]) - } - return out -} diff --git a/internal/leviathan/analysis/search.go b/internal/leviathan/analysis/search.go deleted file mode 100644 index 9818d93..0000000 --- a/internal/leviathan/analysis/search.go +++ /dev/null @@ -1,189 +0,0 @@ -package analysis - -import ( - "bytes" - "context" - "errors" - "regexp" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// SearchHit describes one search result. -type SearchHit struct { - Offset int64 `json:"offset"` - Length int `json:"length"` - Sample string `json:"sample"` // short hex preview -} - -// SearchBytes finds `needle` in the document range and returns hit -// offsets. Scans chunk by chunk with an overlap equal to len(needle)-1 -// so cross-chunk matches are not missed. -func SearchBytes(d *document.Document, start, length int64, needle []byte, max int) ([]SearchHit, error) { - return SearchBytesContext(context.Background(), d, start, length, needle, max) -} - -func SearchBytesContext(ctx context.Context, d *document.Document, start, length int64, needle []byte, max int) ([]SearchHit, error) { - if len(needle) == 0 { - return nil, errors.New("empty needle") - } - if max <= 0 { - max = 10_000 - } - if length < 0 { - length = d.Size() - start - } - - hits := make([]SearchHit, 0, 64) - overlap := int64(len(needle) - 1) - - // We chunk in windows of chunkSize + overlap. - const chunkSize = 1 << 20 // 1MB - off := start - endExclusive := start + length - - for off < endExclusive && len(hits) < max { - if err := ctx.Err(); err != nil { - return nil, err - } - winEnd := off + chunkSize - if winEnd > endExclusive { - winEnd = endExclusive - } - readEnd := winEnd + overlap - if readEnd > endExclusive { - readEnd = endExclusive - } - buf, err := d.Range(off, readEnd-off) - if err != nil { - return nil, err - } - // Limit search to bytes whose start lies in [0, winEnd-off) - searchLimit := winEnd - off - scan := 0 - for { - if err := ctx.Err(); err != nil { - return nil, err - } - idx := bytes.Index(buf[scan:], needle) - if idx < 0 { - break - } - abs := int64(scan+idx) + off - relStart := abs - off - if relStart >= searchLimit { - break - } - hits = append(hits, SearchHit{ - Offset: abs, - Length: len(needle), - Sample: hexPreview(buf[scan+idx:], len(needle)), - }) - if len(hits) >= max { - break - } - scan = scan + idx + 1 - if scan >= len(buf) { - break - } - } - off = winEnd - } - return hits, nil -} - -// SearchRegex runs a Go regexp across the document range, matching -// byte-wise. It decodes matches chunk by chunk with overlap, bounded -// by a generous max hit count. -func SearchRegex(d *document.Document, start, length int64, pattern string, max int) ([]SearchHit, error) { - return SearchRegexContext(context.Background(), d, start, length, pattern, max) -} - -func SearchRegexContext(ctx context.Context, d *document.Document, start, length int64, pattern string, max int) ([]SearchHit, error) { - re, err := regexp.Compile(pattern) - if err != nil { - return nil, err - } - if max <= 0 { - max = 10_000 - } - if length < 0 { - length = d.Size() - start - } - - // For regex, a conservative overlap is 4 KiB so matches spanning - // chunk boundaries are still caught. Very long matches may still - // be missed in extreme cases. - const chunkSize = 1 << 20 - const overlap = 4096 - - hits := make([]SearchHit, 0, 64) - off := start - endExclusive := start + length - - for off < endExclusive && len(hits) < max { - if err := ctx.Err(); err != nil { - return nil, err - } - winEnd := off + chunkSize - if winEnd > endExclusive { - winEnd = endExclusive - } - readEnd := winEnd + overlap - if readEnd > endExclusive { - readEnd = endExclusive - } - buf, err := d.Range(off, readEnd-off) - if err != nil { - return nil, err - } - searchLimit := winEnd - off - locs := re.FindAllIndex(buf, max-len(hits)) - for _, loc := range locs { - if err := ctx.Err(); err != nil { - return nil, err - } - relStart := int64(loc[0]) - if relStart >= searchLimit { - continue - } - abs := relStart + off - hits = append(hits, SearchHit{ - Offset: abs, - Length: loc[1] - loc[0], - Sample: hexPreview(buf[loc[0]:loc[1]], loc[1]-loc[0]), - }) - } - off = winEnd - } - return hits, nil -} - -func hexPreview(b []byte, n int) string { - if n > 32 { - n = 32 - } - if n > len(b) { - n = len(b) - } - const hexchars = "0123456789abcdef" - out := make([]byte, n*2) - for i := 0; i < n; i++ { - out[i*2] = hexchars[b[i]>>4] - out[i*2+1] = hexchars[b[i]&0x0f] - } - return string(out) -} - -// HitsToMaps converts hits for bridge return. -func HitsToMaps(hits []SearchHit) []map[string]any { - out := make([]map[string]any, len(hits)) - for i, h := range hits { - out[i] = map[string]any{ - "offset": h.Offset, - "length": h.Length, - "sample": h.Sample, - } - } - return out -} diff --git a/internal/leviathan/analysis/strings.go b/internal/leviathan/analysis/strings.go deleted file mode 100644 index f6a9f1a..0000000 --- a/internal/leviathan/analysis/strings.go +++ /dev/null @@ -1,198 +0,0 @@ -package analysis - -import ( - "context" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// StringHit represents one extracted string. -type StringHit struct { - Offset int64 `json:"offset"` - Length int `json:"length"` - Encoding string `json:"encoding"` // ascii | utf16le | utf16be - Value string `json:"value"` -} - -// ExtractStrings finds printable runs in the specified range. -// Supports ASCII and UTF-16 LE/BE. minLen is in characters. -func ExtractStrings(d *document.Document, start, length int64, minLen int, encodings []string, max int) ([]map[string]any, error) { - return ExtractStringsContext(context.Background(), d, start, length, minLen, encodings, max) -} - -func ExtractStringsContext(ctx context.Context, d *document.Document, start, length int64, minLen int, encodings []string, max int) ([]map[string]any, error) { - if minLen <= 0 { - minLen = 4 - } - if max <= 0 { - max = 50_000 - } - if length < 0 { - length = d.Size() - start - } - - doASCII, doLE, doBE := false, false, false - if len(encodings) == 0 { - doASCII, doLE, doBE = true, true, true - } else { - for _, e := range encodings { - switch e { - case "ascii": - doASCII = true - case "utf16le": - doLE = true - case "utf16be": - doBE = true - } - } - } - - out := make([]map[string]any, 0, 256) - - // ASCII - if doASCII { - if err := appendASCII(ctx, d, start, length, minLen, &out, max); err != nil { - return nil, err - } - } - // UTF-16 - if len(out) < max && doLE { - if err := appendUTF16(ctx, d, start, length, minLen, true, &out, max); err != nil { - return nil, err - } - } - if len(out) < max && doBE { - if err := appendUTF16(ctx, d, start, length, minLen, false, &out, max); err != nil { - return nil, err - } - } - return out, nil -} - -func appendASCII(ctx context.Context, d *document.Document, start, length int64, minLen int, out *[]map[string]any, max int) error { - // Chunked scan with a carry for runs crossing chunk boundaries. - var runStart int64 = -1 - var runBuf []byte - emit := func() { - if runStart >= 0 && len(runBuf) >= minLen { - *out = append(*out, map[string]any{ - "offset": runStart, - "length": len(runBuf), - "encoding": "ascii", - "value": string(runBuf), - }) - } - runStart = -1 - runBuf = runBuf[:0] - } - - if err := d.ForEachChunk(start, length, func(off int64, chunk []byte) error { - if err := ctx.Err(); err != nil { - return err - } - for i, b := range chunk { - if i%4096 == 0 { - if err := ctx.Err(); err != nil { - return err - } - } - if isPrintableASCII(b) { - if runStart < 0 { - runStart = off + int64(i) - runBuf = make([]byte, 0, 32) - } - runBuf = append(runBuf, b) - } else { - if len(*out) >= max { - return nil - } - emit() - } - } - return nil - }); err != nil { - return err - } - emit() - return nil -} - -func appendUTF16(ctx context.Context, d *document.Document, start, length int64, minLen int, le bool, out *[]map[string]any, max int) error { - // Step by 2. Produce one string for each contiguous run of printable code units. - if length < 2 { - return nil - } - // Read chunks aligned to 2 bytes so pairs don't split. - const chunkSize = 1 << 20 - end := start + length - var runStart int64 = -1 - var runBuf []rune - - enc := "utf16le" - if !le { - enc = "utf16be" - } - - emit := func() { - if runStart >= 0 && len(runBuf) >= minLen { - *out = append(*out, map[string]any{ - "offset": runStart, - "length": len(runBuf) * 2, - "encoding": enc, - "value": string(runBuf), - }) - } - runStart = -1 - runBuf = runBuf[:0] - } - - for off := start; off+1 < end; { - if err := ctx.Err(); err != nil { - return err - } - winEnd := off + chunkSize - if winEnd > end { - winEnd = end - } - if (winEnd-off)%2 != 0 { - winEnd-- // keep pair alignment - } - buf, err := d.Range(off, winEnd-off) - if err != nil { - return err - } - for i := 0; i+1 < len(buf); i += 2 { - if i%4096 == 0 { - if err := ctx.Err(); err != nil { - return err - } - } - var cu uint16 - if le { - cu = uint16(buf[i]) | uint16(buf[i+1])<<8 - } else { - cu = uint16(buf[i])<<8 | uint16(buf[i+1]) - } - if cu >= 0x20 && cu <= 0x7e { - if runStart < 0 { - runStart = off + int64(i) - runBuf = make([]rune, 0, 32) - } - runBuf = append(runBuf, rune(cu)) - } else { - if len(*out) >= max { - emit() - return nil - } - emit() - } - } - off = winEnd - } - emit() - return nil -} - -func isPrintableASCII(b byte) bool { - return b == '\t' || (b >= 0x20 && b <= 0x7e) -} diff --git a/internal/leviathan/analysis/timestamps.go b/internal/leviathan/analysis/timestamps.go deleted file mode 100644 index 439d917..0000000 --- a/internal/leviathan/analysis/timestamps.go +++ /dev/null @@ -1,106 +0,0 @@ -package analysis - -import ( - "encoding/binary" - "math" - "time" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// ScanTimestamps walks the range looking for plausible timestamps in -// common encodings. This is a heuristic: it reports any 4/8-byte -// window whose decoded time falls in [1990-01-01, 2050-12-31]. -func ScanTimestamps(d *document.Document, start, length int64, maxHits int) (map[string]any, error) { - if length < 0 { - length = d.Size() - start - } - if maxHits <= 0 { - maxHits = 20_000 - } - - minT := time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC).Unix() - maxT := time.Date(2050, 12, 31, 0, 0, 0, 0, time.UTC).Unix() - ok := func(sec int64) bool { return sec >= minT && sec <= maxT } - - hits := make([]map[string]any, 0, 256) - counts := map[string]int{} - - const chunk = 1 << 20 - end := start + length - overlap := int64(7) // enough for 8-byte types - for off := start; off < end && len(hits) < maxHits; { - winEnd := off + chunk - if winEnd > end { - winEnd = end - } - readEnd := winEnd + overlap - if readEnd > end { - readEnd = end - } - buf, err := d.Range(off, readEnd-off) - if err != nil { - return nil, err - } - limit := int(winEnd - off) - for i := 0; i < limit && len(hits) < maxHits; i += 4 { - if i+4 <= len(buf) { - vLE := int64(int32(binary.LittleEndian.Uint32(buf[i:]))) - if ok(vLE) { - hits = append(hits, hit("unix_le", off+int64(i), 4, time.Unix(vLE, 0).UTC())) - counts["unix_le"]++ - continue - } - vBE := int64(int32(binary.BigEndian.Uint32(buf[i:]))) - if ok(vBE) { - hits = append(hits, hit("unix_be", off+int64(i), 4, time.Unix(vBE, 0).UTC())) - counts["unix_be"]++ - continue - } - } - if i+8 <= len(buf) { - v := binary.LittleEndian.Uint64(buf[i:]) - // Windows FILETIME - sec := int64(v/10_000_000) - filetimeEpochDelta - if ok(sec) { - hits = append(hits, hit("filetime_le", off+int64(i), 8, time.Unix(sec, 0).UTC())) - counts["filetime_le"]++ - continue - } - // WebKit (microseconds since 1601) - sec = int64(v/1_000_000) - filetimeEpochDelta - if ok(sec) { - hits = append(hits, hit("webkit_le", off+int64(i), 8, time.Unix(sec, 0).UTC())) - counts["webkit_le"]++ - continue - } - // OLE automation date - fdays := math.Float64frombits(v) - if fdays > 2 && fdays < 75000 { - base := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC) - t := base.Add(time.Duration(fdays*86400) * time.Second) - if t.Unix() >= minT && t.Unix() <= maxT { - hits = append(hits, hit("ole_le", off+int64(i), 8, t)) - counts["ole_le"]++ - continue - } - } - } - } - off = winEnd - } - return map[string]any{ - "hits": hits, - "counts": counts, - }, nil -} - -func hit(kind string, off int64, size int, t time.Time) map[string]any { - return map[string]any{ - "kind": kind, - "offset": off, - "length": size, - "iso": t.Format(time.RFC3339), - "unix": t.Unix(), - } -} diff --git a/internal/leviathan/binsnoop/README.md b/internal/leviathan/binsnoop/README.md deleted file mode 100644 index c37c8e3..0000000 --- a/internal/leviathan/binsnoop/README.md +++ /dev/null @@ -1,277 +0,0 @@ -# binsnoop - -A pure-Go binary analysis library for detecting and extracting embedded files, firmware images, compressed data, filesystems, and more from binary blobs. Ported from the [binwalk](https://github.com/ReFirmLabs/binwalk) Rust codebase with like-for-like scanning logic. - -**Zero external dependencies** — everything is self-contained, including the Aho-Corasick multi-pattern matcher, CRC32 validation, and binary structure parsing. - -## Features - -- **111 built-in signatures** covering archives, compression, filesystems, images, executables, firmware headers, cryptographic constructs, and more -- **Aho-Corasick** multi-pattern scanning with overlapping match support -- **Signature validation** — every magic byte match is verified by a dedicated parser that checks headers, checksums, and structural integrity -- **Confidence scoring** — results are ranked Low / Medium / High so callers can filter noise -- **Extraction support** — carve identified objects out of a binary, or invoke external tools (7z, unsquashfs, etc.) to fully extract them -- **Filtering** — include/exclude signatures by name, restrict short signatures to offset 0, or add custom signatures -- **JSON-friendly** — all result types carry `json` struct tags for easy serialisation - -## Installation - -```bash -go get binsnoop -``` - -Or add to your `go.mod`: - -``` -require binsnoop v0.0.0 -``` - -## Quick Start - -```go -package main - -import ( - "fmt" - "binsnoop" -) - -func main() { - scanner := binsnoop.New() - - // Scan a file - results, err := scanner.ScanFile("firmware.bin") - if err != nil { - panic(err) - } - - for _, r := range results { - fmt.Printf("[0x%08X] %s (confidence: %d, size: %d)\n", - r.Offset, r.Description, r.Confidence, r.Size) - } -} -``` - -## API Reference - -### Creating a Scanner - -```go -// Default scanner with all 111 signatures -scanner := binsnoop.New() - -// Scanner with options -scanner, err := binsnoop.NewWithOptions(binsnoop.Options{ - Include: []string{"gzip", "squashfs", "uimage"}, // only these signatures - Exclude: []string{"copyright"}, // remove these signatures - FullSearch: true, // match short sigs at any offset - AdditionalSignatures: []binsnoop.Signature{...}, // add custom signatures -}) -``` - -### Scanning - -```go -// Scan raw bytes -results := scanner.ScanBytes(data) - -// Scan a file on disk -results, err := scanner.ScanFile("/path/to/firmware.bin") -``` - -Both return `[]SignatureResult` sorted by offset. - -### Analysis (Scan + Extraction) - -```go -// Analyse raw bytes (with optional extraction) -analysis := scanner.AnalyzeBytes(data, "firmware.bin", true) - -// Analyse a file on disk -analysis, err := scanner.AnalyzeFile("/path/to/firmware.bin", true) -``` - -Returns `*AnalysisResults` containing the file map and extraction outcomes. - -### Carving - -Extract a single identified object from raw data: - -```go -err := binsnoop.CarveResult(data, results[0], "/tmp/output") -``` - -This writes the bytes at the result's offset/size to a file in the output directory. - -### Listing Signatures - -```go -names := binsnoop.ListSignatures() // []string of all registered signature names -``` - -### Scanner Info - -```go -scanner.SignatureCount() // number of registered signatures (111) -scanner.PatternCount() // total magic byte patterns across all signatures -``` - -## Types - -### `SignatureResult` - -```go -type SignatureResult struct { - Offset int `json:"offset"` // byte offset in the input data - ID string `json:"id"` // unique identifier for this match - Size int `json:"size"` // size in bytes (0 = unknown) - Name string `json:"name"` // signature name (e.g. "gzip") - Confidence uint8 `json:"confidence"` // 0=Low, 128=Medium, 250=High - Description string `json:"description"` // human-readable description - AlwaysDisplay bool `json:"always_display"` // show even if low confidence - ExtractionDeclined bool `json:"extraction_declined"` // parser advises against extraction - PreferredExtractor *Extractor `json:"-"` // extractor to use for this result -} -``` - -### `Options` - -```go -type Options struct { - Include []string // restrict to these signature names - Exclude []string // remove these signature names - FullSearch bool // short sigs match at any offset - AdditionalSignatures []Signature // user-defined signatures -} -``` - -### `AnalysisResults` - -```go -type AnalysisResults struct { - FilePath string `json:"file_path"` - FileMap []SignatureResult `json:"file_map"` - Extractions map[string]*ExtractionResult `json:"extractions"` -} -``` - -### Confidence Levels - -| Constant | Value | Meaning | -|----------|-------|---------| -| `ConfidenceLow` | 0 | Magic matched but weak validation | -| `ConfidenceMedium` | 128 | Header parsed and mostly valid | -| `ConfidenceHigh` | 250 | Full structural validation passed | - -## Custom Signatures - -You can add your own signatures at scanner creation: - -```go -mySignature := binsnoop.Signature{ - Name: "my_format", - Magic: [][]byte{[]byte("MYMAGIC")}, - Parser: func(data []byte, offset int) (*binsnoop.SignatureResult, error) { - // Validate the match and return a result - if offset+16 > len(data) { - return nil, fmt.Errorf("insufficient data") - } - return &binsnoop.SignatureResult{ - Offset: offset, - Size: 1024, - Description: "My custom format, size: 1024 bytes", - Confidence: binsnoop.ConfidenceHigh, - }, nil - }, - Description: "My custom file format", -} - -scanner, _ := binsnoop.NewWithOptions(binsnoop.Options{ - AdditionalSignatures: []binsnoop.Signature{mySignature}, -}) -``` - -## Supported Signatures - -### Archives (12) -gzip, zip, tarball, 7zip, rar, cab, deb, cpio, arj, iso9660, dahua\_zip - -### Compression (10) -lzma, bzip2, xz, zstd, lz4, lzop, lzfse, zlib, compress'd - -### Filesystems (13) -squashfs, cramfs, jffs2, yaffs, romfs, ext, fat, ntfs, apfs, btrfs, logfs, ubi, ubifs - -### Images & Media (7) -png, jpeg, bmp, gif, svg, riff, pdf - -### Executables (2) -elf, pe - -### Firmware (32) -uimage, uboot, trx, tplink, tplink\_rtos, dlob, arcadyan, chk, seama, cfe, jboot\_arm, jboot\_stag, jboot\_sch2, packimg, binhdr, autel, android\_sparse, android\_bootimg, dtb, dlink\_tlv, dlke, shrs, dkbs, dms, encfw, encrpted\_img, wince, mh01, csman, dxbc, matter\_ota, rtk - -### Cryptographic (17) -aes\_sbox, aes\_forward\_table, aes\_reverse\_table, aes\_rcon, aes\_acceleration\_table, rsa, openssl, gpg\_signed, pem\_certificate, pem\_public\_key, pem\_private\_key, pkcs\_der\_hash, luks, crc32, sha256, md5 - -### Miscellaneous (18) -copyright, wind\_kernel, vxworks\_symtab, ecos, linux\_kernel, linux\_boot\_image, linux\_arm\_zimage, linux\_arm64\_boot\_image, efigpt, mbr, uefi\_pi\_volume, uefi\_capsule, pchrom, pcapng, pjl, srecord, dmg, qnx\_ifs, dpapi, qcow - -## Extraction - -Many signatures have associated extractors that invoke external command-line tools. When you call `AnalyzeFile` or `AnalyzeBytes` with `extract: true`, binsnoop will: - -1. Carve the identified data to a temporary file -2. Run the configured external tool (e.g. `7z x`, `unsquashfs`, `jefferson`) -3. Return the extraction results including output directory and success/failure status - -**Common external tools used:** - -| Tool | Signatures | -|------|-----------| -| `7z` | zip, cpio, arj, iso9660, xz, lzma, squashfs, dmg, efigpt | -| `gzip` | gzip | -| `bzip2` | bzip2 | -| `zstd` | zstd | -| `lz4` | lz4 | -| `unsquashfs` | squashfs | -| `jefferson` | jffs2 | -| `unyaffs` | yaffs | -| `tsk_recover` | ext, fat, ntfs | -| `uefi-firmware-parser` | uefi\_pi\_volume, uefi\_capsule, pchrom | -| `simg2img` | android\_sparse | -| `dtc` | dtb | - -## How It Works - -### Scanning Algorithm - -1. **Short-signature pass** — signatures marked `Short` (e.g. zlib, MBR) are checked only at offset 0 (unless `FullSearch` is enabled) -2. **Aho-Corasick scan** — all magic byte patterns are compiled into an Aho-Corasick automaton for efficient multi-pattern matching. When a high-confidence sized match is found, scanning restarts from the end of that match -3. **Validation** — every magic match is passed to its signature parser which validates headers, checksums, and structure -4. **Post-processing** — results are sorted by offset, overlapping/duplicate matches are resolved (higher confidence wins), and unknown sizes are estimated based on the next significant match or EOF - -### Architecture - -``` -binsnoop.go Public API (New, ScanBytes, ScanFile, AnalyzeFile, CarveResult, etc.) -scanner.go Core scan engine (Aho-Corasick integration, post-processing) -match.go Self-contained Aho-Corasick trie with overlapping match support -parse.go Generic binary structure parser (u8/u16/u24/u32/u64, big/little endian) -types.go Core types (SignatureResult, Signature, Extractor, Options, etc.) -common.go Utility functions (CRC32, endian helpers, string helpers) -extract.go Carving and external tool execution -magic.go Signature registry — wires all 111 signatures together -sig_archives.go Archive signature parsers -sig_compression.go Compression signature parsers -sig_filesystems.go Filesystem signature parsers -sig_images.go Image/media signature parsers -sig_executables.go Executable signature parsers -sig_firmware.go Firmware header signature parsers -sig_crypto.go Cryptographic signature parsers -sig_misc.go Miscellaneous signature parsers -``` - -## License - -This project is a Go port of [binwalk](https://github.com/ReFirmLabs/binwalk) by ReFirmLabs. See the original project for licensing details. diff --git a/internal/leviathan/binsnoop/binsnoop.go b/internal/leviathan/binsnoop/binsnoop.go deleted file mode 100644 index 3c38222..0000000 --- a/internal/leviathan/binsnoop/binsnoop.go +++ /dev/null @@ -1,177 +0,0 @@ -// Package binsnoop identifies and optionally extracts files embedded inside binary data. -// -// It is a Go port of the binwalk scanning engine, implementing all magic byte signatures, -// structure parsers, and the Aho-Corasick based multi-pattern matching algorithm. -// -// # Quick Start -// -// scanner := binsnoop.New() -// data, _ := os.ReadFile("firmware.bin") -// results := scanner.Scan(data) -// for _, r := range results { -// fmt.Printf("0x%08X %s\n", r.Offset, r.Description) -// } -// -// # Extraction -// -// // Extract a specific result -// err := binsnoop.CarveResult(data, results[0], "/tmp/output") -// -// # Filtering -// -// // Only scan for specific signatures -// scanner := binsnoop.NewWithOptions(Options{Include: []string{"gzip", "squashfs"}}) -package binsnoop - -import ( - "fmt" - "os" - "path/filepath" - "strings" -) - -// Options configures the Scanner. -type Options struct { - // Include restricts scanning to only these signature names. - Include []string - // Exclude removes these signature names from scanning. - Exclude []string - // FullSearch makes short signatures match at any offset, not just offset 0. - FullSearch bool - // AdditionalSignatures adds user-defined signatures to the scanner. - AdditionalSignatures []Signature -} - -// New creates a Scanner with all default signatures enabled. -func New() *Scanner { - s, _ := NewWithOptions(Options{}) - return s -} - -// NewWithOptions creates a Scanner with the specified options. -func NewWithOptions(opts Options) (*Scanner, error) { - s := &Scanner{ - patternSigTable: make(map[int]Signature), - extractorLookup: make(map[string]*Extractor), - } - - allSigs := AllSignatures() - if opts.AdditionalSignatures != nil { - allSigs = append(allSigs, opts.AdditionalSignatures...) - } - - for _, sig := range allSigs { - if !includeSig(&sig, opts.Include, opts.Exclude) { - continue - } - - s.signatureCount++ - s.patternCount += len(sig.Magic) - - // Register extractor - var ext *Extractor - if sig.Extractor != nil { - extCopy := *sig.Extractor - ext = &extCopy - } - s.extractorLookup[sig.Name] = ext - - for _, pattern := range sig.Magic { - if sig.Short && !opts.FullSearch { - s.shortSignatures = append(s.shortSignatures, sig) - break - } - s.patternSigTable[len(s.patterns)] = sig - s.patterns = append(s.patterns, pattern) - } - } - - s.compile() - return s, nil -} - -// ScanBytes scans raw bytes for signatures and returns all matches. -func (s *Scanner) ScanBytes(data []byte) []SignatureResult { - return s.Scan(data) -} - -// ScanFile reads a file from disk and scans it for signatures. -func (s *Scanner) ScanFile(path string) ([]SignatureResult, error) { - data, err := ReadFile(path) - if err != nil { - return nil, fmt.Errorf("binsnoop: read file: %w", err) - } - return s.Scan(data), nil -} - -// AnalyzeBytes scans and optionally extracts from raw bytes. -func (s *Scanner) AnalyzeBytes(data []byte, filePath string, extract bool) *AnalysisResults { - results := &AnalysisResults{ - FilePath: filePath, - Extractions: make(map[string]*ExtractionResult), - } - - results.FileMap = s.Scan(data) - - if extract && len(results.FileMap) > 0 { - results.Extractions = s.Extract(data, filePath, results.FileMap) - } - - return results -} - -// AnalyzeFile reads a file from disk, scans, and optionally extracts. -func (s *Scanner) AnalyzeFile(path string, extract bool) (*AnalysisResults, error) { - data, err := ReadFile(path) - if err != nil { - return nil, fmt.Errorf("binsnoop: read file: %w", err) - } - return s.AnalyzeBytes(data, path, extract), nil -} - -// CarveResult extracts raw bytes for a specific signature result to a file. -func CarveResult(data []byte, result SignatureResult, outputDir string) error { - if result.Offset < 0 || result.Offset+result.Size > len(data) { - return fmt.Errorf("binsnoop: result offset/size exceeds data bounds") - } - if err := os.MkdirAll(outputDir, 0700); err != nil { - return fmt.Errorf("binsnoop: mkdir: %w", err) - } - outPath := filepath.Join(outputDir, fmt.Sprintf("%x_%s.bin", result.Offset, result.Name)) - return os.WriteFile(outPath, data[result.Offset:result.Offset+result.Size], 0600) -} - -// SignatureCount returns the number of loaded signatures. -func (s *Scanner) SignatureCount() int { return s.signatureCount } - -// PatternCount returns the total number of magic byte patterns. -func (s *Scanner) PatternCount() int { return s.patternCount } - -// ListSignatures returns the names of all available signatures. -func ListSignatures() []string { - sigs := AllSignatures() - names := make([]string, len(sigs)) - for i, s := range sigs { - names[i] = s.Name - } - return names -} - -func includeSig(sig *Signature, include, exclude []string) bool { - if len(include) > 0 { - for _, inc := range include { - if strings.EqualFold(sig.Name, inc) { - return true - } - } - return false - } - if len(exclude) > 0 { - for _, exc := range exclude { - if strings.EqualFold(sig.Name, exc) { - return false - } - } - } - return true -} diff --git a/internal/leviathan/binsnoop/common.go b/internal/leviathan/binsnoop/common.go deleted file mode 100644 index 8e33b12..0000000 --- a/internal/leviathan/binsnoop/common.go +++ /dev/null @@ -1,127 +0,0 @@ -package binsnoop - -import ( - "encoding/binary" - "fmt" - "hash/crc32" - "os" - "time" -) - -const MaxFileReadBytes int64 = 64 << 20 - -// CRC32 calculates the CRC32 checksum using the IEEE polynomial. -func CRC32(data []byte) uint32 { - return crc32.ChecksumIEEE(data) -} - -// EpochToString converts a Unix timestamp to a formatted time string. -func EpochToString(epoch uint32) string { - t := time.Unix(int64(epoch), 0).UTC() - return t.Format("2006-01-02 15:04:05") -} - -// GetCString extracts a NULL-terminated C string from raw bytes. -func GetCString(data []byte) string { - for i, b := range data { - if b == 0 { - s := string(data[:i]) - // Validate UTF-8 printability - for _, c := range s { - if c == 0 { - return "" - } - } - return s - } - } - return string(data) -} - -// IsASCIINumber returns true if the byte is an ASCII digit (0-9). -func IsASCIINumber(b byte) bool { - return b >= '0' && b <= '9' -} - -// IsPrintableASCII returns true if the byte is a printable ASCII character. -func IsPrintableASCII(b byte) bool { - return b >= 0x0A && b <= 0x7E -} - -// IsOffsetSafe validates that the next offset is within bounds and progressing forward. -func IsOffsetSafe(availableData, nextOffset int, lastOffset *int) bool { - if lastOffset != nil { - if *lastOffset >= nextOffset { - return false - } - } - return nextOffset < availableData -} - -// ReadFile reads a bounded file into memory for scanner APIs that require a -// byte slice. Larger callers should use a streaming document path. -func ReadFile(path string) ([]byte, error) { - return ReadFileLimited(path, MaxFileReadBytes) -} - -func ReadFileLimited(path string, maxBytes int64) ([]byte, error) { - info, err := os.Stat(path) - if err != nil { - return nil, err - } - if info.IsDir() { - return nil, fmt.Errorf("binsnoop: path is a directory") - } - if maxBytes > 0 && info.Size() > maxBytes { - return nil, fmt.Errorf("binsnoop: file is too large: %d bytes exceeds %d bytes", info.Size(), maxBytes) - } - return os.ReadFile(path) -} - -// LE16 reads a little-endian uint16 from data at the given offset. -func LE16(data []byte, offset int) uint16 { - if offset+2 > len(data) { - return 0 - } - return binary.LittleEndian.Uint16(data[offset:]) -} - -// BE16 reads a big-endian uint16 from data at the given offset. -func BE16(data []byte, offset int) uint16 { - if offset+2 > len(data) { - return 0 - } - return binary.BigEndian.Uint16(data[offset:]) -} - -// LE32 reads a little-endian uint32 from data at the given offset. -func LE32(data []byte, offset int) uint32 { - if offset+4 > len(data) { - return 0 - } - return binary.LittleEndian.Uint32(data[offset:]) -} - -// BE32 reads a big-endian uint32 from data at the given offset. -func BE32(data []byte, offset int) uint32 { - if offset+4 > len(data) { - return 0 - } - return binary.BigEndian.Uint32(data[offset:]) -} - -// LE64 reads a little-endian uint64 from data at the given offset. -func LE64(data []byte, offset int) uint64 { - if offset+8 > len(data) { - return 0 - } - return binary.LittleEndian.Uint64(data[offset:]) -} - -// BE64 reads a big-endian uint64 from data at the given offset. -func BE64(data []byte, offset int) uint64 { - if offset+8 > len(data) { - return 0 - } - return binary.BigEndian.Uint64(data[offset:]) -} diff --git a/internal/leviathan/binsnoop/common_test.go b/internal/leviathan/binsnoop/common_test.go deleted file mode 100644 index d686b0c..0000000 --- a/internal/leviathan/binsnoop/common_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package binsnoop - -import ( - "os" - "path/filepath" - "runtime" - "strings" - "testing" -) - -func TestReadFileLimitedRejectsOversizedFile(t *testing.T) { - path := filepath.Join(t.TempDir(), "firmware.bin") - if err := os.WriteFile(path, []byte("abcd"), 0600); err != nil { - t.Fatalf("WriteFile() failed: %v", err) - } - _, err := ReadFileLimited(path, 3) - if err == nil { - t.Fatal("expected oversized file to be rejected") - } - if !strings.Contains(err.Error(), "file is too large") { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestCarveUsesPrivatePermissions(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("POSIX permission bits are not enforced on Windows") - } - path := filepath.Join(t.TempDir(), "private", "carved.bin") - if err := Carve(path, []byte("secret"), 0, 6); err != nil { - t.Fatal(err) - } - info, err := os.Stat(path) - if err != nil { - t.Fatal(err) - } - if got := info.Mode().Perm(); got != 0600 { - t.Fatalf("carved file permissions = %04o, want 0600", got) - } - dirInfo, err := os.Stat(filepath.Dir(path)) - if err != nil { - t.Fatal(err) - } - if got := dirInfo.Mode().Perm(); got != 0700 { - t.Fatalf("carve directory permissions = %04o, want 0700", got) - } -} diff --git a/internal/leviathan/binsnoop/extract.go b/internal/leviathan/binsnoop/extract.go deleted file mode 100644 index 4e0318a..0000000 --- a/internal/leviathan/binsnoop/extract.go +++ /dev/null @@ -1,124 +0,0 @@ -package binsnoop - -import ( - "fmt" - "os" - "os/exec" - "path/filepath" - "strings" -) - -// Carve writes a slice of data to a file on disk. -func Carve(outputPath string, data []byte, offset, size int) error { - if offset < 0 || offset+size > len(data) { - return fmt.Errorf("carve: offset %d + size %d exceeds data length %d", offset, size, len(data)) - } - dir := filepath.Dir(outputPath) - if err := os.MkdirAll(dir, 0700); err != nil { - return fmt.Errorf("carve: mkdir: %w", err) - } - return os.WriteFile(outputPath, data[offset:offset+size], 0600) -} - -// executeExternal runs an external extraction tool. -func executeExternal(ext *Extractor, carvedFilePath, outputDir string) *ExtractionResult { - result := &ExtractionResult{ - ExtractorName: ext.Command, - OutputDirectory: outputDir, - } - - if err := os.MkdirAll(outputDir, 0700); err != nil { - return result - } - - // Build arguments, replacing the source file placeholder - args := make([]string, len(ext.Arguments)) - for i, arg := range ext.Arguments { - args[i] = strings.ReplaceAll(arg, SourceFilePlaceholder, carvedFilePath) - } - - cmd := exec.Command(ext.Command, args...) - cmd.Dir = outputDir - - output, err := cmd.CombinedOutput() - _ = output - - exitCode := 0 - if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { - exitCode = exitErr.ExitCode() - } else { - return result - } - } - - // Check if exit code is in the accepted list - for _, code := range ext.ExitCodes { - if exitCode == code { - result.Success = true - break - } - } - - result.DoNotRecurse = ext.DoNotRecurse - return result -} - -// executeInternal runs an internal extraction function. -func executeInternal(ext *Extractor, data []byte, offset int, outputDir string) *ExtractionResult { - if ext.Function == nil { - return &ExtractionResult{} - } - result := ext.Function(data, offset, outputDir) - if result == nil { - result = &ExtractionResult{} - } - result.DoNotRecurse = ext.DoNotRecurse - return result -} - -// ExecuteExtraction performs extraction for a signature result. -func ExecuteExtraction(data []byte, filePath string, sig *SignatureResult, ext *Extractor) *ExtractionResult { - if ext == nil { - return &ExtractionResult{} - } - - // Use preferred extractor if specified - actualExt := ext - if sig.PreferredExtractor != nil { - actualExt = sig.PreferredExtractor - } - - // Build output directory path - baseName := filepath.Base(filePath) - outputDir := filepath.Join(filepath.Dir(filePath), baseName+".extracted", - fmt.Sprintf("%x", sig.Offset)) - - switch actualExt.Type { - case ExtractorTypeInternal: - return executeInternal(actualExt, data, sig.Offset, outputDir) - - case ExtractorTypeExternal: - // Carve data to a temp file for external tools - if err := os.MkdirAll(outputDir, 0700); err != nil { - return &ExtractionResult{OutputDirectory: outputDir} - } - - carvedFile := filepath.Join(outputDir, fmt.Sprintf("carved.%s", actualExt.Extension)) - endOffset := sig.Offset + sig.Size - if endOffset > len(data) { - endOffset = len(data) - } - if err := os.WriteFile(carvedFile, data[sig.Offset:endOffset], 0600); err != nil { - return &ExtractionResult{OutputDirectory: outputDir} - } - - result := executeExternal(actualExt, carvedFile, outputDir) - result.OutputDirectory = outputDir - result.ExtractorName = actualExt.Command - return result - - default: - return &ExtractionResult{} - } -} diff --git a/internal/leviathan/binsnoop/magic.go b/internal/leviathan/binsnoop/magic.go deleted file mode 100644 index 50153d9..0000000 --- a/internal/leviathan/binsnoop/magic.go +++ /dev/null @@ -1,268 +0,0 @@ -package binsnoop - -// AllSignatures returns every supported signature definition. -// This is the equivalent of magic::patterns() in the Rust binwalk codebase. -func AllSignatures() []Signature { - return []Signature{ - // ===================== Archives ===================== - {Name: "gzip", Magic: gzipMagic(), Parser: gzipParser, Description: gzipDescription, Extractor: extGzip()}, - {Name: "deb", Magic: debMagic(), Parser: debParser, Description: debDescription}, - {Name: "7zip", Magic: sevenzipMagic(), Parser: sevenzipParser, Description: sevenzipDescription, Extractor: extSevenzip()}, - {Name: "xz", Magic: XZMagic(), Parser: XZParser, Description: XZDescription, Extractor: extLzma()}, - {Name: "tarball", Magic: tarballMagic(), Parser: tarballParser, Description: tarballDescription, Extractor: extTar()}, - {Name: "zip", Magic: zipMagic(), Parser: zipParser, Description: zipDescription, Extractor: extSevenzip()}, - {Name: "cpio", Magic: cpioMagic(), Parser: cpioParser, Description: cpioDescription, Extractor: extSevenzip()}, - {Name: "iso9660", Magic: iso9660Magic(), Parser: iso9660Parser, Description: iso9660Description, Extractor: extSevenzip()}, - {Name: "rar", Magic: rarMagic(), Parser: rarParser, Description: rarDescription, Extractor: extRar()}, - {Name: "cab", Magic: cabMagic(), Parser: cabParser, Description: cabDescription, Extractor: extCab()}, - {Name: "arj", Magic: arjMagic(), Parser: arjParser, Description: arjDescription, Extractor: extSevenzip()}, - {Name: "dahua_zip", Magic: dahuaZipMagic(), Parser: dahuaZipParser, Description: dahuaZipDescription, Extractor: extSevenzip()}, - - // ===================== Compression ===================== - {Name: "lzma", Magic: LZMAMagic(), Parser: LZMAParser, Description: LZMADescription, Extractor: extLzma()}, - {Name: "bzip2", Magic: Bzip2Magic(), Parser: Bzip2Parser, Description: Bzip2Description, Extractor: extBzip2()}, - {Name: "zstd", Magic: ZSTDMagic(), Parser: ZSTDParser, Description: ZSTDDescription, Extractor: extZstd()}, - {Name: "lz4", Magic: LZ4Magic(), Parser: LZ4Parser, Description: LZ4Description, Extractor: extLz4()}, - {Name: "lzop", Magic: LZOPMagic(), Parser: LZOPParser, Description: LZOPDescription, Extractor: extLzop()}, - {Name: "lzfse", Magic: LZFSEMagic(), Parser: LZFSEParser, Description: LZFSEDescription, Extractor: extLzfse()}, - {Name: "zlib", Short: true, Magic: ZlibMagic(), Parser: ZlibParser, Description: ZlibDescription, Extractor: extZlib()}, - {Name: "compressd", Short: true, Magic: CompressdMagic(), Parser: CompressdParser, Description: CompressdDescription, Extractor: extSevenzip()}, - - // ===================== Filesystems ===================== - {Name: "squashfs", Magic: squashfs_magic(), Parser: squashfs_parser, Description: SquashFSDescription, Extractor: extSquashfs()}, - {Name: "cramfs", Magic: cramfs_magic(), Parser: cramfs_parser, Description: CramFSDescription, Extractor: extSevenzip()}, - {Name: "jffs2", Magic: jffs2_magic(), Parser: jffs2_parser, Description: JFFS2Description, Extractor: extJffs2()}, - {Name: "yaffs", Magic: yaffs_magic(), Parser: yaffs_parser, Description: YAFFSDescription, Extractor: extYaffs2()}, - {Name: "romfs", Magic: romfs_magic(), Parser: romfs_parser, Description: RomFSDescription, Extractor: extRomfs()}, - {Name: "ext", Magic: ext_magic(), Parser: ext_parser, Description: EXTDescription, Extractor: extTsk()}, - {Name: "fat", Short: true, MagicOffset: FATMagicOffset, Magic: fat_magic(), Parser: fat_parser, Description: FATDescription, Extractor: extTsk()}, - {Name: "ntfs", Magic: ntfs_magic(), Parser: ntfs_parser, Description: NTFSDescription, Extractor: extTsk()}, - {Name: "apfs", Magic: apfs_magic(), Parser: apfs_parser, Description: APFSDescription, Extractor: extSevenzip()}, - {Name: "btrfs", AlwaysDisplay: true, Magic: btrfs_magic(), Parser: btrfs_parser, Description: BTRFSDescription}, - {Name: "logfs", Magic: logfs_magic(), Parser: logfs_parser, Description: LogFSDescription}, - {Name: "ubi", Magic: ubi_magic(), Parser: ubi_parser, Description: UBIImageDescription, Extractor: extUbi()}, - {Name: "ubifs", Magic: ubifs_magic(), Parser: ubifs_parser, Description: UBIFSDescription, Extractor: extUbifs()}, - - // ===================== Images & Media ===================== - {Name: "png", Magic: PNGMagic(), Parser: PNGParser, Description: PNGDescription, Extractor: extPng()}, - {Name: "jpeg", Magic: JPEGMagic(), Parser: JPEGParser, Description: JPEGDescription, Extractor: extJpeg()}, - {Name: "bmp", Magic: BMPMagic(), Parser: BMPParser, Description: BMPDescription, Extractor: extBmp()}, - {Name: "gif", Magic: GIFMagic(), Parser: GIFParser, Description: GIFDescription, Extractor: extGif()}, - {Name: "svg", Magic: SVGMagic(), Parser: SVGParser, Description: SVGDescription, Extractor: extSvg()}, - {Name: "riff", Magic: RIFFMagic(), Parser: RIFFParser, Description: RIFFDescription, Extractor: extRiff()}, - {Name: "pdf", Magic: PDFMagic(), Parser: PDFParser, Description: PDFDescription}, - - // ===================== Executables ===================== - {Name: "elf", Magic: ELFMagic(), Parser: ELFParser, Description: ELFDescription}, - {Name: "pe", Magic: PEMagic(), Parser: PEParser, Description: PEDescription}, - - // ===================== Firmware Headers ===================== - {Name: "uimage", Magic: uimageMagic(), Parser: uimageParser, Description: uimageDescription, Extractor: extUimage()}, - {Name: "packimg", Magic: packimgMagic(), Parser: packimgParser, Description: packimgDescription}, - {Name: "uboot", AlwaysDisplay: true, Magic: ubootMagic(), Parser: ubootParser, Description: ubootDescription}, - {Name: "trx", Magic: trxMagic(), Parser: trxParser, Description: trxDescription, Extractor: extTrx()}, - {Name: "tplink", Magic: tplinkMagic(), Parser: tplinkParser, Description: tplinkDescription}, - {Name: "tplink_rtos", Magic: tplinkRTOSMagic(), Parser: tplinkRTOSParser, Description: tplinkRTOSDescription}, - {Name: "dlob", Magic: dlobMagic(), Parser: dlobParser, Description: dlobDescription}, - {Name: "arcadyan", Magic: arcadyanMagic(), Parser: arcadyanParser, Description: arcadyanDescription}, - {Name: "chk", Magic: chkMagic(), Parser: chkParser, Description: chkDescription}, - {Name: "seama", AlwaysDisplay: true, Magic: seamaMagic(), Parser: seamaParser, Description: seamaDescription}, - {Name: "cfe", AlwaysDisplay: true, Magic: cfeMagic(), Parser: cfeParser, Description: cfeDescription}, - {Name: "jboot_arm", Magic: jbootARMMagic(), Parser: jbootARMParser, Description: jbootARMDescription}, - {Name: "jboot_stag", Magic: jbootSTAGMagic(), Parser: jbootSTAGParser, Description: jbootSTAGDescription}, - {Name: "jboot_sch2", Magic: jbootSCH2Magic(), Parser: jbootSCH2Parser, Description: jbootSCH2Description, Extractor: extJboot()}, - {Name: "binhdr", Magic: binhdrMagic(), Parser: binhdrParser, Description: binhdrDescription}, - {Name: "autel", Magic: autelMagic(), Parser: autelParser, Description: autelDescription, Extractor: extAutel()}, - {Name: "android_sparse", Magic: androidSparseMagic(), Parser: androidSparseParser, Description: androidSparseDescription, Extractor: extAndroidSparse()}, - {Name: "dtb", Magic: dtbMagic(), Parser: dtbParser, Description: dtbDescription, Extractor: extDtb()}, - {Name: "android_bootimg", Magic: androidBootimgMagic(), Parser: androidBootimgParser, Description: androidBootimgDescription}, - {Name: "dlink_tlv", Magic: dlinkTLVMagic(), Parser: dlinkTLVParser, Description: dlinkTLVDescription, Extractor: extEncfw()}, - {Name: "dlke", Magic: dlkeMagic(), Parser: dlkeParser, Description: dlkeDescription, Extractor: extEncfw()}, - {Name: "shrs", Magic: shrsMagic(), Parser: shrsParser, Description: shrsDescription, Extractor: extEncfw()}, - {Name: "dkbs", Magic: dkbsMagic(), Parser: dkbsParser, Description: dkbsDescription, Extractor: extEncfw()}, - {Name: "dms", Magic: dmsMagic(), Parser: dmsParser, Description: dmsDescription}, - {Name: "encfw", Short: true, AlwaysDisplay: true, Magic: encfwMagic(), Parser: encfwParser, Description: encfwDescription, Extractor: extEncfw()}, - {Name: "encrpted_img", Short: true, Magic: encrptedImgMagic(), Parser: encrptedImgParser, Description: encrptedImgDescription, Extractor: extEncfw()}, - {Name: "wince", Magic: winceMagic(), Parser: winceParser, Description: winceDescription}, - {Name: "mh01", Magic: mh01Magic(), Parser: mh01Parser, Description: mh01Description}, - {Name: "csman", Short: true, Magic: csmanMagic(), Parser: csmanParser, Description: csmanDescription}, - {Name: "dxbc", Magic: dxbcMagic(), Parser: dxbcParser, Description: dxbcDescription}, - {Name: "matter_ota", Short: true, Magic: matterOTAMagic(), Parser: matterOTAParser, Description: matterOTADescription}, - {Name: "rtk", Short: true, Magic: rtkMagic(), Parser: rtkParser, Description: rtkDescription}, - {Name: "dpapi", Short: true, AlwaysDisplay: true, Magic: dpapiMagic(), Parser: dpapiParser, Description: dpapiDescription}, - {Name: "qcow", Short: true, AlwaysDisplay: true, Magic: qcowMagic(), Parser: qcowParser, Description: qcowDescription}, - - // ===================== Crypto & Hashes ===================== - {Name: "aes_sbox", Magic: aesSBoxMagic(), Parser: aesSBoxParser, Description: DescriptionAESSBox}, - {Name: "aes_forward_table", Magic: aesForwardTableMagic(), Parser: aesForwardTableParser, Description: DescriptionAESFT}, - {Name: "aes_reverse_table", Magic: aesReverseTableMagic(), Parser: aesReverseTableParser, Description: DescriptionAESRT}, - {Name: "aes_rcon", Magic: aesRconMagic(), Parser: aesRconParser, Description: DescriptionAESRCON}, - {Name: "aes_acceleration_table", Magic: aesAccelerationTableMagic(), Parser: aesAccelerationTableParser, Description: DescriptionAESAcc}, - {Name: "rsa", AlwaysDisplay: true, Magic: rsaMagic(), Parser: rsaParser, Description: DescriptionRSA}, - {Name: "openssl", AlwaysDisplay: true, Magic: opensslMagic(), Parser: opensslParser, Description: DescriptionOpenSSL, Extractor: extEncfw()}, - {Name: "gpg_signed", Short: true, Magic: gpgSignedMagic(), Parser: gpgSignedParser, Description: DescriptionGPGSigned, Extractor: extGpg()}, - {Name: "pem_certificate", Magic: pemCertificateMagic(), Parser: pemParser, Description: DescriptionPEMCertificate, Extractor: extPemCert()}, - {Name: "pem_public_key", AlwaysDisplay: true, Magic: pemPublicKeyMagic(), Parser: pemParser, Description: DescriptionPEMPublicKey, Extractor: extPemKey()}, - {Name: "pem_private_key", AlwaysDisplay: true, Magic: pemPrivateKeyMagic(), Parser: pemParser, Description: DescriptionPEMPrivateKey, Extractor: extPemKey()}, - {Name: "pkcs_der_hash", Magic: derHashMagic(), Parser: derHashParser, Description: DescriptionDERHash}, - {Name: "luks", Magic: luksMagic(), Parser: luksParser, Description: DescriptionLUKS}, - {Name: "crc32", Magic: crc32ConstMagic(), Parser: crc32Parser, Description: DescriptionCRC32}, - {Name: "sha256", Magic: sha256ConstMagic(), Parser: sha256Parser, Description: DescriptionSHA256}, - {Name: "md5", Magic: md5ConstMagic(), Parser: md5Parser, Description: DescriptionMD5}, - - // ===================== Misc ===================== - {Name: "copyright", Magic: copyrightMagic(), Parser: copyrightParser, Description: DescriptionCopyright}, - {Name: "wind_kernel", AlwaysDisplay: true, Magic: windKernelMagic(), Parser: windKernelParser, Description: DescriptionWINDKernelVersion}, - {Name: "vxworks_symtab", AlwaysDisplay: true, Magic: vxworksSymtabMagic(), Parser: vxworksSymtabParser, Description: DescriptionVxWorksSymTab}, - {Name: "ecos", AlwaysDisplay: true, Magic: ecosExceptionHandlerMagic(), Parser: ecosExceptionHandlerParser, Description: DescriptionECosExceptionHandler}, - {Name: "linux_kernel", AlwaysDisplay: true, Magic: linuxKernelVersionMagic(), Parser: linuxKernelVersionParser, Description: DescriptionLinuxKernelVersion, Extractor: extLinuxKernel()}, - {Name: "linux_boot_image", Magic: linuxBootImageMagic(), Parser: linuxBootImageParser, Description: DescriptionLinuxBootImage}, - {Name: "linux_arm_zimage", Magic: linuxARMzImageMagic(), Parser: linuxARMzImageParser, Description: DescriptionLinuxARMzImage}, - {Name: "linux_arm64_boot_image", Magic: linuxARM64BootImageMagic(), Parser: linuxARM64BootImageParser, Description: DescriptionLinuxARM64BootImage}, - {Name: "efigpt", Magic: efigptMagic(), Parser: efigptParser, Description: DescriptionEFIGPT, Extractor: extSevenzip()}, - {Name: "mbr", Short: true, MagicOffset: MBRMagicOffset, AlwaysDisplay: true, Magic: mbrMagic(), Parser: mbrParser, Description: DescriptionMBR, Extractor: extMbr()}, - {Name: "uefi_pi_volume", Magic: uefiVolumeMagic(), Parser: uefiVolumeParser, Description: DescriptionUEFIVolume, Extractor: extUefi()}, - {Name: "uefi_capsule", Magic: uefiCapsuleMagic(), Parser: uefiCapsuleParser, Description: DescriptionUEFICapsule, Extractor: extUefi()}, - {Name: "pchrom", Magic: pchRomMagic(), Parser: pchRomParser, Description: DescriptionPCHRom, Extractor: extUefi()}, - {Name: "pcapng", Magic: pcapngMagic(), Parser: pcapngParser, Description: DescriptionPcapNG, Extractor: extPcap()}, - {Name: "pjl", Magic: pjlMagic(), Parser: pjlParser, Description: DescriptionPJL}, - {Name: "srecord", Magic: srecordMagic(), Parser: srecordParser, Description: DescriptionSRecord, Extractor: extSrec()}, - {Name: "srecord_generic", Short: true, Magic: srecordGenericMagic(), Parser: srecordParser, Description: DescriptionSRecordGeneric, Extractor: extSrec()}, - {Name: "dmg", Magic: dmgMagic(), Parser: dmgParser, Description: DescriptionDMG, Extractor: extDmg()}, - {Name: "qnx_ifs", Magic: qnxIFSMagic(), Parser: qnxIFSParser, Description: DescriptionQNXIFS, Extractor: extDumpifs()}, - } -} - -// ============================================================================ -// External extractor definitions (command-line tools) -// ============================================================================ - -func extSevenzip() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "bin", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extGzip() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "gzip", Extension: "gz", Arguments: []string{"-d", "-k", "-f", "%e"}, ExitCodes: []int{0}} -} -func extLzma() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "xz", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extTar() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "tar", Extension: "tar", Arguments: []string{"xf", "%e"}, ExitCodes: []int{0}} -} -func extBzip2() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "bzip2", Extension: "bz2", Arguments: []string{"-d", "-k", "-f", "%e"}, ExitCodes: []int{0}} -} -func extZstd() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "zstd", Extension: "zst", Arguments: []string{"-d", "-f", "%e"}, ExitCodes: []int{0}} -} -func extLz4() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "lz4", Extension: "lz4", Arguments: []string{"-d", "-f", "%e", "decompressed.bin"}, ExitCodes: []int{0}} -} -func extLzop() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "lzop", Extension: "lzo", Arguments: []string{"-d", "-f", "%e"}, ExitCodes: []int{0}} -} -func extLzfse() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "lzfse", Extension: "lzfse", Arguments: []string{"-decode", "-i", "%e", "-o", "decompressed.bin"}, ExitCodes: []int{0}} -} -func extZlib() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "bin", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extSquashfs() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "unsquashfs", Extension: "sqsh", Arguments: []string{"-f", "-d", "squashfs-root", "%e"}, ExitCodes: []int{0}} -} -func extJffs2() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "jefferson", Extension: "jffs2", Arguments: []string{"-d", "jffs2-root", "%e"}, ExitCodes: []int{0}} -} -func extYaffs2() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "unyaffs", Extension: "yaffs", Arguments: []string{"%e", "yaffs-root"}, ExitCodes: []int{0}} -} -func extRomfs() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "romfs", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extTsk() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "tsk_recover", Extension: "img", Arguments: []string{"-a", "-e", "%e", "tsk-root"}, ExitCodes: []int{0}} -} -func extCab() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cabextract", Extension: "cab", Arguments: []string{"%e"}, ExitCodes: []int{0}} -} -func extRar() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "unrar", Extension: "rar", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0}} -} -func extUbi() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "ubireader_extract_images", Extension: "ubi", Arguments: []string{"%e"}, ExitCodes: []int{0}} -} -func extUbifs() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "ubireader_extract_files", Extension: "ubifs", Arguments: []string{"%e"}, ExitCodes: []int{0}} -} -func extPng() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "png", Arguments: []string{"%e", "image.png"}, ExitCodes: []int{0}} -} -func extJpeg() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "jpg", Arguments: []string{"%e", "image.jpg"}, ExitCodes: []int{0}} -} -func extBmp() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "bmp", Arguments: []string{"%e", "image.bmp"}, ExitCodes: []int{0}} -} -func extGif() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "gif", Arguments: []string{"%e", "image.gif"}, ExitCodes: []int{0}} -} -func extSvg() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "svg", Arguments: []string{"%e", "image.svg"}, ExitCodes: []int{0}} -} -func extRiff() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "riff", Arguments: []string{"%e", "media.riff"}, ExitCodes: []int{0}} -} -func extUimage() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "bin", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extTrx() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "bin", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extJboot() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "bin", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extAutel() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "bin", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extAndroidSparse() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "simg2img", Extension: "simg", Arguments: []string{"%e", "output.img"}, ExitCodes: []int{0}} -} -func extDtb() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "dtc", Extension: "dtb", Arguments: []string{"-I", "dtb", "-O", "dts", "-o", "device_tree.dts", "%e"}, ExitCodes: []int{0}} -} -func extEncfw() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "bin", Arguments: []string{"%e", "encrypted_firmware.bin"}, ExitCodes: []int{0}} -} -func extGpg() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "gpg", Extension: "gpg", Arguments: []string{"--decrypt", "-o", "decrypted.bin", "%e"}, ExitCodes: []int{0}} -} -func extPemCert() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "pem", Arguments: []string{"%e", "certificate.pem"}, ExitCodes: []int{0}} -} -func extPemKey() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "pem", Arguments: []string{"%e", "key.pem"}, ExitCodes: []int{0}} -} -func extLinuxKernel() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "bin", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extMbr() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "bin", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extUefi() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "uefi-firmware-parser", Extension: "bin", Arguments: []string{"-e", "-O", "%e"}, ExitCodes: []int{0}} -} -func extPcap() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "cp", Extension: "pcapng", Arguments: []string{"%e", "capture.pcapng"}, ExitCodes: []int{0}} -} -func extSrec() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "srec_cat", Extension: "srec", Arguments: []string{"%e", "-o", "output.bin", "-binary"}, ExitCodes: []int{0}} -} -func extDmg() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "dmg", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} -func extDumpifs() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "dumpifs", Extension: "ifs", Arguments: []string{"-x", "%e"}, ExitCodes: []int{0}} -} -func extIso9660() *Extractor { - return &Extractor{Type: ExtractorTypeExternal, Command: "7z", Extension: "iso", Arguments: []string{"x", "-y", "%e"}, ExitCodes: []int{0, 2}} -} diff --git a/internal/leviathan/binsnoop/match.go b/internal/leviathan/binsnoop/match.go deleted file mode 100644 index 264820a..0000000 --- a/internal/leviathan/binsnoop/match.go +++ /dev/null @@ -1,134 +0,0 @@ -package binsnoop - -// ahoCorasick implements the Aho-Corasick multi-pattern byte matching algorithm. -type ahoCorasick struct { - gotoFn []map[byte]int - failFn []int - outputFn [][]int - patterns [][]byte -} - -// ahoMatch represents a single pattern match. -type ahoMatch struct { - PatternIndex int - Start int -} - -// newAhoCorasick builds an Aho-Corasick automaton from the given byte patterns. -func newAhoCorasick(patterns [][]byte) *ahoCorasick { - ac := &ahoCorasick{ - gotoFn: []map[byte]int{make(map[byte]int)}, - failFn: []int{0}, - outputFn: [][]int{nil}, - patterns: patterns, - } - - // Build the trie (goto function) - for i, pattern := range patterns { - state := 0 - for _, b := range pattern { - if next, ok := ac.gotoFn[state][b]; ok { - state = next - } else { - newState := len(ac.gotoFn) - ac.gotoFn[state][b] = newState - ac.gotoFn = append(ac.gotoFn, make(map[byte]int)) - ac.failFn = append(ac.failFn, 0) - ac.outputFn = append(ac.outputFn, nil) - state = newState - } - } - ac.outputFn[state] = append(ac.outputFn[state], i) - } - - // Build failure links (BFS) - queue := make([]int, 0) - for _, next := range ac.gotoFn[0] { - queue = append(queue, next) - } - - for len(queue) > 0 { - r := queue[0] - queue = queue[1:] - - for b, s := range ac.gotoFn[r] { - queue = append(queue, s) - state := ac.failFn[r] - for { - if _, ok := ac.gotoFn[state][b]; ok || state == 0 { - break - } - state = ac.failFn[state] - } - if next, ok := ac.gotoFn[state][b]; ok && next != s { - ac.failFn[s] = next - } else { - ac.failFn[s] = 0 - } - // Merge output from failure state - if ac.outputFn[ac.failFn[s]] != nil { - ac.outputFn[s] = append(ac.outputFn[s], ac.outputFn[ac.failFn[s]]...) - } - } - } - - return ac -} - -// findOverlapping searches data starting at startOffset and returns all overlapping matches. -func (ac *ahoCorasick) findOverlapping(data []byte, startOffset int) []ahoMatch { - var matches []ahoMatch - state := 0 - - for i := startOffset; i < len(data); i++ { - b := data[i] - for { - if _, ok := ac.gotoFn[state][b]; ok || state == 0 { - break - } - state = ac.failFn[state] - } - if next, ok := ac.gotoFn[state][b]; ok { - state = next - } - for _, patIdx := range ac.outputFn[state] { - matchStart := i - len(ac.patterns[patIdx]) + 1 - matches = append(matches, ahoMatch{ - PatternIndex: patIdx, - Start: matchStart, - }) - } - } - - return matches -} - -// findOverlappingLimit is the bounded variant used by deep scans. It stops -// collecting once limit matches have been found so adversarial input cannot -// force an unbounded match slice. -func (ac *ahoCorasick) findOverlappingLimit(data []byte, startOffset, limit int) ([]ahoMatch, bool) { - if limit <= 0 { - return nil, false - } - matches := make([]ahoMatch, 0, min(limit, 256)) - state := 0 - for i := startOffset; i < len(data); i++ { - b := data[i] - for { - if _, ok := ac.gotoFn[state][b]; ok || state == 0 { - break - } - state = ac.failFn[state] - } - if next, ok := ac.gotoFn[state][b]; ok { - state = next - } - for _, patIdx := range ac.outputFn[state] { - matches = append(matches, ahoMatch{PatternIndex: patIdx, Start: i - len(ac.patterns[patIdx]) + 1}) - if len(matches) >= limit { - return matches, true - } - } - } - return matches, false -} diff --git a/internal/leviathan/binsnoop/parse.go b/internal/leviathan/binsnoop/parse.go deleted file mode 100644 index 4057cd4..0000000 --- a/internal/leviathan/binsnoop/parse.go +++ /dev/null @@ -1,107 +0,0 @@ -package binsnoop - -import ( - "errors" - "fmt" -) - -// ErrParse is returned when structure parsing fails. -var ErrParse = errors.New("structure parse error") - -// StructField defines a single field in a binary structure. -type StructField struct { - Name string - Type string // "u8", "u16", "u24", "u32", "u64" -} - -// typeSize returns the byte size for a type string. -func typeSize(ctype string) (int, error) { - switch ctype { - case "u8": - return 1, nil - case "u16": - return 2, nil - case "u24": - return 3, nil - case "u32": - return 4, nil - case "u64": - return 8, nil - default: - return 0, fmt.Errorf("unknown type: %s", ctype) - } -} - -// StructSize returns the total byte size of a structure definition. -func StructSize(fields []StructField) int { - total := 0 - for _, f := range fields { - sz, err := typeSize(f.Type) - if err != nil { - continue - } - total += sz - } - return total -} - -// Parse parses a binary structure from data according to the field definitions. -// Endianness must be "big" or "little". All values are returned as int. -func Parse(data []byte, fields []StructField, endianness string) (map[string]int, error) { - structSize := StructSize(fields) - if len(data) < structSize { - return nil, ErrParse - } - - result := make(map[string]int, len(fields)) - offset := 0 - - for _, f := range fields { - sz, err := typeSize(f.Type) - if err != nil { - return nil, ErrParse - } - - var value int - - switch sz { - case 1: - value = int(data[offset]) - case 2: - if endianness == "big" { - value = int(data[offset])<<8 | int(data[offset+1]) - } else { - value = int(data[offset+1])<<8 | int(data[offset]) - } - case 3: - if endianness == "big" { - value = int(data[offset])<<16 | int(data[offset+1])<<8 | int(data[offset+2]) - } else { - value = int(data[offset+2])<<16 | int(data[offset+1])<<8 | int(data[offset]) - } - case 4: - if endianness == "big" { - value = int(data[offset])<<24 | int(data[offset+1])<<16 | int(data[offset+2])<<8 | int(data[offset+3]) - } else { - value = int(data[offset+3])<<24 | int(data[offset+2])<<16 | int(data[offset+1])<<8 | int(data[offset]) - } - case 8: - if endianness == "big" { - value = int(uint64(data[offset])<<56 | uint64(data[offset+1])<<48 | - uint64(data[offset+2])<<40 | uint64(data[offset+3])<<32 | - uint64(data[offset+4])<<24 | uint64(data[offset+5])<<16 | - uint64(data[offset+6])<<8 | uint64(data[offset+7])) - } else { - value = int(uint64(data[offset+7])<<56 | uint64(data[offset+6])<<48 | - uint64(data[offset+5])<<40 | uint64(data[offset+4])<<32 | - uint64(data[offset+3])<<24 | uint64(data[offset+2])<<16 | - uint64(data[offset+1])<<8 | uint64(data[offset])) - } - } - - offset += sz - result[f.Name] = value - } - - return result, nil -} diff --git a/internal/leviathan/binsnoop/scanner.go b/internal/leviathan/binsnoop/scanner.go deleted file mode 100644 index 4afb117..0000000 --- a/internal/leviathan/binsnoop/scanner.go +++ /dev/null @@ -1,352 +0,0 @@ -package binsnoop - -import "sort" - -// Scanner is the core scanning engine. It holds compiled signature patterns -// and performs Aho-Corasick based multi-pattern matching on binary data. -type Scanner struct { - signatureCount int - patternCount int - shortSignatures []Signature - patterns [][]byte - patternSigTable map[int]Signature - extractorLookup map[string]*Extractor - ac *ahoCorasick -} - -// compile builds the Aho-Corasick automaton from loaded patterns. -func (s *Scanner) compile() { - if len(s.patterns) > 0 { - s.ac = newAhoCorasick(s.patterns) - } -} - -// Scan searches data for all known file signatures and returns validated results. -// This implements the same algorithm as the Rust binwalk scanner: -// 1. Check short signatures at file offset 0 -// 2. Run Aho-Corasick scan with restart on high-confidence sized matches -// 3. Sort and de-conflict overlapping results -// 4. Estimate sizes for signatures with unknown size -func (s *Scanner) Scan(data []byte) []SignatureResult { - var fileMap []SignatureResult - nextValidOffset := 0 - availableData := len(data) - - // Phase 1: Check short signatures at offset 0 - for i := range s.shortSignatures { - sig := &s.shortSignatures[i] - for _, magic := range sig.Magic { - magicStart := sig.MagicOffset - magicEnd := magicStart + len(magic) - - if len(data) >= magicEnd && bytesEqual(data[magicStart:magicEnd], magic) { - result, err := sig.Parser(data, magicStart) - if err != nil { - continue - } - autoPopulate(result, sig) - fileMap = append(fileMap, *result) - - if result.Confidence >= ConfidenceHigh { - nextValidOffset = result.Offset + result.Size - } - break - } - } - } - - // Phase 2: Aho-Corasick scan. - // - // The automaton is run exactly once over the whole buffer; we then walk - // the matches in position order. When a high/medium-confidence sized - // signature is accepted, nextValidOffset jumps past it and any later - // matches that start inside that consumed region are skipped. This is the - // same result the binwalk scanner produces, but without re-running the - // search from every new offset — a previous implementation rescanned the - // tail of the file on each accepted match, which is O(n^2) on inputs that - // contain many closely-spaced signatures (e.g. real firmware images) and - // could appear to hang. - if s.ac == nil { - return s.postProcess(data, fileMap) - } - - matches := s.ac.findOverlapping(data, nextValidOffset) - for _, m := range matches { - magicOffset := m.Start - if magicOffset < nextValidOffset { - continue - } - - sig, ok := s.patternSigTable[m.PatternIndex] - if !ok { - continue - } - - result, err := sig.Parser(data, magicOffset) - if err != nil { - continue - } - - sigEnd := result.Offset + result.Size - if sigEnd > availableData { - continue - } - - autoPopulate(result, &sig) - fileMap = append(fileMap, *result) - - if result.Confidence >= ConfidenceMedium && result.Size > 0 { - nextValidOffset = sigEnd - } - } - - return s.postProcess(data, fileMap) -} - -// postProcess sorts results, removes conflicts, and estimates unknown sizes. -func (s *Scanner) postProcess(data []byte, fileMap []SignatureResult) []SignatureResult { - if len(fileMap) == 0 { - return fileMap - } - - // Sort by offset - sort.Slice(fileMap, func(i, j int) bool { - return fileMap[i].Offset < fileMap[j].Offset - }) - - // De-conflict overlapping signatures in a single pass, filtering in place. - // (The previous implementation removed elements with - // append(fileMap[:i], fileMap[i+1:]...) inside the loop; each removal is - // O(n), so a file with many overlapping hits was O(n^2) and could appear - // to hang.) Removed entries are dropped; on a duplicate offset the - // higher-confidence signature wins, matching the original behaviour. - out := fileMap[:0] - nextValidOffset := 0 - for _, thisSig := range fileMap { - // Duplicate offset: keep only the highest-confidence signature. - if len(out) > 0 && thisSig.Offset == out[len(out)-1].Offset { - if thisSig.Confidence > out[len(out)-1].Confidence { - out[len(out)-1] = thisSig - if thisSig.Confidence >= ConfidenceMedium { - nextValidOffset = thisSig.Offset + thisSig.Size - } - } - continue - } - - // Drop signatures that fall inside a previously accepted sized region. - if thisSig.Offset < nextValidOffset { - continue - } - - // Drop signatures whose declared size runs past EOF or wraps. - remainingSize := len(data) - thisSig.Offset - if thisSig.Size > remainingSize || (thisSig.Offset+thisSig.Size) < 0 { - continue - } - - out = append(out, thisSig) - if thisSig.Confidence >= ConfidenceMedium { - nextValidOffset = thisSig.Offset + thisSig.Size - } - } - fileMap = out - - // Estimate sizes for signatures with unknown size (0): extend each to the - // offset of the next medium-or-higher confidence signature, or to EOF. - // Computed in a single right-to-left pass to stay O(n). - nextMediumOffset := len(data) - for i := len(fileMap) - 1; i >= 0; i-- { - if fileMap[i].Size == 0 { - fileMap[i].Size = nextMediumOffset - fileMap[i].Offset - } - if fileMap[i].Confidence >= ConfidenceMedium { - nextMediumOffset = fileMap[i].Offset - } - } - - return fileMap -} - -// ScanDeep validates signatures without suppressing hits inside an already -// identified container. Results are bounded and annotated with containment. -func (s *Scanner) ScanDeep(data []byte, maxResults int) DeepScan { - if maxResults <= 0 || maxResults > 4096 { - maxResults = 4096 - } - result := DeepScan{} - if len(data) == 0 { - return result - } - - results := make([]SignatureResult, 0) - seen := make(map[struct { - offset int - name string - }]int) - add := func(parsed *SignatureResult, sig *Signature) { - if parsed == nil || parsed.Offset < 0 || parsed.Offset > len(data) { - return - } - if parsed.Size < 0 || parsed.Size > len(data)-parsed.Offset { - return - } - autoPopulate(parsed, sig) - key := struct { - offset int - name string - }{parsed.Offset, parsed.Name} - if idx, ok := seen[key]; ok { - if parsed.Confidence > results[idx].Confidence { - results[idx] = *parsed - } - return - } - if len(results) >= maxResults { - result.Truncated = true - return - } - seen[key] = len(results) - results = append(results, *parsed) - } - - for i := range s.shortSignatures { - sig := &s.shortSignatures[i] - for _, magic := range sig.Magic { - start := sig.MagicOffset - end := start + len(magic) - if start >= 0 && end <= len(data) && bytesEqual(data[start:end], magic) { - parsed, err := sig.Parser(data, start) - if err == nil { - add(parsed, sig) - } - break - } - } - } - - if s.ac != nil && len(results) < maxResults { - matchLimit := maxResults * 16 - matches, truncated := s.ac.findOverlappingLimit(data, 0, matchLimit) - result.Truncated = result.Truncated || truncated - for _, match := range matches { - sig, ok := s.patternSigTable[match.PatternIndex] - if !ok { - continue - } - parsed, err := sig.Parser(data, match.Start) - if err != nil { - continue - } - add(parsed, &sig) - if len(results) >= maxResults { - result.Truncated = true - break - } - } - } - - sort.Slice(results, func(i, j int) bool { - if results[i].Offset != results[j].Offset { - return results[i].Offset < results[j].Offset - } - if results[i].Confidence != results[j].Confidence { - return results[i].Confidence > results[j].Confidence - } - return results[i].Size > results[j].Size - }) - unique := results[:0] - for _, hit := range results { - if len(unique) > 0 && unique[len(unique)-1].Offset == hit.Offset { - continue - } - unique = append(unique, hit) - } - results = unique - - nextOffset := len(data) - for i := len(results) - 1; i >= 0; i-- { - if results[i].Size == 0 { - results[i].Size = nextOffset - results[i].Offset - } - nextOffset = results[i].Offset - } - - active := make([]int, 0) - for _, hit := range results { - end := hit.Offset + hit.Size - kept := active[:0] - for _, idx := range active { - if result.Results[idx].End > hit.Offset { - kept = append(kept, idx) - } - } - active = kept - parent := -1 - overlap := false - for i := len(active) - 1; i >= 0; i-- { - candidate := result.Results[active[i]] - if candidate.End >= end && candidate.Offset < hit.Offset { - parent = active[i] - break - } - overlap = true - } - deep := DeepScanResult{SignatureResult: hit, End: end, ParentOffset: -1, Overlap: overlap} - if parent >= 0 { - deep.ParentOffset = result.Results[parent].Offset - deep.Depth = result.Results[parent].Depth + 1 - } - result.Results = append(result.Results, deep) - active = append(active, len(result.Results)-1) - } - return result -} - -// Extract performs extraction on all extractable signatures in the file map. -func (s *Scanner) Extract(data []byte, filePath string, fileMap []SignatureResult) map[string]*ExtractionResult { - results := make(map[string]*ExtractionResult) - - for _, sig := range fileMap { - if sig.ExtractionDeclined { - continue - } - - ext, ok := s.extractorLookup[sig.Name] - if !ok || ext == nil { - continue - } - - result := ExecuteExtraction(data, filePath, &sig, ext) - if !result.Success && sig.Size < len(data)-sig.Offset { - // Retry with all available data - expandedSig := sig - expandedSig.Size = len(data) - sig.Offset - result = ExecuteExtraction(data, filePath, &expandedSig, ext) - } - - results[sig.ID] = result - } - - return results -} - -// bytesEqual compares two byte slices. -func bytesEqual(a, b []byte) bool { - if len(a) != len(b) { - return false - } - for i := range a { - if a[i] != b[i] { - return false - } - } - return true -} - -// autoPopulate fills in auto-generated fields on a SignatureResult. -func autoPopulate(result *SignatureResult, sig *Signature) { - result.ID = newUUID() - result.Name = sig.Name - result.AlwaysDisplay = sig.AlwaysDisplay -} diff --git a/internal/leviathan/binsnoop/sig_archives.go b/internal/leviathan/binsnoop/sig_archives.go deleted file mode 100644 index b010c5a..0000000 --- a/internal/leviathan/binsnoop/sig_archives.go +++ /dev/null @@ -1,1143 +0,0 @@ -package binsnoop - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" - "strconv" - "strings" -) - -// --------------------------------------------------------------------------- -// gzip -// --------------------------------------------------------------------------- - -const gzipDescription = "gzip compressed data" - -func gzipMagic() [][]byte { - return [][]byte{ - {0x1f, 0x8b, 0x08}, - } -} - -func gzipParser(data []byte, offset int) (*SignatureResult, error) { - const ( - flagCRC = 0b0000_0010 - flagExtra = 0b0000_0100 - flagName = 0b0000_1000 - flagComment = 0b0001_0000 - flagReserved = 0b1110_0000 - - deflateCompression = 8 - crcSize = 2 - nullByteSize = 1 - ) - - knownOSIDs := map[int]string{ - 0: "FAT filesystem (MS-DOS, OS/2, NT/Win32", - 1: "Amiga", - 2: "VMS (or OpenVMS)", - 3: "Unix", - 4: "VM/CMS", - 5: "Atari TOS", - 6: "HPFS filesystem (OS/2, NT)", - 7: "Macintosh", - 8: "Z-System", - 9: "CP/M", - 10: "TOPS-20", - 11: "NTFS filesystem (NT)", - 12: "QDOS", - 13: "Acorn RISCOS", - 255: "unknown", - } - - gzipHeaderFields := []StructField{ - {"magic", "u16"}, - {"compression_method", "u8"}, - {"flags", "u8"}, - {"timestamp", "u32"}, - {"extra_flags", "u8"}, - {"osid", "u8"}, - } - - gzipExtraHeaderFields := []StructField{ - {"id", "u16"}, - {"extra_data_len", "u16"}, - } - - if offset >= len(data) { - return nil, fmt.Errorf("gzip: offset out of bounds") - } - - headerData := data[offset:] - hdr, err := Parse(headerData, gzipHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("gzip: failed to parse header") - } - - if hdr["flags"]&flagReserved != 0 { - return nil, fmt.Errorf("gzip: reserved flags set") - } - if hdr["compression_method"] != deflateCompression { - return nil, fmt.Errorf("gzip: invalid compression method %d", hdr["compression_method"]) - } - osName, ok := knownOSIDs[hdr["osid"]] - if !ok { - return nil, fmt.Errorf("gzip: unknown OS ID %d", hdr["osid"]) - } - - headerSize := StructSize(gzipHeaderFields) - - // Handle optional extra data - if hdr["flags"]&flagExtra != 0 { - extraHeaderSize := StructSize(gzipExtraHeaderFields) - extraStart := headerSize - extraEnd := extraStart + extraHeaderSize - if extraEnd > len(headerData) { - return nil, fmt.Errorf("gzip: not enough data for extra header") - } - extraHdr, err := Parse(headerData[extraStart:], gzipExtraHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("gzip: failed to parse extra header") - } - headerSize += extraHeaderSize + extraHdr["extra_data_len"] - } - - // Handle optional original file name (NULL-terminated) - originalName := "" - if hdr["flags"]&flagName != 0 { - if headerSize >= len(headerData) { - return nil, fmt.Errorf("gzip: not enough data for original name") - } - originalName = GetCString(headerData[headerSize:]) - headerSize += len(originalName) + nullByteSize - } - - // Handle optional comment (NULL-terminated) - if hdr["flags"]&flagComment != 0 { - if headerSize >= len(headerData) { - return nil, fmt.Errorf("gzip: not enough data for comment") - } - comment := GetCString(headerData[headerSize:]) - headerSize += len(comment) + nullByteSize - } - - // Handle optional header CRC - if hdr["flags"]&flagCRC != 0 { - headerSize += crcSize - } - - if len(headerData) < headerSize { - return nil, fmt.Errorf("gzip: data shorter than header") - } - - originalFileNameText := "" - if originalName != "" { - originalFileNameText = fmt.Sprintf(" original file name: \"%s\",", originalName) - } - - description := fmt.Sprintf("%s,%s operating system: %s, timestamp: %s", - gzipDescription, - originalFileNameText, - osName, - EpochToString(uint32(hdr["timestamp"])), - ) - - return &SignatureResult{ - Offset: offset, - Size: 0, // size unknown without decompression - Description: description, - Confidence: ConfidenceMedium, - }, nil -} - -// --------------------------------------------------------------------------- -// zip -// --------------------------------------------------------------------------- - -const zipDescription = "ZIP archive" - -func zipMagic() [][]byte { - return [][]byte{ - {0x50, 0x4b, 0x03, 0x04}, // PK\x03\x04 - } -} - -func zipParser(data []byte, offset int) (*SignatureResult, error) { - return parseZipLike(data, offset, zipDescription) -} - -// parseZipLike is shared between zip and dahua_zip parsers. -func parseZipLike(data []byte, offset int, descPrefix string) (*SignatureResult, error) { - const ( - unusedFlagsMask = 0b11010111_10000000 - compressionEncrypted = 99 - ) - - zipLocalFileFields := []StructField{ - {"magic", "u32"}, - {"version", "u16"}, - {"flags", "u16"}, - {"compression", "u16"}, - {"modification_time", "u16"}, - {"modification_date", "u16"}, - {"crc", "u32"}, - {"compressed_size", "u32"}, - {"uncompressed_size", "u32"}, - {"file_name_len", "u16"}, - {"extra_field_len", "u16"}, - } - - allowedCompressionMethods := map[int]bool{ - 0: true, 1: true, 2: true, 3: true, 4: true, 5: true, 6: true, - 8: true, 9: true, 10: true, 12: true, 14: true, 18: true, - 19: true, 20: true, 93: true, 94: true, 95: true, 96: true, - 97: true, 98: true, compressionEncrypted: true, - } - - if offset >= len(data) { - return nil, fmt.Errorf("zip: offset out of bounds") - } - - zipData := data[offset:] - hdr, err := Parse(zipData, zipLocalFileFields, "little") - if err != nil { - return nil, fmt.Errorf("zip: failed to parse local file header") - } - - if hdr["flags"]&unusedFlagsMask != 0 { - return nil, fmt.Errorf("zip: unused flags set") - } - if !allowedCompressionMethods[hdr["compression"]] { - return nil, fmt.Errorf("zip: unsupported compression method %d", hdr["compression"]) - } - - versionMajor := hdr["version"] / 10 - versionMinor := hdr["version"] % 10 - - localHeaderSize := StructSize(zipLocalFileFields) + hdr["file_name_len"] + hdr["extra_field_len"] - dataSize := hdr["compressed_size"] - if dataSize == 0 { - dataSize = hdr["uncompressed_size"] - } - firstEntryTotalSize := localHeaderSize + dataSize - - // Try to find the end-of-central-directory header - eocdMagic := []byte("PK\x05\x06\x00\x00\x00\x00") - eocdFields := []StructField{ - {"magic", "u32"}, - {"disk_number", "u16"}, - {"central_directory_disk_number", "u16"}, - {"central_directory_disk_entries", "u16"}, - {"central_directory_total_entries", "u16"}, - {"central_directory_size", "u32"}, - {"central_directory_offset", "u32"}, - {"comment_length", "u16"}, - } - - totalSize := 0 - fileCount := 0 - missingEOCD := false - - // Search for EOCD in data after offset - searchData := data[offset:] - eocdIdx := -1 - for i := 0; i+len(eocdMagic) <= len(searchData); i++ { - if bytes.Equal(searchData[i:i+len(eocdMagic)], eocdMagic) { - eocdData := searchData[i:] - eocdHdr, err := Parse(eocdData, eocdFields, "little") - if err != nil { - continue - } - if eocdHdr["central_directory_disk_entries"] == eocdHdr["central_directory_total_entries"] && - eocdHdr["central_directory_total_entries"] > 0 { - eocdSize := StructSize(eocdFields) + eocdHdr["comment_length"] - eocdIdx = i - totalSize = eocdIdx + eocdSize - fileCount = eocdHdr["central_directory_total_entries"] - break - } - } - } - - if eocdIdx < 0 { - // No EOCD found — walk local file entries - missingEOCD = true - availableData := len(data) - offset - var previousOffset *int - nextOffset := offset + firstEntryTotalSize - - for IsOffsetSafe(availableData, nextOffset, previousOffset) { - entryHdr, err := Parse(data[nextOffset:], zipLocalFileFields, "little") - if err != nil { - totalSize = nextOffset - offset - break - } - if entryHdr["flags"]&unusedFlagsMask != 0 || !allowedCompressionMethods[entryHdr["compression"]] { - totalSize = nextOffset - offset - break - } - entryHeaderSize := StructSize(zipLocalFileFields) + entryHdr["file_name_len"] + entryHdr["extra_field_len"] - entryDataSize := entryHdr["compressed_size"] - if entryDataSize == 0 { - entryDataSize = entryHdr["uncompressed_size"] - } - prev := nextOffset - previousOffset = &prev - nextOffset += entryHeaderSize + entryDataSize - } - if totalSize == 0 { - totalSize = nextOffset - offset - } - } - - if totalSize <= 0 { - return nil, fmt.Errorf("zip: could not determine archive size") - } - - var description string - if missingEOCD { - description = fmt.Sprintf("%s, version: %d.%d, missing end-of-central-directory header, total size: %d bytes", - descPrefix, versionMajor, versionMinor, totalSize) - } else { - description = fmt.Sprintf("%s, version: %d.%d, file count: %d, total size: %d bytes", - descPrefix, versionMajor, versionMinor, fileCount, totalSize) - } - - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: description, - Confidence: ConfidenceHigh, - }, nil -} - -// --------------------------------------------------------------------------- -// tarball -// --------------------------------------------------------------------------- - -const tarballDescription = "POSIX tar archive" - -const ( - tarballBlockSize = 512 - tarballMagicOffset = 257 - tarballMagicSize = 5 - tarballSizeOffset = 124 - tarballSizeLen = 11 - tarballMinExpectedHdrs = 10 - tarballChecksumStart = 148 - tarballChecksumEnd = 156 -) - -var tarballUniversalMagic = []byte("ustar") - -func tarballMagic() [][]byte { - return [][]byte{ - {0x75, 0x73, 0x74, 0x61, 0x72, 0x00}, // ustar\x00 - {0x75, 0x73, 0x74, 0x61, 0x72, 0x20, 0x20, 0x00}, // ustar\x20\x20\x00 - } -} - -func tarballParser(data []byte, offset int) (*SignatureResult, error) { - // The magic bytes are at offset 257 within a tar header block - tarballStartOffset := offset - tarballMagicOffset - - if tarballStartOffset < 0 || tarballStartOffset >= len(data) { - return nil, fmt.Errorf("tarball: invalid start offset") - } - - var tarballTotalSize int - var validHeaderCount int - nextHeaderStart := tarballStartOffset - var previousHeaderStart *int - availableData := len(data) - - for IsOffsetSafe(availableData, nextHeaderStart, previousHeaderStart) { - nextHeaderEnd := nextHeaderStart + tarballBlockSize - if nextHeaderEnd > len(data) { - break - } - headerBlock := data[nextHeaderStart:nextHeaderEnd] - - if !tarballHeaderChecksumValid(headerBlock) { - break - } - - validHeaderCount++ - - entrySize, err := tarballEntrySize(headerBlock) - if err != nil { - break - } - - tarballTotalSize += entrySize - prev := nextHeaderStart - previousHeaderStart = &prev - nextHeaderStart += entrySize - } - - if tarballTotalSize < tarballBlockSize { - return nil, fmt.Errorf("tarball: archive too small") - } - - confidence := ConfidenceMedium - if validHeaderCount >= tarballMinExpectedHdrs { - confidence = ConfidenceHigh - } - - return &SignatureResult{ - Offset: tarballStartOffset, - Size: tarballTotalSize, - Description: fmt.Sprintf("%s, file count: %d", tarballDescription, validHeaderCount), - Confidence: confidence, - }, nil -} - -func tarballHeaderChecksumValid(headerBlock []byte) bool { - checksumValueBytes := headerBlock[tarballChecksumStart:tarballChecksumEnd] - reportedChecksum := tarballOctal(checksumValueBytes) - var sum int - for i, b := range headerBlock { - if i >= tarballChecksumStart && i < tarballChecksumEnd { - sum += 0x20 - } else { - sum += int(b) - } - } - return sum == reportedChecksum -} - -func tarballEntrySize(entryData []byte) (int, error) { - entryMagic := entryData[tarballMagicOffset : tarballMagicOffset+tarballMagicSize] - if !bytes.Equal(entryMagic, tarballUniversalMagic) { - return 0, fmt.Errorf("tarball: invalid entry magic") - } - - entrySizeBytes := entryData[tarballSizeOffset : tarballSizeOffset+tarballSizeLen] - reportedEntrySize := tarballOctal(entrySizeBytes) - - blockCount := 1 + int(math.Ceil(float64(reportedEntrySize)/float64(tarballBlockSize))) - return blockCount * tarballBlockSize, nil -} - -func tarballOctal(octalStr []byte) int { - var num int - for _, c := range octalStr { - if c < 0x30 || c > 0x39 { - break - } - num = num*8 + int(c) - 0x30 - } - return num -} - -// --------------------------------------------------------------------------- -// 7-zip (sevenzip) -// --------------------------------------------------------------------------- - -const sevenzipDescription = "7-zip archive data" - -func sevenzipMagic() [][]byte { - return [][]byte{ - {0x37, 0x7a, 0xbc, 0xaf, 0x27, 0x1c}, // 7z\xbc\xaf\x27\x1c - } -} - -func sevenzipParser(data []byte, offset int) (*SignatureResult, error) { - const ( - sevenzipCRCStart = 12 - sevenzipHeaderSize = 32 - ) - - sevenzipFields := []StructField{ - {"magic_p1", "u16"}, - {"magic_p2", "u32"}, - {"major_version", "u8"}, - {"minor_version", "u8"}, - {"header_crc", "u32"}, - {"next_header_offset", "u64"}, - {"next_header_size", "u64"}, - {"next_header_crc", "u32"}, - } - - if offset >= len(data) { - return nil, fmt.Errorf("7zip: offset out of bounds") - } - - szData := data[offset:] - hdr, err := Parse(szData, sevenzipFields, "little") - if err != nil { - return nil, fmt.Errorf("7zip: failed to parse header") - } - - // Validate the header CRC (over bytes 12..32) - if sevenzipHeaderSize > len(szData) { - return nil, fmt.Errorf("7zip: not enough data for header CRC") - } - crcData := szData[sevenzipCRCStart:sevenzipHeaderSize] - calculatedHeaderCRC := CRC32(crcData) - if calculatedHeaderCRC != uint32(hdr["header_crc"]) { - return nil, fmt.Errorf("7zip: header CRC mismatch") - } - - // Validate the next_header CRC - nextCRCStart := offset + sevenzipHeaderSize + hdr["next_header_offset"] - nextCRCEnd := nextCRCStart + hdr["next_header_size"] - if nextCRCEnd > len(data) { - return nil, fmt.Errorf("7zip: not enough data for next header CRC") - } - nextCRCData := data[nextCRCStart:nextCRCEnd] - calculatedNextCRC := CRC32(nextCRCData) - if int(calculatedNextCRC) != hdr["next_header_crc"] { - return nil, fmt.Errorf("7zip: next header CRC mismatch") - } - - totalSize := sevenzipHeaderSize + hdr["next_header_offset"] + hdr["next_header_size"] - - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, version %d.%d, total size: %d bytes", sevenzipDescription, hdr["major_version"], hdr["minor_version"], totalSize), - Confidence: ConfidenceHigh, - }, nil -} - -// --------------------------------------------------------------------------- -// RAR -// --------------------------------------------------------------------------- - -const rarDescription = "RAR archive" - -func rarMagic() [][]byte { - return [][]byte{ - {0x52, 0x61, 0x72, 0x21, 0x1a, 0x07}, // Rar!\x1A\x07 - } -} - -func rarParser(data []byte, offset int) (*SignatureResult, error) { - archiveHeaderFields := []StructField{ - {"magic_p1", "u32"}, - {"magic_p2", "u16"}, - {"version", "u8"}, - } - - // version field 0 => RARv4, 1 => RARv5 - versionMap := map[int]int{0: 4, 1: 5} - - if offset >= len(data) { - return nil, fmt.Errorf("rar: offset out of bounds") - } - - hdr, err := Parse(data[offset:], archiveHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("rar: failed to parse header") - } - - version, ok := versionMap[hdr["version"]] - if !ok { - return nil, fmt.Errorf("rar: unknown version %d", hdr["version"]) - } - - // EOF markers for RARv4 and RARv5 - eofMarkers := map[int][]byte{ - 4: {0xc4, 0x3d, 0x7b, 0x00, 0x40, 0x07, 0x00}, - 5: {0x1d, 0x77, 0x56, 0x51, 0x03, 0x05, 0x04, 0x00}, - } - - totalSize := 0 - extraDesc := "" - - if marker, ok := eofMarkers[version]; ok { - searchData := data[offset:] - idx := bytes.Index(searchData, marker) - if idx >= 0 { - totalSize = idx + len(marker) - } else { - extraDesc = " (failed to locate RAR EOF)" - } - } - - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, version: %d, total size: %d bytes%s", rarDescription, version, totalSize, extraDesc), - Confidence: ConfidenceMedium, - }, nil -} - -// --------------------------------------------------------------------------- -// CAB (Microsoft Cabinet) -// --------------------------------------------------------------------------- - -const cabDescription = "Microsoft Cabinet archive" - -func cabMagic() [][]byte { - return [][]byte{ - {0x4d, 0x53, 0x43, 0x46, 0x00, 0x00, 0x00, 0x00}, // MSCF\x00\x00\x00\x00 - } -} - -func cabParser(data []byte, offset int) (*SignatureResult, error) { - const ( - majorVersion = 1 - minorVersion = 3 - cabStructSize = 40 - cabExtraStructSize = 20 - flagExtraDataPresent = 4 - ) - - cabHeaderFields := []StructField{ - {"magic", "u32"}, - {"reserved1", "u32"}, - {"size", "u32"}, - {"reserved2", "u32"}, - {"first_file_offset", "u32"}, - {"reserved3", "u32"}, - {"minor_version", "u8"}, - {"major_version", "u8"}, - {"folder_count", "u16"}, - {"file_count", "u16"}, - {"flags", "u16"}, - {"id", "u16"}, - {"set_number", "u16"}, - {"extra_header_size", "u16"}, - {"cbCFFolder", "u8"}, - {"cbCFData", "u8"}, - } - - cabExtraHeaderFields := []StructField{ - {"unknown1", "u32"}, - {"data_offset", "u32"}, - {"data_size", "u32"}, - {"unknown2", "u32"}, - {"unknown3", "u32"}, - } - - if offset >= len(data) { - return nil, fmt.Errorf("cab: offset out of bounds") - } - - hdr, err := Parse(data[offset:], cabHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("cab: failed to parse header") - } - - // All reserved fields must be 0 - if hdr["reserved1"] != 0 || hdr["reserved2"] != 0 || hdr["reserved3"] != 0 { - return nil, fmt.Errorf("cab: reserved fields not zero") - } - - // Version must be 1.3 - if hdr["major_version"] != majorVersion || hdr["minor_version"] != minorVersion { - return nil, fmt.Errorf("cab: invalid version %d.%d", hdr["major_version"], hdr["minor_version"]) - } - - totalSize := hdr["size"] - headerSize := cabStructSize - - // Handle extra data flag - if hdr["flags"]&flagExtraDataPresent != 0 && hdr["extra_header_size"] == cabExtraStructSize { - extraStart := cabStructSize - extraEnd := extraStart + cabExtraStructSize - if offset+extraEnd > len(data) { - return nil, fmt.Errorf("cab: not enough data for extra header") - } - extraHdr, err := Parse(data[offset+extraStart:], cabExtraHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("cab: failed to parse extra header") - } - if extraHdr["data_offset"] != hdr["size"] { - return nil, fmt.Errorf("cab: extra data offset mismatch") - } - totalSize += extraHdr["data_size"] - _ = headerSize - } else if hdr["flags"]&flagExtraDataPresent != 0 { - return nil, fmt.Errorf("cab: extra data flag set but wrong extra header size") - } - - availableData := len(data) - offset - if totalSize > availableData { - return nil, fmt.Errorf("cab: reported size exceeds available data") - } - - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, file count: %d, folder count: %d, header size: %d, total size: %d bytes", cabDescription, hdr["file_count"], hdr["folder_count"], cabStructSize, totalSize), - Confidence: ConfidenceMedium, - }, nil -} - -// --------------------------------------------------------------------------- -// Debian package (deb) -// --------------------------------------------------------------------------- - -const debDescription = "Debian package file" - -func debMagic() [][]byte { - return [][]byte{ - // "!\ndebian-binary " - {0x21, 0x3c, 0x61, 0x72, 0x63, 0x68, 0x3e, 0x0a, - 0x64, 0x65, 0x62, 0x69, 0x61, 0x6e, 0x2d, 0x62, - 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x20, 0x20}, - } -} - -func debParser(data []byte, offset int) (*SignatureResult, error) { - const ( - endMarkerSize = 2 - dataFileSizeLen = 10 - dataFileSizeOffset = 48 - controlFileSizeEnd = 130 - controlFileSizeStart = 120 - ) - - if offset >= len(data) { - return nil, fmt.Errorf("deb: offset out of bounds") - } - - debData := data[offset:] - - if len(debData) < controlFileSizeEnd { - return nil, fmt.Errorf("deb: not enough data for control file size") - } - - // Read control file size as ASCII decimal - controlFileSizeBytes := debData[controlFileSizeStart:controlFileSizeEnd] - controlFileSizeStr := strings.TrimSpace(string(controlFileSizeBytes)) - controlFileSize, err := strconv.Atoi(controlFileSizeStr) - if err != nil { - return nil, fmt.Errorf("deb: invalid control file size: %s", controlFileSizeStr) - } - - // Calculate data file size location - dataFileSizeStart := controlFileSizeEnd + endMarkerSize + controlFileSize + dataFileSizeOffset - dataFileSizeEnd := dataFileSizeStart + dataFileSizeLen - - if dataFileSizeEnd > len(debData) { - return nil, fmt.Errorf("deb: not enough data for data file size") - } - - dataFileSizeBytes := debData[dataFileSizeStart:dataFileSizeEnd] - dataFileSizeStr := strings.TrimSpace(string(dataFileSizeBytes)) - dataFileSize, err := strconv.Atoi(dataFileSizeStr) - if err != nil { - return nil, fmt.Errorf("deb: invalid data file size: %s", dataFileSizeStr) - } - - totalSize := dataFileSizeEnd + endMarkerSize + dataFileSize - - if totalSize > len(debData) { - return nil, fmt.Errorf("deb: reported size exceeds available data") - } - - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, total size: %d bytes", debDescription, totalSize), - Confidence: ConfidenceHigh, - }, nil -} - -// --------------------------------------------------------------------------- -// CPIO -// --------------------------------------------------------------------------- - -const cpioDescription = "CPIO ASCII archive" - -const cpioHeaderSize = 110 - -func cpioMagic() [][]byte { - return [][]byte{ - {0x30, 0x37, 0x30, 0x37, 0x30, 0x31}, // 070701 - {0x30, 0x37, 0x30, 0x37, 0x30, 0x32}, // 070702 - } -} - -func cpioParser(data []byte, offset int) (*SignatureResult, error) { - const eofMarker = "TRAILER!!!" - - if offset >= len(data) { - return nil, fmt.Errorf("cpio: offset out of bounds") - } - - validMagics := cpioMagic() - - var headerCount int - var totalSize int - nextHeaderOffset := offset - var previousHeaderOffset *int - availableData := len(data) - - for IsOffsetSafe(availableData, nextHeaderOffset, previousHeaderOffset) { - if nextHeaderOffset >= len(data) { - break - } - cpioEntryData := data[nextHeaderOffset:] - - entryHeader, err := parseCPIOEntryHeader(cpioEntryData) - if err != nil { - break - } - - // Validate magic - magicValid := false - for _, m := range validMagics { - if bytes.Equal(entryHeader.magic, m) { - magicValid = true - break - } - } - if !magicValid { - break - } - - headerCount++ - totalSize += entryHeader.headerSize + entryHeader.dataSize - - if entryHeader.fileName == eofMarker { - if headerCount > 1 { - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, file count: %d", cpioDescription, headerCount-1), - Confidence: ConfidenceHigh, - }, nil - } - break - } - - prev := nextHeaderOffset - previousHeaderOffset = &prev - nextHeaderOffset = offset + totalSize - } - - return nil, fmt.Errorf("cpio: no valid archive found") -} - -type cpioEntry struct { - magic []byte - fileName string - headerSize int - dataSize int -} - -func parseCPIOEntryHeader(cpioData []byte) (*cpioEntry, error) { - const ( - nullByteSize = 1 - cpioMagicStart = 0 - cpioMagicEnd = 6 - fileSizeStart = 54 - fileSizeEnd = 62 - fileNameSizeStart = 94 - fileNameSizeEnd = 102 - ) - - if len(cpioData) <= cpioHeaderSize { - return nil, fmt.Errorf("cpio: not enough data") - } - - headerMagic := make([]byte, cpioMagicEnd-cpioMagicStart) - copy(headerMagic, cpioData[cpioMagicStart:cpioMagicEnd]) - - // Get file data size (ASCII hex) - fileSizeStr := string(cpioData[fileSizeStart:fileSizeEnd]) - fileDataSize, err := strconv.ParseInt(fileSizeStr, 16, 64) - if err != nil { - return nil, fmt.Errorf("cpio: invalid file data size") - } - - // Get file name size (ASCII hex) - fileNameSizeStr := string(cpioData[fileNameSizeStart:fileNameSizeEnd]) - fileNameSize, err := strconv.ParseInt(fileNameSizeStr, 16, 64) - if err != nil { - return nil, fmt.Errorf("cpio: invalid file name size") - } - - fileNameStart := cpioHeaderSize - fileNameEnd := fileNameStart + int(fileNameSize) - nullByteSize - - if fileNameEnd > len(cpioData) || fileNameEnd < fileNameStart { - return nil, fmt.Errorf("cpio: file name out of bounds") - } - - fileName := string(cpioData[fileNameStart:fileNameEnd]) - - headerTotalSize := cpioHeaderSize + int(fileNameSize) - - return &cpioEntry{ - magic: headerMagic, - fileName: fileName, - headerSize: headerTotalSize + cpioBytePadding(headerTotalSize), - dataSize: int(fileDataSize) + cpioBytePadding(int(fileDataSize)), - }, nil -} - -func cpioBytePadding(n int) int { - mod := n % 4 - if mod == 0 { - return 0 - } - return 4 - mod -} - -// --------------------------------------------------------------------------- -// ARJ -// --------------------------------------------------------------------------- - -const arjDescription = "ARJ archive data" - -func arjMagic() [][]byte { - return [][]byte{ - {0x60, 0xea}, - } -} - -func arjParser(data []byte, offset int) (*SignatureResult, error) { - arjHeaderFields := []StructField{ - {"magic", "u16"}, - {"basic_header_size", "u16"}, - {"extra_header_size", "u8"}, - {"archiver_version", "u8"}, - {"min_version", "u8"}, - {"host_os", "u8"}, - {"internal_flags", "u8"}, - {"compression_method", "u8"}, - {"file_type", "u8"}, - {"reserved1", "u8"}, - {"datetime_file", "u32"}, - {"compressed_filesize", "u32"}, - {"original_filesize", "u32"}, - } - - if offset >= len(data) { - return nil, fmt.Errorf("arj: offset out of bounds") - } - - hdr, err := Parse(data[offset:], arjHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("arj: failed to parse header") - } - - // Version validation - if hdr["archiver_version"] < 1 || hdr["archiver_version"] > 16 || - hdr["min_version"] < 1 || hdr["min_version"] > 16 || - hdr["archiver_version"] < hdr["min_version"] { - return nil, fmt.Errorf("arj: invalid version info") - } - - // Flags - flags := "no password" - if hdr["internal_flags"]&0x01 != 0 { - flags = "password" - } - if hdr["internal_flags"]&0x04 != 0 { - flags += "|multi-volume" - } - if hdr["internal_flags"]&0x10 != 0 { - flags += "|slash-switched" - } - if hdr["internal_flags"]&0x20 != 0 { - flags += "|backup" - } - - // Host OS - hostOSMap := map[int]string{ - 0: "MS-DOS", 1: "PRIMOS", 2: "UNIX", 3: "AMIGA", 4: "MAX-OS", - 5: "OS/2", 6: "APPLE GS", 7: "ATARI ST", 8: "NeXT", 9: "VAX VMS", - } - hostOS, ok := hostOSMap[hdr["host_os"]] - if !ok { - return nil, fmt.Errorf("arj: unknown host OS %d", hdr["host_os"]) - } - - // Compression method - compressionMethodMap := map[int]string{ - 0: "stored", 1: "compressed most", 2: "compressed", - 3: "compressed faster", 4: "compressed fastest", - } - compressionMethod, ok := compressionMethodMap[hdr["compression_method"]] - if !ok { - return nil, fmt.Errorf("arj: unknown compression method %d", hdr["compression_method"]) - } - - // File type - fileTypeMap := map[int]string{ - 0: "binary", 1: "7-bit text", 2: "comment header", - 3: "directory", 4: "volume label", - } - fileType, ok := fileTypeMap[hdr["file_type"]] - if !ok { - return nil, fmt.Errorf("arj: unknown file type %d", hdr["file_type"]) - } - - // Compressed/uncompressed sizes — check for negative as signed i32 - compressedFileSize := int32(hdr["compressed_filesize"]) - if compressedFileSize < 0 { - return nil, fmt.Errorf("arj: negative compressed file size") - } - uncompressedFileSize := int32(hdr["original_filesize"]) - if uncompressedFileSize < 0 { - return nil, fmt.Errorf("arj: negative uncompressed file size") - } - - // Original file name - headerSize := hdr["extra_header_size"] - originalName := "" - if headerSize+4 < len(data[offset:]) { - originalName = GetCString(data[offset+headerSize+4:]) - } - - originalFileDate := EpochToString(uint32(hdr["datetime_file"])) - - availableData := len(data) - offset - if headerSize > availableData { - return nil, fmt.Errorf("arj: header size exceeds available data") - } - - extractionDeclined := fileType != "comment header" - - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf( - "%s, header size: %d, version %d, minimum version to extract: %d, flags: %s, compression method: %s, file type: %s, original name: %s, original file date: %s, compressed file size: %d, uncompressed file size: %d, os: %s", - arjDescription, - headerSize, - hdr["archiver_version"], - hdr["min_version"], - flags, - compressionMethod, - fileType, - originalName, - originalFileDate, - compressedFileSize, - uncompressedFileSize, - hostOS, - ), - Confidence: ConfidenceMedium, - ExtractionDeclined: extractionDeclined, - }, nil -} - -// --------------------------------------------------------------------------- -// ISO 9660 -// --------------------------------------------------------------------------- - -const iso9660Description = "ISO9660 primary volume" - -func iso9660Magic() [][]byte { - return [][]byte{ - {0x01, 0x43, 0x44, 0x30, 0x30, 0x31, 0x01, 0x00}, // \x01CD001\x01\x00 - } -} - -func iso9660Parser(data []byte, offset int) (*SignatureResult, error) { - const ( - isoMagicOffset = 32768 - isoStructStart = 32840 - ) - - isoFields := []StructField{ - {"unused1", "u64"}, - {"volume_size_lsb", "u32"}, - {"volume_size_msb", "u32"}, - {"unused2", "u64"}, - {"unused3", "u64"}, - {"unused4", "u64"}, - {"unused5", "u64"}, - {"set_size_lsb", "u16"}, - {"set_size_msb", "u16"}, - {"sequence_number_lsb", "u16"}, - {"sequence_number_msb", "u16"}, - {"block_size_lsb", "u16"}, - {"block_size_msb", "u16"}, - {"path_table_size_lsb", "u32"}, - {"path_table_size_msb", "u32"}, - } - - if offset < isoMagicOffset { - return nil, fmt.Errorf("iso9660: not enough data before magic offset") - } - - isoStart := offset - isoMagicOffset - - if isoStart+isoStructStart >= len(data) { - return nil, fmt.Errorf("iso9660: not enough data for header") - } - - isoHeaderData := data[isoStart+isoStructStart:] - hdr, err := Parse(isoHeaderData, isoFields, "little") - if err != nil { - return nil, fmt.Errorf("iso9660: failed to parse header") - } - - // Unused fields must be 0 - if hdr["unused1"] != 0 || hdr["unused2"] != 0 || hdr["unused3"] != 0 || - hdr["unused4"] != 0 || hdr["unused5"] != 0 { - return nil, fmt.Errorf("iso9660: unused fields not zero") - } - - // Validate byte-swapped field pairs - if hdr["set_size_lsb"] != int(swapU16(uint16(hdr["set_size_msb"]))) { - return nil, fmt.Errorf("iso9660: set_size mismatch") - } - if hdr["block_size_lsb"] != int(swapU16(uint16(hdr["block_size_msb"]))) { - return nil, fmt.Errorf("iso9660: block_size mismatch") - } - if hdr["volume_size_lsb"] != int(swapU32(uint32(hdr["volume_size_msb"]))) { - return nil, fmt.Errorf("iso9660: volume_size mismatch") - } - if hdr["sequence_number_lsb"] != int(swapU16(uint16(hdr["sequence_number_msb"]))) { - return nil, fmt.Errorf("iso9660: sequence_number mismatch") - } - if hdr["path_table_size_lsb"] != int(swapU32(uint32(hdr["path_table_size_msb"]))) { - return nil, fmt.Errorf("iso9660: path_table_size mismatch") - } - - imageSize := hdr["volume_size_lsb"] * hdr["block_size_lsb"] - - return &SignatureResult{ - Offset: isoStart, - Size: imageSize, - Description: fmt.Sprintf("%s, total size: %d bytes", iso9660Description, imageSize), - Confidence: ConfidenceHigh, - }, nil -} - -// swapU16 swaps bytes of a uint16 (big-endian to native / vice versa) -func swapU16(v uint16) uint16 { - b := make([]byte, 2) - binary.BigEndian.PutUint16(b, v) - return binary.LittleEndian.Uint16(b) -} - -// swapU32 swaps bytes of a uint32 -func swapU32(v uint32) uint32 { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, v) - return binary.LittleEndian.Uint32(b) -} - -// --------------------------------------------------------------------------- -// Dahua ZIP -// --------------------------------------------------------------------------- - -const dahuaZipDescription = "Dahua ZIP archive" - -func dahuaZipMagic() [][]byte { - return [][]byte{ - {0x44, 0x48, 0x03, 0x04}, // DH\x03\x04 - } -} - -func dahuaZipParser(data []byte, offset int) (*SignatureResult, error) { - // Parse as a normal ZIP file, then replace the description prefix - result, err := parseZipLike(data, offset, zipDescription) - if err != nil { - return nil, fmt.Errorf("dahua_zip: %v", err) - } - result.Description = strings.Replace(result.Description, zipDescription, dahuaZipDescription, 1) - return result, nil -} diff --git a/internal/leviathan/binsnoop/sig_compression.go b/internal/leviathan/binsnoop/sig_compression.go deleted file mode 100644 index eccfd7a..0000000 --- a/internal/leviathan/binsnoop/sig_compression.go +++ /dev/null @@ -1,1081 +0,0 @@ -package binsnoop - -import ( - "encoding/binary" - "fmt" -) - -// --------------------------------------------------------------------------- -// LZMA -// --------------------------------------------------------------------------- - -const LZMADescription = "LZMA compressed data" - -func LZMAMagic() [][]byte { - // Common LZMA properties - properties := []byte{0x5D, 0x6E, 0x6D, 0x6C} - - dictionarySizes := []uint32{ - 0x10000000, - 0x20000000, - 0x01000000, - 0x02000000, - 0x04000000, - 0x00800000, - 0x00400000, - 0x00200000, - 0x00100000, - 0x00080000, - 0x00020000, - 0x00010000, - } - - // Build magic signatures from property+dictionary_size combinations (little-endian) - var sigs [][]byte - for _, prop := range properties { - for _, dict := range dictionarySizes { - magic := make([]byte, 5) - magic[0] = prop - binary.LittleEndian.PutUint32(magic[1:], dict) - sigs = append(sigs, magic) - } - } - return sigs -} - -func LZMAParser(data []byte, offset int) (*SignatureResult, error) { - // Streamed data has a reported size of -1 (0xFFFFFFFFFFFFFFFF) - const lzmaStreamSize = -1 - - const minSupportedDecompressedSize = 256 - const maxSupportedDecompressedSize = 0xFFFFFFFF - - lzmaStructure := []StructField{ - {"properties", "u8"}, - {"dictionary_size", "u32"}, - {"decompressed_size", "u64"}, - {"null_byte", "u8"}, - } - - lzmaData := data[offset:] - if len(lzmaData) < StructSize(lzmaStructure) { - return nil, fmt.Errorf("LZMA: not enough data for header") - } - - hdr, err := Parse(lzmaData, lzmaStructure, "little") - if err != nil { - return nil, fmt.Errorf("LZMA: header parse error: %w", err) - } - - // Make sure the expected NULL byte is NULL - if hdr["null_byte"] != 0 { - return nil, fmt.Errorf("LZMA: null byte is not zero") - } - - // Sanity check the reported decompressed size. - // In Go int (signed 64-bit), 0xFFFFFFFFFFFFFFFF becomes -1. - decompressedSize := hdr["decompressed_size"] - validSize := decompressedSize == lzmaStreamSize || - (decompressedSize >= minSupportedDecompressedSize && - decompressedSize <= maxSupportedDecompressedSize) - if !validSize { - return nil, fmt.Errorf("LZMA: invalid decompressed size: %d", decompressedSize) - } - - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf( - "%s, properties: 0x%02X, dictionary size: %d bytes, uncompressed size: %d bytes", - LZMADescription, - hdr["properties"], - hdr["dictionary_size"], - decompressedSize, - ), - Confidence: ConfidenceHigh, - }, nil -} - -// --------------------------------------------------------------------------- -// bzip2 -// --------------------------------------------------------------------------- - -const Bzip2Description = "bzip2 compressed data" - -func Bzip2Magic() [][]byte { - // Magic bytes include version, block size, and compressed magic signature - return [][]byte{ - []byte("BZh91AY&SY"), - []byte("BZh81AY&SY"), - []byte("BZh71AY&SY"), - []byte("BZh61AY&SY"), - []byte("BZh51AY&SY"), - []byte("BZh41AY&SY"), - []byte("BZh31AY&SY"), - []byte("BZh21AY&SY"), - []byte("BZh11AY&SY"), - } -} - -func Bzip2Parser(data []byte, offset int) (*SignatureResult, error) { - // Validate we have enough data for at least the magic + some compressed data - if offset+10 >= len(data) { - return nil, fmt.Errorf("bzip2: not enough data") - } - - return &SignatureResult{ - Offset: offset, - Description: Bzip2Description, - Confidence: ConfidenceHigh, - }, nil -} - -// --------------------------------------------------------------------------- -// XZ -// --------------------------------------------------------------------------- - -const XZDescription = "XZ compressed data" - -func XZMagic() [][]byte { - return [][]byte{ - {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, - } -} - -func XZParser(data []byte, offset int) (*SignatureResult, error) { - const xzCRCStart = 6 - const xzCRCEnd = 8 - const xzHeaderSize = 12 - - xzStructure := []StructField{ - {"magic_p1", "u32"}, - {"magic_p2", "u16"}, - {"flags", "u16"}, - {"header_crc", "u32"}, - } - - xzData := data[offset:] - if len(xzData) < StructSize(xzStructure) { - return nil, fmt.Errorf("XZ: not enough data for header") - } - - hdr, err := Parse(xzData, xzStructure, "little") - if err != nil { - return nil, fmt.Errorf("XZ: header parse error: %w", err) - } - - // Validate the header CRC over the flags bytes (bytes 6..8) - if xzCRCEnd > len(xzData) { - return nil, fmt.Errorf("XZ: not enough data for CRC") - } - - crcData := xzData[xzCRCStart:xzCRCEnd] - calculatedCRC := CRC32(crcData) - if calculatedCRC != uint32(hdr["header_crc"]) { - return nil, fmt.Errorf("XZ: header CRC mismatch: expected 0x%08X, got 0x%08X", - hdr["header_crc"], calculatedCRC) - } - - return &SignatureResult{ - Offset: offset, - Description: XZDescription, - Confidence: ConfidenceHigh, - Size: xzHeaderSize, - }, nil -} - -// --------------------------------------------------------------------------- -// ZSTD -// --------------------------------------------------------------------------- - -const ZSTDDescription = "ZSTD compressed data" - -func ZSTDMagic() [][]byte { - return [][]byte{ - {0x28, 0xB5, 0x2F, 0xFD}, - } -} - -func ZSTDParser(data []byte, offset int) (*SignatureResult, error) { - const eofChecksumSize = 4 - const minBlockCount = 2 - - availableData := len(data) - - // Parse the ZSTD frame header - zstdHeader, err := parseZSTDHeader(data[offset:]) - if err != nil { - return nil, fmt.Errorf("ZSTD: %w", err) - } - - // Calculate first block header offset accounting for optional fields - nextBlockStart := offset + zstdHeader.fixedHeaderSize - - // If single segment flag is not set, window descriptor byte is present - if !zstdHeader.singleSegmentFlag { - nextBlockStart++ - } - - // Dictionary ID field size - if zstdHeader.dictionaryIDFlag == 1 { - nextBlockStart++ - } else if zstdHeader.dictionaryIDFlag == 2 { - nextBlockStart += 2 - } else if zstdHeader.dictionaryIDFlag == 3 { - nextBlockStart += 4 - } - - // Frame content header field size - if zstdHeader.frameContentFlag == 0 && zstdHeader.singleSegmentFlag { - nextBlockStart++ - } else if zstdHeader.frameContentFlag == 1 { - nextBlockStart += 2 - } else if zstdHeader.frameContentFlag == 2 { - nextBlockStart += 4 - } else if zstdHeader.frameContentFlag == 3 { - nextBlockStart += 8 - } - - var previousBlockStart *int - blockCount := 0 - totalSize := 0 - - // Walk through blocks to determine end of ZSTD data - for IsOffsetSafe(availableData, nextBlockStart, previousBlockStart) { - blockHeader, err := parseZSTDBlockHeader(data[nextBlockStart:]) - if err != nil { - break - } - - blockCount++ - prev := nextBlockStart - previousBlockStart = &prev - nextBlockStart += blockHeader.headerSize + blockHeader.blockSize - - if blockHeader.lastBlock { - totalSize = nextBlockStart - offset - if zstdHeader.contentChecksumPresent { - totalSize += eofChecksumSize - } - if blockCount >= minBlockCount { - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, total size: %d bytes", ZSTDDescription, totalSize), - Confidence: ConfidenceHigh, - Size: totalSize, - }, nil - } - break - } - } - - return nil, fmt.Errorf("ZSTD: failed to validate block structure") -} - -type zstdHeaderInfo struct { - fixedHeaderSize int - dictionaryIDFlag int - contentChecksumPresent bool - singleSegmentFlag bool - frameContentFlag int -} - -func parseZSTDHeader(zstdData []byte) (*zstdHeaderInfo, error) { - const frameUnusedBitsMask = 0b00011000 - const dictionaryIDMask = 0b11 - const contentChecksumMask = 0b100 - const singleSegmentMask = 0b100000 - const frameContentMask = 0b11000000 - const frameContentShift = 6 - - structure := []StructField{ - {"magic", "u32"}, - {"frame_header_descriptor", "u8"}, - } - - hdr, err := Parse(zstdData, structure, "little") - if err != nil { - return nil, fmt.Errorf("header parse error: %w", err) - } - - // Unused bits should be zero - if (hdr["frame_header_descriptor"] & frameUnusedBitsMask) != 0 { - return nil, fmt.Errorf("unused bits are set in frame header descriptor") - } - - return &zstdHeaderInfo{ - fixedHeaderSize: StructSize(structure), - dictionaryIDFlag: hdr["frame_header_descriptor"] & dictionaryIDMask, - contentChecksumPresent: (hdr["frame_header_descriptor"] & contentChecksumMask) != 0, - singleSegmentFlag: (hdr["frame_header_descriptor"] & singleSegmentMask) != 0, - frameContentFlag: (hdr["frame_header_descriptor"] & frameContentMask) >> frameContentShift, - }, nil -} - -type zstdBlockHeaderInfo struct { - headerSize int - blockType int - blockSize int - lastBlock bool -} - -func parseZSTDBlockHeader(blockData []byte) (*zstdBlockHeaderInfo, error) { - const blockTypeMask = 0b110 - const blockTypeShift = 1 - const rleBlockType = 1 - const reservedBlockType = 3 - const lastBlockMask = 0b1 - const blockSizeMask = 0b1111_1111_1111_1111_1111_1000 - const blockSizeShift = 3 - - structure := []StructField{{"info_bits", "u24"}} - - hdr, err := Parse(blockData, structure, "little") - if err != nil { - return nil, fmt.Errorf("block header parse error: %w", err) - } - - info := &zstdBlockHeaderInfo{ - headerSize: StructSize(structure), - lastBlock: (hdr["info_bits"] & lastBlockMask) != 0, - blockType: (hdr["info_bits"] & blockTypeMask) >> blockTypeShift, - blockSize: (hdr["info_bits"] & blockSizeMask) >> blockSizeShift, - } - - // RLE block: single byte of raw data - if info.blockType == rleBlockType { - info.blockSize = 1 - } - - // Reserved block type is invalid - if info.blockType == reservedBlockType { - return nil, fmt.Errorf("reserved block type encountered") - } - - return info, nil -} - -// --------------------------------------------------------------------------- -// LZ4 -// --------------------------------------------------------------------------- - -const LZ4Description = "LZ4 compressed data" - -func LZ4Magic() [][]byte { - return [][]byte{ - {0x04, 0x22, 0x4D, 0x18}, - } -} - -func LZ4Parser(data []byte, offset int) (*SignatureResult, error) { - const contentChecksumLen = 4 - - lz4Header, err := parseLZ4FileHeader(data[offset:]) - if err != nil { - return nil, fmt.Errorf("LZ4: %w", err) - } - - if offset+lz4Header.headerSize >= len(data) { - return nil, fmt.Errorf("LZ4: not enough data after header") - } - - lz4Data := data[offset+lz4Header.headerSize:] - dataSize, err := getLZ4DataSize(lz4Data, lz4Header.blockChecksumPresent) - if err != nil { - return nil, fmt.Errorf("LZ4: %w", err) - } - - totalSize := lz4Header.headerSize + dataSize - if lz4Header.contentChecksumPresent { - totalSize += contentChecksumLen - } - - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, total size: %d bytes", LZ4Description, totalSize), - Confidence: ConfidenceMedium, - Size: totalSize, - }, nil -} - -type lz4FileHeaderInfo struct { - headerSize int - blockChecksumPresent bool - contentChecksumPresent bool -} - -func parseLZ4FileHeader(lz4Data []byte) (*lz4FileHeaderInfo, error) { - const magicSize = 4 - const lz4StructSize = 6 - - const bdReservedMask = 0b10001111 - const flagsReservedMask = 0b00000010 - - const flagDictionaryPresent = 0b00000001 - const flagContentSizePresent = 0b00001000 - const flagBlockChecksumPresent = 0b00010000 - const flagContentChecksumPresent = 0b00000100 - - const dictionaryLen = 4 - const contentSizeLen = 8 - - structure := []StructField{ - {"magic", "u32"}, - {"flags", "u8"}, - {"bd", "u8"}, - } - - hdr, err := Parse(lz4Data, structure, "little") - if err != nil { - return nil, fmt.Errorf("file header parse error: %w", err) - } - - // Reserved bits must not be set - if (hdr["flags"] & flagsReservedMask) != 0 { - return nil, fmt.Errorf("flags reserved bits are set") - } - if (hdr["bd"] & bdReservedMask) != 0 { - return nil, fmt.Errorf("bd reserved bits are set") - } - - // Calculate CRC data boundaries (CRC covers descriptor excluding magic) - crcDataStart := magicSize - crcDataEnd := crcDataStart + (lz4StructSize - magicSize) - - if (hdr["flags"] & flagContentSizePresent) != 0 { - crcDataEnd += contentSizeLen - } - if (hdr["flags"] & flagDictionaryPresent) != 0 { - crcDataEnd += dictionaryLen - } - - if crcDataEnd >= len(lz4Data) { - return nil, fmt.Errorf("not enough data for header CRC") - } - - crcData := lz4Data[crcDataStart:crcDataEnd] - actualCRC := lz4Data[crcDataEnd] - - // LZ4 header CRC is the second byte of xxh32 hash over the descriptor - calculatedCRC := byte((xxh32(crcData, 0) >> 8) & 0xFF) - if actualCRC != calculatedCRC { - return nil, fmt.Errorf("header CRC mismatch: expected 0x%02X, got 0x%02X", - actualCRC, calculatedCRC) - } - - return &lz4FileHeaderInfo{ - headerSize: crcDataEnd + 1, - blockChecksumPresent: (hdr["flags"] & flagBlockChecksumPresent) != 0, - contentChecksumPresent: (hdr["flags"] & flagContentChecksumPresent) != 0, - }, nil -} - -func getLZ4DataSize(lz4Data []byte, checksumPresent bool) (int, error) { - dataSize := 0 - var lastOffset *int - available := len(lz4Data) - - for IsOffsetSafe(available, dataSize, lastOffset) { - if dataSize >= len(lz4Data) { - break - } - - blockHeader, err := parseLZ4BlockHeader(lz4Data[dataSize:], checksumPresent) - if err != nil { - break - } - - prev := dataSize - lastOffset = &prev - dataSize += blockHeader.headerSize + blockHeader.dataSize + blockHeader.checksumSize - - if blockHeader.lastBlock { - return dataSize, nil - } - } - - return 0, fmt.Errorf("failed to find LZ4 end marker") -} - -type lz4BlockHeaderInfo struct { - dataSize int - headerSize int - checksumSize int - lastBlock bool -} - -func parseLZ4BlockHeader(blockData []byte, checksumPresent bool) (*lz4BlockHeaderInfo, error) { - const sizeMask uint32 = 0x7FFFFFFF - const endMarker = 0 - const checksumSize = 4 - const blockStructSize = 4 - - structure := []StructField{{"block_size", "u32"}} - - hdr, err := Parse(blockData, structure, "little") - if err != nil { - return nil, fmt.Errorf("block header parse error: %w", err) - } - - info := &lz4BlockHeaderInfo{ - headerSize: blockStructSize, - lastBlock: hdr["block_size"] == endMarker, - } - - if checksumPresent { - info.checksumSize = checksumSize - } - - // High bit is not part of the actual block size - info.dataSize = int(uint32(hdr["block_size"]) & sizeMask) - - return info, nil -} - -// xxh32 implements the XXHash32 algorithm used by LZ4 for header checksums. -func xxh32(input []byte, seed uint32) uint32 { - const ( - prime1 uint32 = 0x9E3779B1 - prime2 uint32 = 0x85EBCA77 - prime3 uint32 = 0xC2B2AE3D - prime4 uint32 = 0x27D4EB2F - prime5 uint32 = 0x165667B1 - ) - - n := len(input) - p := 0 - var h32 uint32 - - if n >= 16 { - v1 := seed + prime1 + prime2 - v2 := seed + prime2 - v3 := seed - v4 := seed - prime1 - for p+16 <= n { - v1 = xxh32Round(v1, binary.LittleEndian.Uint32(input[p:]), prime1, prime2) - v2 = xxh32Round(v2, binary.LittleEndian.Uint32(input[p+4:]), prime1, prime2) - v3 = xxh32Round(v3, binary.LittleEndian.Uint32(input[p+8:]), prime1, prime2) - v4 = xxh32Round(v4, binary.LittleEndian.Uint32(input[p+12:]), prime1, prime2) - p += 16 - } - h32 = ((v1 << 1) | (v1 >> 31)) + - ((v2 << 7) | (v2 >> 25)) + - ((v3 << 12) | (v3 >> 20)) + - ((v4 << 18) | (v4 >> 14)) - } else { - h32 = seed + prime5 - } - - h32 += uint32(n) - - for p+4 <= n { - h32 += binary.LittleEndian.Uint32(input[p:]) * prime3 - h32 = ((h32 << 17) | (h32 >> 15)) * prime4 - p += 4 - } - - for p < n { - h32 += uint32(input[p]) * prime5 - h32 = ((h32 << 11) | (h32 >> 21)) * prime1 - p++ - } - - h32 ^= h32 >> 15 - h32 *= prime2 - h32 ^= h32 >> 13 - h32 *= prime3 - h32 ^= h32 >> 16 - - return h32 -} - -func xxh32Round(acc, input, prime1, prime2 uint32) uint32 { - acc += input * prime2 - acc = (acc << 13) | (acc >> 19) - acc *= prime1 - return acc -} - -// --------------------------------------------------------------------------- -// LZOP -// --------------------------------------------------------------------------- - -const LZOPDescription = "LZO compressed data" - -func LZOPMagic() [][]byte { - return [][]byte{ - {0x89, 0x4C, 0x5A, 0x4F, 0x00, 0x0D, 0x0A, 0x1A, 0x0A}, - } -} - -func LZOPParser(data []byte, offset int) (*SignatureResult, error) { - lzopHeader, err := parseLZOPFileHeader(data[offset:]) - if err != nil { - return nil, fmt.Errorf("LZOP: %w", err) - } - - if offset+lzopHeader.headerSize >= len(data) { - return nil, fmt.Errorf("LZOP: not enough data after header") - } - - lzopData := data[offset+lzopHeader.headerSize:] - dataSize, err := getLZODataSize(lzopData, lzopHeader.blockChecksumPresent) - if err != nil { - return nil, fmt.Errorf("LZOP: %w", err) - } - - totalSize := lzopHeader.headerSize + dataSize - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, total size: %d bytes", LZOPDescription, totalSize), - Confidence: ConfidenceHigh, - Size: totalSize, - }, nil -} - -type lzopFileHeaderInfo struct { - headerSize int - blockChecksumPresent bool -} - -func parseLZOPFileHeader(lzopData []byte) (*lzopFileHeaderInfo, error) { - const lzoMaxVersion = 0x1040 - const lzoHeaderSizeP1 = 21 - const lzoHeaderSizeP2 = 13 - const filterSize = 4 - const lzoChecksumSize = 4 - - const flagFilter = 0x0000_0800 - const flagCRC32C = 0x0000_0200 - const flagAdler32C = 0x0000_0002 - - structureP1 := []StructField{ - {"magic_p1", "u8"}, - {"magic_p2", "u64"}, - {"version", "u16"}, - {"lib_version", "u16"}, - {"version_needed", "u16"}, - {"method", "u8"}, - {"level", "u8"}, - {"flags", "u32"}, - } - - structureP2 := []StructField{ - {"mode", "u32"}, - {"mtime", "u32"}, - {"gmt_diff", "u32"}, - {"file_name_length", "u8"}, - } - - allowedMethods := []int{1, 2, 3} - - hdrP1, err := Parse(lzopData, structureP1, "big") - if err != nil { - return nil, fmt.Errorf("header part 1 parse error: %w", err) - } - - // Sanity check methods - methodValid := false - for _, m := range allowedMethods { - if hdrP1["method"] == m { - methodValid = true - break - } - } - if !methodValid { - return nil, fmt.Errorf("invalid method: %d", hdrP1["method"]) - } - - // Sanity check version numbers - if hdrP1["version"] > lzoMaxVersion { - return nil, fmt.Errorf("version too high: 0x%04X", hdrP1["version"]) - } - if hdrP1["version"] < hdrP1["version_needed"] { - return nil, fmt.Errorf("version %d < version_needed %d", hdrP1["version"], hdrP1["version_needed"]) - } - - headerP2Start := lzoHeaderSizeP1 - if (hdrP1["flags"] & flagFilter) != 0 { - headerP2Start += filterSize - } - - headerP2End := headerP2Start + lzoHeaderSizeP2 - if headerP2End > len(lzopData) { - return nil, fmt.Errorf("not enough data for header part 2") - } - - hdrP2, err := Parse(lzopData[headerP2Start:headerP2End], structureP2, "big") - if err != nil { - return nil, fmt.Errorf("header part 2 parse error: %w", err) - } - - headerSize := headerP2End + hdrP2["file_name_length"] + lzoChecksumSize - - // Faithfully ported: in the Rust source this is & (AND) which always yields false - blockChecksumPresent := (hdrP1["flags"] & flagAdler32C & flagCRC32C) != 0 - - if headerSize > len(lzopData) { - return nil, fmt.Errorf("calculated header size exceeds available data") - } - - return &lzopFileHeaderInfo{ - headerSize: headerSize, - blockChecksumPresent: blockChecksumPresent, - }, nil -} - -type lzopBlockHeaderInfo struct { - headerSize int - compressedSize int - uncompressedSize int - checksumSize int -} - -func parseLZOPBlockHeader(lzoData []byte, compressedChecksumPresent bool) (*lzopBlockHeaderInfo, error) { - const blockHeaderSize = 12 - const maxUncompressedBlockSize = 64 * 1024 * 1024 - const lzoChecksumSize = 4 - - structure := []StructField{ - {"uncompressed_size", "u32"}, - {"compressed_size", "u32"}, - {"uncompressed_checksum", "u32"}, - } - - hdr, err := Parse(lzoData, structure, "big") - if err != nil { - return nil, fmt.Errorf("block header parse error: %w", err) - } - - if hdr["compressed_size"] == 0 || - hdr["uncompressed_size"] == 0 || - hdr["uncompressed_checksum"] == 0 || - hdr["uncompressed_size"] > maxUncompressedBlockSize { - return nil, fmt.Errorf("invalid block header values") - } - - info := &lzopBlockHeaderInfo{ - headerSize: blockHeaderSize, - compressedSize: hdr["compressed_size"], - uncompressedSize: hdr["uncompressed_size"], - } - - if compressedChecksumPresent { - info.checksumSize = lzoChecksumSize - } - - return info, nil -} - -func parseLZOPEOFMarker(eofData []byte) (int, error) { - const eofMarkerValue = 0 - const eofMarkerSize = 4 - - structure := []StructField{{"marker", "u32"}} - - hdr, err := Parse(eofData, structure, "big") - if err != nil { - return 0, fmt.Errorf("EOF marker parse error: %w", err) - } - - if hdr["marker"] != eofMarkerValue { - return 0, fmt.Errorf("invalid EOF marker: %d", hdr["marker"]) - } - - return eofMarkerSize, nil -} - -func getLZODataSize(lzoData []byte, compressedChecksumPresent bool) (int, error) { - const minBlockCount = 2 - - available := len(lzoData) - var lastOffset *int - dataSize := 0 - blockCount := 0 - - for IsOffsetSafe(available, dataSize, lastOffset) { - blockHeader, err := parseLZOPBlockHeader(lzoData[dataSize:], compressedChecksumPresent) - if err != nil { - break - } - - blockCount++ - prev := dataSize - lastOffset = &prev - dataSize += blockHeader.headerSize + blockHeader.compressedSize + blockHeader.checksumSize - } - - if blockCount >= minBlockCount { - if dataSize < len(lzoData) { - eofSize, err := parseLZOPEOFMarker(lzoData[dataSize:]) - if err != nil { - return 0, fmt.Errorf("EOF marker error: %w", err) - } - dataSize += eofSize - return dataSize, nil - } - } - - return 0, fmt.Errorf("failed to validate LZO block structure") -} - -// --------------------------------------------------------------------------- -// LZFSE -// --------------------------------------------------------------------------- - -const LZFSEDescription = "LZFSE compressed data" - -func LZFSEMagic() [][]byte { - return [][]byte{ - []byte("bvx-"), - []byte("bvx1"), - []byte("bvx2"), - []byte("bvxn"), - } -} - -func LZFSEParser(data []byte, offset int) (*SignatureResult, error) { - available := len(data) - nextBlockOffset := offset - var previousBlockOffset *int - - for IsOffsetSafe(available, nextBlockOffset, previousBlockOffset) { - prev := nextBlockOffset - previousBlockOffset = &prev - - block, err := parseLZFSEBlockHeader(data[nextBlockOffset:]) - if err != nil { - break - } - - nextBlockOffset += block.headerSize + block.dataSize - - if block.eof { - totalSize := nextBlockOffset - offset - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, total size: %d bytes", LZFSEDescription, totalSize), - Confidence: ConfidenceHigh, - Size: totalSize, - }, nil - } - } - - return nil, fmt.Errorf("LZFSE: failed to find end-of-stream block") -} - -type lzfseBlockInfo struct { - eof bool - dataSize int - headerSize int -} - -func parseLZFSEBlockHeader(lzfseData []byte) (*lzfseBlockInfo, error) { - // LZFSE block types (little-endian u32 representations) - const endOfStream = 0x24787662 // "bvx$" - const uncompressed = 0x2D787662 // "bvx-" - const compressedV1 = 0x31787662 // "bvx1" - const compressedV2 = 0x32787662 // "bvx2" - const compressedLZVN = 0x6E787662 // "bvxn" - - blockTypeStructure := []StructField{{"block_type", "u32"}} - - hdr, err := Parse(lzfseData, blockTypeStructure, "little") - if err != nil { - return nil, fmt.Errorf("block header parse error: %w", err) - } - - blockType := hdr["block_type"] - - switch blockType { - case endOfStream: - return &lzfseBlockInfo{eof: true, dataSize: 0, headerSize: 4}, nil - - case uncompressed: - return parseLZFSEUncompressedBlock(lzfseData) - - case compressedV1: - return parseLZFSECompressedV1Block(lzfseData) - - case compressedV2: - return parseLZFSECompressedV2Block(lzfseData) - - case compressedLZVN: - return parseLZFSECompressedLZVNBlock(lzfseData) - } - - return nil, fmt.Errorf("unknown LZFSE block type: 0x%08X", blockType) -} - -func parseLZFSEUncompressedBlock(lzfseData []byte) (*lzfseBlockInfo, error) { - const headerSize = 8 - - structure := []StructField{ - {"magic", "u32"}, - {"n_raw_bytes", "u32"}, - } - - hdr, err := Parse(lzfseData, structure, "little") - if err != nil { - return nil, fmt.Errorf("uncompressed block parse error: %w", err) - } - - return &lzfseBlockInfo{ - eof: false, - dataSize: hdr["n_raw_bytes"], - headerSize: headerSize, - }, nil -} - -func parseLZFSECompressedV1Block(lzfseData []byte) (*lzfseBlockInfo, error) { - const headerSize = 770 - - structure := []StructField{ - {"magic", "u32"}, - {"n_raw_bytes", "u32"}, - {"n_payload_bytes", "u32"}, - {"n_literals", "u32"}, - {"n_matches", "u32"}, - {"n_literal_payload_bytes", "u32"}, - {"n_lmd_payload_bytes", "u32"}, - {"literal_bits", "u32"}, - {"literal_state", "u64"}, - {"lmd_bits", "u32"}, - {"l_state", "u16"}, - {"m_state", "u16"}, - {"d_state", "u16"}, - } - - hdr, err := Parse(lzfseData, structure, "little") - if err != nil { - return nil, fmt.Errorf("compressed v1 block parse error: %w", err) - } - - return &lzfseBlockInfo{ - eof: false, - dataSize: hdr["n_literal_payload_bytes"] + hdr["n_lmd_payload_bytes"], - headerSize: headerSize, - }, nil -} - -func parseLZFSECompressedV2Block(lzfseData []byte) (*lzfseBlockInfo, error) { - const nPayloadShift = 20 - const lmdPayloadShift = 40 - const payloadMask = 0b11111_11111_11111_11111 - - structure := []StructField{ - {"magic", "u32"}, - {"uncompressed_size", "u32"}, - {"packed_field_1", "u64"}, - {"packed_field_2", "u64"}, - {"header_size", "u32"}, - {"state_fields", "u32"}, - } - - hdr, err := Parse(lzfseData, structure, "little") - if err != nil { - return nil, fmt.Errorf("compressed v2 block parse error: %w", err) - } - - nLMDPayloadBytes := (hdr["packed_field_2"] >> lmdPayloadShift) & payloadMask - nLiteralPayloadBytes := (hdr["packed_field_1"] >> nPayloadShift) & payloadMask - - return &lzfseBlockInfo{ - eof: false, - dataSize: nLMDPayloadBytes + nLiteralPayloadBytes, - headerSize: hdr["header_size"], - }, nil -} - -func parseLZFSECompressedLZVNBlock(lzfseData []byte) (*lzfseBlockInfo, error) { - const headerSize = 12 - - structure := []StructField{ - {"magic", "u32"}, - {"n_raw_bytes", "u32"}, - {"n_payload_bytes", "u32"}, - } - - hdr, err := Parse(lzfseData, structure, "little") - if err != nil { - return nil, fmt.Errorf("compressed LZVN block parse error: %w", err) - } - - return &lzfseBlockInfo{ - eof: false, - dataSize: hdr["n_payload_bytes"], - headerSize: headerSize, - }, nil -} - -// --------------------------------------------------------------------------- -// Zlib -// --------------------------------------------------------------------------- - -const ZlibDescription = "Zlib compressed file" - -func ZlibMagic() [][]byte { - return [][]byte{ - {0x78, 0x9C}, - {0x78, 0xDA}, - {0x78, 0x5E}, - } -} - -func ZlibParser(data []byte, offset int) (*SignatureResult, error) { - if offset+2 > len(data) { - return nil, fmt.Errorf("zlib: not enough data") - } - - // Zlib header: CMF (byte 0) and FLG (byte 1) - // The two-byte value (CMF*256 + FLG) must be a multiple of 31 (FCHECK) - cmf := int(data[offset]) - flg := int(data[offset+1]) - if (cmf*256+flg)%31 != 0 { - return nil, fmt.Errorf("zlib: FCHECK validation failed") - } - - // CM (compression method) should be 8 (deflate) - cm := cmf & 0x0F - if cm != 8 { - return nil, fmt.Errorf("zlib: invalid compression method: %d", cm) - } - - // CINFO should be <= 7 - cinfo := (cmf >> 4) & 0x0F - if cinfo > 7 { - return nil, fmt.Errorf("zlib: invalid CINFO: %d", cinfo) - } - - return &SignatureResult{ - Offset: offset, - Description: ZlibDescription, - Confidence: ConfidenceHigh, - }, nil -} - -// --------------------------------------------------------------------------- -// compress'd -// --------------------------------------------------------------------------- - -const CompressdDescription = "compress'd data" - -func CompressdMagic() [][]byte { - return [][]byte{ - {0x1F, 0x9D, 0x90}, - } -} - -func CompressdParser(data []byte, offset int) (*SignatureResult, error) { - result := &SignatureResult{ - Offset: offset, - Description: CompressdDescription, - Confidence: ConfidenceLow, - } - - // Higher confidence when found at the beginning of a file - if offset == 0 { - result.Confidence = ConfidenceMedium - } - - return result, nil -} diff --git a/internal/leviathan/binsnoop/sig_crypto.go b/internal/leviathan/binsnoop/sig_crypto.go deleted file mode 100644 index f1424a1..0000000 --- a/internal/leviathan/binsnoop/sig_crypto.go +++ /dev/null @@ -1,795 +0,0 @@ -package binsnoop - -import ( - "encoding/base64" - "fmt" - "strings" -) - -// ---- AES descriptions ---- - -const ( - DescriptionAESSBox = "AES S-Box" - DescriptionAESFT = "AES Forward Table" - DescriptionAESRT = "AES Reverse Table" - DescriptionAESRCON = "AES RCON" - DescriptionAESAcc = "AES Acceleration Table" -) - -// aesSBoxMagic returns magic bytes for AES S-Box (forward and reverse). -func aesSBoxMagic() [][]byte { - return [][]byte{ - {0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5}, // Forward S-Box - {0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38}, // Reverse S-Box - } -} - -// aesSBoxParser validates AES S-Box signatures. -func aesSBoxParser(data []byte, offset int) (*SignatureResult, error) { - return &SignatureResult{ - Offset: offset, - Description: DescriptionAESSBox, - Confidence: ConfidenceLow, - }, nil -} - -// aesForwardTableMagic returns magic bytes for AES Forward Table. -func aesForwardTableMagic() [][]byte { - return [][]byte{ - {0xC6, 0x63, 0x63, 0xA5, 0xF8, 0x7C, 0x7C, 0x84, 0xEE, 0x77, 0x77, 0x99, 0xF6, 0x7B, 0x7B, 0x8D}, - } -} - -// aesForwardTableParser validates AES Forward Table signatures. -func aesForwardTableParser(data []byte, offset int) (*SignatureResult, error) { - return &SignatureResult{ - Offset: offset, - Description: DescriptionAESFT, - Confidence: ConfidenceLow, - }, nil -} - -// aesReverseTableMagic returns magic bytes for AES Reverse Table. -func aesReverseTableMagic() [][]byte { - return [][]byte{ - {0x51, 0xF4, 0xA7, 0x50, 0x7E, 0x41, 0x65, 0x53, 0x1A, 0x17, 0xA4, 0xC3, 0x3A, 0x27, 0x5E, 0x96}, - } -} - -// aesReverseTableParser validates AES Reverse Table signatures. -func aesReverseTableParser(data []byte, offset int) (*SignatureResult, error) { - return &SignatureResult{ - Offset: offset, - Description: DescriptionAESRT, - Confidence: ConfidenceLow, - }, nil -} - -// aesRconMagic returns magic bytes for AES RCON. -func aesRconMagic() [][]byte { - return [][]byte{ - {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36}, - {0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, - 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, - 0x1B, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00}, - } -} - -// aesRconParser validates AES RCON signatures. -func aesRconParser(data []byte, offset int) (*SignatureResult, error) { - return &SignatureResult{ - Offset: offset, - Description: DescriptionAESRCON, - Confidence: ConfidenceLow, - }, nil -} - -// aesAccelerationTableMagic returns magic bytes for AES Acceleration Table. -func aesAccelerationTableMagic() [][]byte { - return [][]byte{ - // combined sbox x2 - {0xA5, 0x84, 0x99, 0x8D, 0x0D, 0xBD, 0xB1, 0x54, 0x50, 0x03, 0xA9, 0x7D, 0x19, 0x62, 0xE6, 0x9A}, - // combined sbox x3 - {0xC6, 0xF8, 0xEE, 0xF6, 0xFF, 0xD6, 0xDE, 0x91, 0x60, 0x02, 0xCE, 0x56, 0xE7, 0xB5, 0x4D, 0xEC}, - // Galois mult 2 - {0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, - 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e}, - // Galois mult 3 - {0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, - 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39}, - // Galois mult 9 - {0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, - 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf}, - // Galois mult 11 - {0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, - 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81}, - // Galois mult 13 - {0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, - 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3}, - // Galois mult 14 - {0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, - 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca}, - } -} - -// aesAccelerationTableParser validates AES Acceleration Table signatures. -func aesAccelerationTableParser(data []byte, offset int) (*SignatureResult, error) { - return &SignatureResult{ - Offset: offset, - Description: DescriptionAESAcc, - Confidence: ConfidenceLow, - }, nil -} - -// ---- RSA ---- - -const DescriptionRSA = "RSA encrypted session key" - -type rsaKeyDefinition struct { - magic []byte - keySize int - usageOffset int - keyIDOffset int - validChecks map[int][][]byte - terminatorOffset int -} - -func rsaKeyDefinitions() []rsaKeyDefinition { - return []rsaKeyDefinition{ - // 1024b RSA key - { - magic: []byte{0x84, 0x8C, 0x03}, - keySize: 1024, - keyIDOffset: 3, - terminatorOffset: 142, - usageOffset: 11, - validChecks: map[int][][]byte{ - 12: { - {0x04, 0x00}, {0x03, 0xff}, {0x03, 0xfe}, {0x03, 0xfd}, - {0x03, 0xfc}, {0x03, 0xfb}, {0x03, 0xfa}, {0x03, 0xf9}, - }, - }, - }, - // 2048b RSA key - { - magic: []byte{0x85, 0x01, 0x0c, 0x03}, - keySize: 2048, - keyIDOffset: 4, - terminatorOffset: 271, - usageOffset: 12, - validChecks: map[int][][]byte{ - 13: { - {0x08, 0x00}, {0x07, 0xff}, {0x07, 0xfe}, {0x07, 0xfd}, - {0x07, 0xfc}, {0x07, 0xfb}, {0x07, 0xfa}, {0x07, 0xf9}, - }, - }, - }, - // 3072b RSA key - { - magic: []byte{0x85, 0x01, 0x8c, 0x03}, - keySize: 3072, - keyIDOffset: 4, - terminatorOffset: 399, - usageOffset: 12, - validChecks: map[int][][]byte{ - 13: { - {0x0c, 0x00}, {0x0b, 0xff}, {0x0b, 0xfe}, {0x0b, 0xfd}, - {0x0b, 0xfc}, {0x0b, 0xfb}, {0x0b, 0xfa}, {0x0b, 0xf9}, - }, - }, - }, - // 4096b RSA key - { - magic: []byte{0x85, 0x02, 0x0c, 0x03}, - keySize: 4096, - keyIDOffset: 4, - terminatorOffset: 527, - usageOffset: 12, - validChecks: map[int][][]byte{ - 13: { - {0x10, 0x00}, {0x0f, 0xff}, {0x0f, 0xfe}, {0x0f, 0xfd}, - {0x0f, 0xfc}, {0x0f, 0xfb}, {0x0f, 0xfa}, {0x0f, 0xf9}, - }, - }, - }, - // 8192b RSA key - { - magic: []byte{0x85, 0x04, 0x0c, 0x03}, - keySize: 8192, - keyIDOffset: 4, - terminatorOffset: 1039, - usageOffset: 12, - validChecks: map[int][][]byte{ - 13: { - {0x20, 0x00}, {0x1f, 0xff}, {0x1f, 0xfe}, {0x1f, 0xfd}, - {0x1f, 0xfc}, {0x1f, 0xfb}, {0x1f, 0xfa}, {0x1f, 0xf9}, - }, - }, - }, - } -} - -// rsaMagic returns magic bytes for all known RSA key sizes. -func rsaMagic() [][]byte { - var magics [][]byte - for _, def := range rsaKeyDefinitions() { - m := make([]byte, len(def.magic)) - copy(m, def.magic) - magics = append(magics, m) - } - return magics -} - -type rsaKeyInfo struct { - bits int - canSign bool - canEncrypt bool - keyID int - dataSize int -} - -func rsaKeyParse(rawData []byte, def *rsaKeyDefinition) (*rsaKeyInfo, error) { - const terminatorByte byte = 0xD2 - const encryptOnly byte = 2 - const signAndEncrypt byte = 1 - const validBytesSize = 2 - - keyIDFields := []StructField{{"id", "u64"}} - - keyDataLen := def.terminatorOffset + 1 - if len(rawData) < keyDataLen { - return nil, fmt.Errorf("insufficient RSA key data") - } - keyData := rawData[:keyDataLen] - - if keyData[def.terminatorOffset] != terminatorByte { - return nil, fmt.Errorf("invalid RSA terminator byte") - } - - if def.keyIDOffset+8 > len(keyData) { - return nil, fmt.Errorf("key ID offset out of bounds") - } - keyIDParsed, err := Parse(keyData[def.keyIDOffset:], keyIDFields, "big") - if err != nil { - return nil, fmt.Errorf("failed to parse RSA key ID") - } - - info := &rsaKeyInfo{keyID: keyIDParsed["id"]} - info.canEncrypt = keyData[def.usageOffset] == encryptOnly - info.canSign = keyData[def.usageOffset] == signAndEncrypt - if info.canSign { - info.canEncrypt = true - } - - if !info.canSign && !info.canEncrypt { - return nil, fmt.Errorf("RSA key cannot sign or encrypt") - } - - for checkOffset, expectedList := range def.validChecks { - if checkOffset+validBytesSize > len(keyData) { - continue - } - actual := keyData[checkOffset : checkOffset+validBytesSize] - for _, expected := range expectedList { - if len(expected) == validBytesSize && actual[0] == expected[0] && actual[1] == expected[1] { - info.bits = def.keySize - info.dataSize = keyDataLen - return info, nil - } - } - } - - return nil, fmt.Errorf("RSA key validation bytes mismatch") -} - -// rsaParser validates RSA encrypted session key signatures. -func rsaParser(data []byte, offset int) (*SignatureResult, error) { - result := &SignatureResult{ - Offset: offset, - Description: DescriptionRSA, - Confidence: ConfidenceMedium, - } - - for _, def := range rsaKeyDefinitions() { - magicEnd := offset + len(def.magic) - if magicEnd > len(data) { - continue - } - match := true - for i, b := range def.magic { - if data[offset+i] != b { - match = false - break - } - } - if !match { - continue - } - - info, err := rsaKeyParse(data[offset:], &def) - if err != nil { - break - } - result.Size = info.dataSize - result.Description = fmt.Sprintf("%s, %d bits, can sign: %t, can encrypt: %t, key ID: %#018X", - result.Description, info.bits, info.canSign, info.canEncrypt, info.keyID) - return result, nil - } - - return nil, fmt.Errorf("invalid RSA signature") -} - -// ---- OpenSSL ---- - -const DescriptionOpenSSL = "OpenSSL encryption" - -// opensslMagic returns magic bytes for OpenSSL encrypted files. -func opensslMagic() [][]byte { - return [][]byte{ - []byte("Salted__"), - } -} - -func isSaltInvalid(salt int) bool { - const saltLen = 8 - badByteCount := 0 - for i := 0; i < saltLen; i++ { - saltByte := byte((salt >> (8 * i)) & 0xFF) - if saltByte == 0 || IsPrintableASCII(saltByte) { - badByteCount++ - } - } - return badByteCount == saltLen -} - -// opensslParser validates OpenSSL encryption signatures. -func opensslParser(data []byte, offset int) (*SignatureResult, error) { - sslFields := []StructField{{"magic", "u64"}, {"salt", "u64"}} - - result := &SignatureResult{ - Offset: offset, - Description: DescriptionOpenSSL, - Confidence: ConfidenceLow, - } - - if offset+StructSize(sslFields) > len(data) { - return nil, fmt.Errorf("insufficient OpenSSL data") - } - - sslHeader, err := Parse(data[offset:], sslFields, "big") - if err != nil { - return nil, fmt.Errorf("failed to parse OpenSSL header") - } - - salt := sslHeader["salt"] - if salt == 0 { - return nil, fmt.Errorf("OpenSSL salt is zero") - } - - if isSaltInvalid(salt) { - return nil, fmt.Errorf("OpenSSL salt is invalid") - } - - if offset == 0 { - result.Confidence = ConfidenceMedium - } - - result.Description = fmt.Sprintf("%s, salt: %#X", result.Description, salt) - return result, nil -} - -// ---- GPG ---- - -const DescriptionGPGSigned = "GPG signed file" - -// gpgSignedMagic returns magic bytes for GPG signed files. -func gpgSignedMagic() [][]byte { - return [][]byte{ - {0xA3, 0x01}, - } -} - -// gpgSignedParser validates GPG signed file signatures. -// The Rust version does a zlib decompression dry run; here we do a simplified validation. -func gpgSignedParser(data []byte, offset int) (*SignatureResult, error) { - result := &SignatureResult{ - Offset: offset, - Confidence: ConfidenceHigh, - Description: DescriptionGPGSigned, - } - - // GPG signed files are zlib compressed with magic bytes replaced. - // Validate there is enough data and the zlib-like stream follows. - if offset+4 > len(data) { - return nil, fmt.Errorf("insufficient GPG data") - } - - // The third byte should be a valid zlib CMF byte (deflate method = 8) - cmf := data[offset+2] - if cmf&0x0F != 8 { - return nil, fmt.Errorf("GPG: invalid deflate method in underlying zlib stream") - } - - result.Description = fmt.Sprintf("%s, compressed data at offset %d", result.Description, offset) - return result, nil -} - -// ---- PEM ---- - -const ( - DescriptionPEMCertificate = "PEM certificate" - DescriptionPEMPublicKey = "PEM public key" - DescriptionPEMPrivateKey = "PEM private key" -) - -// pemCertificateMagic returns magic bytes for PEM certificates. -func pemCertificateMagic() [][]byte { - return [][]byte{ - []byte("-----BEGIN CERTIFICATE-----"), - } -} - -// pemPublicKeyMagic returns magic bytes for PEM public keys. -func pemPublicKeyMagic() [][]byte { - return [][]byte{ - []byte("-----BEGIN PUBLIC KEY-----"), - []byte("-----BEGIN RSA PUBLIC KEY-----"), - []byte("-----BEGIN DSA PUBLIC KEY-----"), - []byte("-----BEGIN ECDSA PUBLIC KEY-----"), - } -} - -// pemPrivateKeyMagic returns magic bytes for PEM private keys. -func pemPrivateKeyMagic() [][]byte { - return [][]byte{ - []byte("-----BEGIN PRIVATE KEY-----"), - []byte("-----BEGIN EC PRIVATE KEY-----"), - []byte("-----BEGIN RSA PRIVATE KEY-----"), - []byte("-----BEGIN DSA PRIVATE KEY-----"), - []byte("-----BEGIN OPENSSH PRIVATE KEY-----"), - []byte("-----BEGIN ANY PRIVATE KEY-----"), - []byte("-----BEGIN ENCRYPTED PRIVATE KEY-----"), - []byte("-----BEGIN TSS2 PRIVATE KEY-----"), - } -} - -func decodePEMData(pemData []byte) (int, error) { - const delim = "--" - - pemStr := string(pemData) - delimCount := 0 - var base64Str strings.Builder - - for _, line := range strings.Split(pemStr, "\n") { - line = strings.TrimRight(line, "\r") - if strings.HasPrefix(line, delim) { - delimCount++ - if delimCount == 2 { - break - } - continue - } - base64Str.WriteString(line) - } - - if base64Str.Len() == 0 { - return 0, fmt.Errorf("no PEM base64 data found") - } - - decoded, err := base64.StdEncoding.DecodeString(base64Str.String()) - if err != nil { - return 0, fmt.Errorf("PEM base64 decode failed: %v", err) - } - - return len(decoded), nil -} - -// pemFindEnd locates the end delimiter and returns the total PEM size. -func pemFindEnd(data []byte, offset int) (int, bool) { - const endPrefix = "-----END " - pemData := string(data[offset:]) - idx := strings.Index(pemData, endPrefix) - if idx < 0 { - return 0, false - } - // Find end of the delimiter line - rest := pemData[idx:] - nlIdx := strings.Index(rest, "\n") - if nlIdx < 0 { - return idx + len(rest), true - } - return idx + nlIdx + 1, true -} - -// pemParser validates PEM certificate and key signatures. -func pemParser(data []byte, offset int) (*SignatureResult, error) { - const minPEMLen = 26 - - result := &SignatureResult{ - Offset: offset, - Confidence: ConfidenceHigh, - } - - if offset+minPEMLen > len(data) { - return nil, fmt.Errorf("insufficient PEM data") - } - - pemMagic := data[offset : offset+minPEMLen] - pemMagicStr := string(pemMagic) - - // Determine type based on first 26 bytes - matched := false - for _, m := range pemPublicKeyMagic() { - if len(m) >= minPEMLen && strings.HasPrefix(string(m), pemMagicStr[:minPEMLen]) { - result.Description = DescriptionPEMPublicKey - matched = true - break - } - } - if !matched { - for _, m := range pemPrivateKeyMagic() { - if len(m) >= minPEMLen && strings.HasPrefix(string(m), pemMagicStr[:minPEMLen]) { - result.Description = DescriptionPEMPrivateKey - matched = true - break - } - } - } - if !matched { - for _, m := range pemCertificateMagic() { - if len(m) >= minPEMLen && strings.HasPrefix(string(m), pemMagicStr[:minPEMLen]) { - result.Description = DescriptionPEMCertificate - matched = true - break - } - } - } - if !matched { - return nil, fmt.Errorf("PEM magic not recognised") - } - - // Find the end of PEM data - pemSize, found := pemFindEnd(data, offset) - if !found || pemSize == 0 { - return nil, fmt.Errorf("PEM end delimiter not found") - } - - // Validate base64 content - if offset+pemSize > len(data) { - pemSize = len(data) - offset - } - if _, err := decodePEMData(data[offset : offset+pemSize]); err != nil { - return nil, fmt.Errorf("PEM validation failed: %v", err) - } - - if offset == 0 && pemSize == len(data) { - result.ExtractionDeclined = true - } - - result.Size = pemSize - return result, nil -} - -// ---- PKCS DER ---- - -const DescriptionDERHash = "PKCS DER hash" - -func derHashLookups() map[string][]byte { - return map[string][]byte{ - "MD5": {0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10}, - "SHA1": {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14}, - "SHA256": {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}, - "SHA384": {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30}, - "SHA512": {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04}, - } -} - -// derHashMagic returns magic bytes for all DER hash types. -func derHashMagic() [][]byte { - var magics [][]byte - for _, m := range derHashLookups() { - mc := make([]byte, len(m)) - copy(mc, m) - magics = append(magics, mc) - } - return magics -} - -// derHashParser validates PKCS DER hash signatures. -func derHashParser(data []byte, offset int) (*SignatureResult, error) { - result := &SignatureResult{ - Offset: offset, - Description: DescriptionDERHash, - Confidence: ConfidenceMedium, - } - - for name, magic := range derHashLookups() { - hashEnd := offset + len(magic) - if hashEnd > len(data) { - continue - } - match := true - for i, b := range magic { - if data[offset+i] != b { - match = false - break - } - } - if match { - result.Size = len(magic) - result.Description = fmt.Sprintf("%s, %s", result.Description, name) - return result, nil - } - } - - return nil, fmt.Errorf("DER hash validation failed") -} - -// ---- LUKS ---- - -const DescriptionLUKS = "LUKS header" - -// luksMagic returns magic bytes for LUKS headers. -func luksMagic() [][]byte { - return [][]byte{ - {0x4C, 0x55, 0x4B, 0x53, 0xBA, 0xBE}, // "LUKS\xBA\xBE" - } -} - -// luksParser validates LUKS header signatures. -func luksParser(data []byte, offset int) (*SignatureResult, error) { - const cipherAlgoStart = 8 - const cipherAlgoEnd = 40 - const cipherModeStart = 40 - const cipherModeEnd = 72 - const hashFnStart = 72 - const hashFnEnd = 104 - const luks2MinHeaderSize = 4032 - - luksBaseFields := []StructField{ - {"magic_1", "u32"}, - {"magic_2", "u16"}, - {"version", "u16"}, - {"header_size", "u64"}, - } - - result := &SignatureResult{ - Offset: offset, - Name: "luks", - Description: DescriptionLUKS, - Confidence: ConfidenceMedium, - } - - if offset+StructSize(luksBaseFields) > len(data) { - return nil, fmt.Errorf("insufficient LUKS data") - } - - luksBase, err := Parse(data[offset:], luksBaseFields, "big") - if err != nil { - return nil, fmt.Errorf("failed to parse LUKS header") - } - - version := luksBase["version"] - - // Hash function string - if offset+hashFnEnd > len(data) { - return nil, fmt.Errorf("LUKS hash function data out of bounds") - } - hashFn := GetCString(data[offset+hashFnStart : offset+hashFnEnd]) - if hashFn == "" { - return nil, fmt.Errorf("LUKS hash function string empty") - } - - if version == 1 { - if offset+cipherAlgoEnd > len(data) { - return nil, fmt.Errorf("LUKS cipher data out of bounds") - } - cipherAlgo := GetCString(data[offset+cipherAlgoStart : offset+cipherAlgoEnd]) - cipherMode := GetCString(data[offset+cipherModeStart : offset+cipherModeEnd]) - if cipherAlgo == "" || cipherMode == "" { - return nil, fmt.Errorf("LUKS v1 cipher strings empty") - } - result.Description = fmt.Sprintf("%s, version: %d, cipher algorithm: %s, cipher mode: %s, hash fn: %s", - result.Description, version, cipherAlgo, cipherMode, hashFn) - return result, nil - } else if version == 2 { - headerSize := luksBase["header_size"] - availableData := len(data) - offset - if headerSize <= luks2MinHeaderSize || headerSize >= availableData { - return nil, fmt.Errorf("LUKS v2 header size invalid") - } - result.Description = fmt.Sprintf("%s, version: %d, header size: %d bytes, hash fn: %s", - result.Description, version, headerSize, hashFn) - return result, nil - } - - return nil, fmt.Errorf("unsupported LUKS version: %d", version) -} - -// ---- Hash constants ---- - -const hashMagicLen = 16 - -const ( - DescriptionCRC32 = "CRC32 polynomial table" - DescriptionSHA256 = "SHA256 hash constants" - DescriptionMD5 = "MD5 hash constants" -) - -// crc32Magic returns magic bytes for CRC32 polynomial tables. -// Order matters: index 0 is big endian, index 1 is little endian. -func crc32ConstMagic() [][]byte { - return [][]byte{ - // Big endian - {0x00, 0x00, 0x00, 0x00, 0x77, 0x07, 0x30, 0x96, 0xEE, 0x0E, 0x61, 0x2C, 0x99, 0x09, 0x51, 0xBA}, - // Little endian - {0x00, 0x00, 0x00, 0x00, 0x96, 0x30, 0x07, 0x77, 0x2C, 0x61, 0x0E, 0xEE, 0xBA, 0x51, 0x09, 0x99}, - } -} - -// sha256Magic returns magic bytes for SHA256 hash constants. -func sha256ConstMagic() [][]byte { - return [][]byte{ - // Big endian - {0x42, 0x8a, 0x2f, 0x98, 0x71, 0x37, 0x44, 0x91, 0xb5, 0xc0, 0xfb, 0xcf, 0xe9, 0xb5, 0xdb, 0xa5}, - // Little endian - {0x98, 0x2f, 0x8a, 0x42, 0x91, 0x44, 0x37, 0x71, 0xcf, 0xfb, 0xc0, 0xb5, 0xa5, 0xdb, 0xb5, 0xe9}, - } -} - -// md5Magic returns magic bytes for MD5 hash constants. -func md5ConstMagic() [][]byte { - return [][]byte{ - // Big endian - {0xd7, 0x6a, 0xa4, 0x78, 0xe8, 0xc7, 0xb7, 0x56, 0x24, 0x20, 0x70, 0xdb, 0xc1, 0xbd, 0xce, 0xee}, - // Little endian - {0x78, 0xa4, 0x6a, 0xd7, 0x56, 0xb7, 0xc7, 0xe8, 0xdb, 0x70, 0x20, 0x24, 0xee, 0xce, 0xbd, 0xc1}, - } -} - -func hashEndianness(data []byte, offset int, magics [][]byte) string { - if offset+hashMagicLen > len(data) { - return "little" - } - thisMagic := data[offset : offset+hashMagicLen] - if len(magics) > 0 && len(magics[0]) == hashMagicLen { - match := true - for i := 0; i < hashMagicLen; i++ { - if thisMagic[i] != magics[0][i] { - match = false - break - } - } - if match { - return "big" - } - } - return "little" -} - -// crc32Parser validates CRC32 polynomial table signatures. -func crc32Parser(data []byte, offset int) (*SignatureResult, error) { - return &SignatureResult{ - Offset: offset, - Size: hashMagicLen, - Description: fmt.Sprintf("%s, %s endian", DescriptionCRC32, hashEndianness(data, offset, crc32ConstMagic())), - }, nil -} - -// sha256Parser validates SHA256 hash constant signatures. -func sha256Parser(data []byte, offset int) (*SignatureResult, error) { - return &SignatureResult{ - Offset: offset, - Size: hashMagicLen, - Description: fmt.Sprintf("%s, %s endian", DescriptionSHA256, hashEndianness(data, offset, sha256ConstMagic())), - }, nil -} - -// md5Parser validates MD5 hash constant signatures. -func md5Parser(data []byte, offset int) (*SignatureResult, error) { - return &SignatureResult{ - Offset: offset, - Size: hashMagicLen, - Description: fmt.Sprintf("%s, %s endian", DescriptionMD5, hashEndianness(data, offset, md5ConstMagic())), - }, nil -} diff --git a/internal/leviathan/binsnoop/sig_executables.go b/internal/leviathan/binsnoop/sig_executables.go deleted file mode 100644 index 20b3429..0000000 --- a/internal/leviathan/binsnoop/sig_executables.go +++ /dev/null @@ -1,485 +0,0 @@ -package binsnoop - -import ( - "fmt" -) - -// --------------------------------------------------------------------------- -// ELF -// --------------------------------------------------------------------------- - -const ELFDescription = "ELF binary" - -func ELFMagic() [][]byte { - return [][]byte{ - {0x7F, 0x45, 0x4C, 0x46}, // "\x7FELF" - } -} - -func ELFParser(data []byte, offset int) (*SignatureResult, error) { - const elfIdentSize = 16 - const elfInfoStructSize = 8 - const expectedVersion = 1 - - elfIdentFields := []StructField{ - {"magic", "u32"}, - {"class", "u8"}, - {"endianness", "u8"}, - {"version", "u8"}, - {"osabi", "u8"}, - {"abiversion", "u8"}, - {"padding_1", "u32"}, - {"padding_2", "u24"}, - } - - elfInfoFields := []StructField{ - {"type", "u16"}, - {"machine", "u16"}, - {"version", "u32"}, - } - - elfClasses := map[int]int{ - 1: 32, - 2: 64, - } - - elfEndianness := map[int]string{ - 1: "little", - 2: "big", - } - - elfOSABI := map[int]string{ - 0: "System-V (Unix)", - 1: "HP-UX", - 2: "NetBSD", - 3: "Linux", - 4: "GNU Hurd", - 5: "86Open", - 6: "Solaris", - 7: "AIX", - 8: "IRIX", - 9: "FreeBSD", - 10: "Tru64", - 11: "Novell Modesto", - 12: "OpenBSD", - 13: "OpenVMS", - 14: "NonStop Kernel", - 15: "AROS", - 16: "FenixOS", - 17: "Nuxi CloudABI", - 18: "OpenVOS", - 97: "ARM ABI", - 102: "Cell LV2", - 202: "Cafe OS", - 255: "embedded", - } - - elfTypes := map[int]string{ - 0: "no file type", - 1: "relocatable", - 2: "executable", - 3: "shared object", - 4: "core file", - } - - elfMachines := map[int]string{ - 0: "no machine", - 1: "AT&T WE 32100", - 2: "SPARC", - 3: "x86", - 4: "Motorola 68k", - 5: "Motorola 88k", - 6: "Intel MCU", - 7: "Intel 80860", - 8: "MIPS", - 9: "IBM System/370", - 10: "MIPS RS3000", - 11: "RS6000", - 15: "HP PA-RISC", - 16: "nCUBE", - 17: "Fujitsu VPP500", - 18: "SPARC32PLUS", - 19: "Intel 80960", - 20: "PowerPC", - 21: "PowerPC 64-bit", - 22: "S390", - 23: "IBM SPU/SPC", - 24: "cisco SVIP", - 25: "cisco 7200", - 36: "NEC V800", - 37: "Fujitsu FR20", - 38: "TRW RH-32", - 39: "Motorola RCE", - 40: "ARM", - 41: "Digital Alpha", - 42: "SuperH", - 43: "SPARCv9", - 44: "Siemens TriCore embedded processor", - 45: "Argonaut RISC Core", - 46: "Hitachi H8/300", - 47: "Hitachi H8/300H", - 48: "Hitachi H8S", - 49: "Hitachi H8/500", - 50: "IA-64", - 51: "Stanford MIPS-X", - 52: "Motorola ColdFire", - 53: "Motorola M68HC12", - 54: "Fujitsu MMA Multimedia Accelerator", - 55: "Siemens PCP", - 56: "Sony nCPU embedded RISC processor", - 57: "Denso NDR1 microprocessor", - 58: "Motorola StarCore", - 59: "Toyota ME16", - 60: "STMicroelectronics ST100", - 61: "Advanced Logic TinyJ embedded processor", - 62: "AMD X86-64", - 63: "Sony DSP processor", - 64: "PDP-10", - 65: "PDP-11", - 66: "Siemens FX66", - 67: "STMicroelectronics ST9+", - 68: "STMicroelectronics ST7", - 69: "Motorola MC68HC16", - 70: "Motorola MC68HC11", - 71: "Motorola MC68HC08", - 72: "Motorola MC68HC05", - 73: "Silicon Graphics SVx", - 74: "STMicroelectonrics ST19", - 75: "Digital VAX", - 76: "Axis Communications 32-bit CPU", - 77: "Infineon Technologies 32-bit CPU", - 78: "Element 14 64-bit DSP", - 79: "LSI Logic 16-bit DSP", - 80: "MMIX", - 81: "Harvard machine-independent", - 82: "SiTera Prism", - 83: "Atmel AVR 8-bit", - 84: "Fujitsu FR30", - 85: "Mitsubishi D10V", - 86: "Mitsubishi D30V", - 87: "NEC v850", - 88: "Renesas M32R", - 89: "Matsushita MN10300", - 90: "Matsushita MN10200", - 91: "picoJava", - 92: "OpenRISC", - 93: "Synopsys ARCompact ARC700 cores", - 94: "Tensilica Xtensa", - 95: "Alphamosaic VideoCore", - 96: "Thompson Multimedia", - 97: "NatSemi 32k", - 98: "Tenor Network TPC", - 99: "Trebia SNP 1000", - 100: "STMicroelectronics ST200", - 101: "Ubicom IP2022", - 102: "MAX Processor", - 103: "NatSemi CompactRISC", - 104: "Fujitsu F2MC16", - 105: "TI msp430", - 106: "Analog Devices Blackfin", - 107: "S1C33 Family of Seiko Epson", - 108: "Sharp embedded", - 109: "Arca RISC", - 110: "PKU-Unity Ltd.", - 111: "eXcess: 16/32/64-bit", - 112: "Icera Deep Execution Processor", - 113: "Altera Nios II", - 114: "NatSemi CRX", - 115: "Motorola XGATE", - 116: "Infineon C16x/XC16x", - 117: "Renesas M16C series", - 118: "Microchip dsPIC30F", - 119: "Freescale RISC core", - 120: "Renesas M32C series", - 131: "Altium TSK3000 core", - 132: "Freescale RS08", - 134: "Cyan Technology eCOG2", - 135: "Sunplus S+core7 RISC", - 136: "New Japan Radio (NJR) 24-bit DSP", - 137: "Broadcom VideoCore III processor", - 138: "LatticeMico32", - 139: "Seiko Epson C17 family", - 140: "TMS320C6000", - 141: "TMS320C2000", - 142: "TMS320C55x", - 144: "TI Programmable Realtime Unit", - 160: "STMicroelectronics 64bit VLIW DSP", - 161: "Cypress M8C", - 162: "Renesas R32C series", - 163: "NXP TriMedia family", - 164: "Qualcomm DSP6", - 165: "Intel 8051 and variants", - 166: "STMicroelectronics STxP7x family", - 167: "Andes embedded RISC", - 168: "Cyan eCOG1X family", - 169: "Dallas MAXQ30", - 170: "New Japan Radio (NJR) 16-bit DSP", - 171: "M2000 Reconfigurable RISC", - 172: "Cray NV2 vector architecture", - 173: "Renesas RX family", - 174: "META", - 175: "MCST Elbrus e2k", - 176: "Cyan Technology eCOG16 family", - 177: "NatSemi CompactRISC", - 178: "Freescale Extended Time Processing Unit", - 179: "Infineon SLE9X", - 180: "Intel L1OM", - 181: "Intel K1OM", - 183: "ARM 64-bit", - 185: "Atmel 32-bit family", - 186: "STMicroeletronics STM8 8-bit", - 187: "Tilera TILE64", - 188: "Tilera TILEPro", - 189: "Xilinx MicroBlaze 32-bit RISC", - 190: "NVIDIA CUDA architecture", - 191: "Tilera TILE-Gx", - 195: "Synopsys ARCv2/HS3x/HS4x cores", - 197: "Renesas RL78 family", - 199: "Renesas 78K0R", - 200: "Freescale 56800EX", - 201: "Beyond BA1", - 202: "Beyond BA2", - 203: "XMOS xCORE", - 204: "Microchip 8-bit PIC(r)", - 210: "KM211 KM32", - 211: "KM211 KMX32", - 212: "KM211 KMX16", - 213: "KM211 KMX8", - 214: "KM211 KVARC", - 215: "Paneve CDP", - 216: "Cognitive Smart Memory", - 217: "iCelero CoolEngine", - 218: "Nanoradio Optimized RISC", - 219: "CSR Kalimba architecture family", - 220: "Zilog Z80", - 221: "Controls and Data Services VISIUMcore processor", - 222: "FTDI Chip FT32 high performance 32-bit RISC architecture", - 223: "Moxie processor family", - 224: "AMD GPU architecture", - 243: "RISC-V", - 244: "Lanai 32-bit processor", - 245: "CEVA Processor Architecture Family", - 246: "CEVA X2 Processor Family", - 247: "Berkeley Packet Filter", - 248: "Graphcore Intelligent Processing Unit", - 249: "Imagination Technologies", - 250: "Netronome Flow Processor", - 251: "NEC Vector Engine", - 252: "C-SKY processor family", - 253: "Synopsys ARCv3 64-bit ISA/HS6x cores", - 254: "MOS Technology MCS 6502 processor", - 255: "Synopsys ARCv3 32-bit", - 256: "Kalray VLIW core of the MPPA family", - 257: "WDC 65C816", - 258: "LoongArch", - 259: "ChipON KungFu32", - } - - elfData := data[offset:] - if len(elfData) < elfIdentSize+elfInfoStructSize { - return nil, fmt.Errorf("ELF: not enough data") - } - - // Parse the e_ident (endianness doesn't matter for single-byte fields) - eIdent, err := Parse(elfData, elfIdentFields, "little") - if err != nil { - return nil, fmt.Errorf("ELF: e_ident parse error: %w", err) - } - - // Sanity check padding fields - if eIdent["padding_1"] != 0 || eIdent["padding_2"] != 0 { - return nil, fmt.Errorf("ELF: non-zero padding in e_ident") - } - if eIdent["version"] != expectedVersion { - return nil, fmt.Errorf("ELF: unexpected e_ident version %d", eIdent["version"]) - } - - classVal, classOK := elfClasses[eIdent["class"]] - if !classOK { - return nil, fmt.Errorf("ELF: unknown class %d", eIdent["class"]) - } - - endianStr, endianOK := elfEndianness[eIdent["endianness"]] - if !endianOK { - return nil, fmt.Errorf("ELF: unknown endianness %d", eIdent["endianness"]) - } - - osabiStr, osabiOK := elfOSABI[eIdent["osabi"]] - if !osabiOK { - return nil, fmt.Errorf("ELF: unknown OS/ABI %d", eIdent["osabi"]) - } - - // Parse the rest of the ELF header with the correct endianness - elfInfoData := elfData[elfIdentSize : elfIdentSize+elfInfoStructSize] - elfInfo, err := Parse(elfInfoData, elfInfoFields, endianStr) - if err != nil { - return nil, fmt.Errorf("ELF: info parse error: %w", err) - } - - if elfInfo["version"] != expectedVersion { - return nil, fmt.Errorf("ELF: unexpected info version %d", elfInfo["version"]) - } - - typeStr, typeOK := elfTypes[elfInfo["type"]] - if !typeOK { - return nil, fmt.Errorf("ELF: unknown type %d", elfInfo["type"]) - } - - machineStr := "Unknown" - if m, ok := elfMachines[elfInfo["machine"]]; ok { - machineStr = m - } - - return &SignatureResult{ - Offset: offset, - Name: "elf", - Description: fmt.Sprintf("%s, %d-bit %s, %s for %s, %s endian", ELFDescription, classVal, typeStr, machineStr, osabiStr, endianStr), - Confidence: ConfidenceMedium, - }, nil -} - -// --------------------------------------------------------------------------- -// PE -// --------------------------------------------------------------------------- - -const PEDescription = "Windows PE binary" - -func PEMagic() [][]byte { - // First 16 bytes of common DOS headers (e_magic through e_ss) - return [][]byte{ - {0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00}, - {0x4D, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - } -} - -func PEParser(data []byte, offset int) (*SignatureResult, error) { - const peMagicValue = 0x00004550 // "PE\x00\x00" - - dosFields := []StructField{ - {"e_magic", "u16"}, - {"e_cblp", "u16"}, - {"e_cp", "u16"}, - {"e_crlc", "u16"}, - {"e_cparhdr", "u16"}, - {"e_minalloc", "u16"}, - {"e_maxalloc", "u16"}, - {"e_ss", "u16"}, - {"e_sp", "u16"}, - {"e_csum", "u16"}, - {"e_ip", "u16"}, - {"e_cs", "u16"}, - {"e_lfarlc", "u16"}, - {"e_ovno", "u16"}, - {"e_res_1", "u16"}, - {"e_res_2", "u16"}, - {"e_res_3", "u16"}, - {"e_res_4", "u16"}, - {"e_oemid", "u16"}, - {"e_oeminfo", "u16"}, - {"e_res_5", "u16"}, - {"e_res_6", "u16"}, - {"e_res_7", "u16"}, - {"e_res_8", "u16"}, - {"e_res_9", "u16"}, - {"e_res_10", "u16"}, - {"e_res_11", "u16"}, - {"e_res_12", "u16"}, - {"e_res_13", "u16"}, - {"e_res_14", "u16"}, - {"e_lfanew", "u32"}, - } - - peFields := []StructField{ - {"magic", "u32"}, - {"machine", "u16"}, - {"number_of_sections", "u16"}, - {"timestamp", "u32"}, - {"symbol_table_ptr", "u32"}, - {"number_of_symbols", "u32"}, - {"optional_header_size", "u16"}, - {"characteristics", "u16"}, - } - - knownMachineTypes := map[int]string{ - 0x0: "Unknown", - 0x184: "Alpha32", - 0x284: "Alpha64", - 0x1D3: "Matsushita AM33", - 0x8664: "Intel x86-64", - 0x1C0: "ARM", - 0xAA64: "ARM-64", - 0x1C4: "ARM Thumb2", - 0xEBC: "EFI", - 0x14C: "Intel x86", - 0x200: "Intel Itanium", - 0x6232: "LoongArch 32-bit", - 0x6264: "LoongArch 64-bit", - 0x9041: "Mitsubishi M32R", - 0x266: "MIPS16", - 0x366: "MIPS with FPU", - 0x466: "MIPS16 with FPU", - 0x1F0: "PowerPC", - 0x1F1: "PowerPC with FPU", - 0x5032: "RISC-V 32-bit", - 0x5064: "RISC-V 64-bit", - 0x5128: "RISC-V 128-bit", - 0x1A2: "Hitachi SH3", - 0x1A3: "Hitachi SH3 DSP", - 0x1A6: "Hitachi SH4", - 0x1A8: "Hitachi SH5", - 0x1C2: "Thumb", - 0x169: "MIPS WCEv2", - } - - peData := data[offset:] - dosStructSize := StructSize(dosFields) - peStructSize := StructSize(peFields) - - if len(peData) < dosStructSize { - return nil, fmt.Errorf("PE: not enough data for DOS header") - } - - dosHeader, err := Parse(peData, dosFields, "little") - if err != nil { - return nil, fmt.Errorf("PE: DOS header parse error: %w", err) - } - - // Sanity check the reserved header fields; they should all be 0 - for _, field := range []string{ - "e_res_1", "e_res_2", "e_res_3", "e_res_4", - "e_res_5", "e_res_6", "e_res_7", "e_res_8", - "e_res_9", "e_res_10", "e_res_11", "e_res_12", - "e_res_13", "e_res_14", - } { - if dosHeader[field] != 0 { - return nil, fmt.Errorf("PE: reserved field %s is non-zero", field) - } - } - - peHeaderStart := dosHeader["e_lfanew"] - peHeaderEnd := peHeaderStart + peStructSize - - if peHeaderEnd > len(peData) { - return nil, fmt.Errorf("PE: e_lfanew points beyond available data") - } - - peHeader, err := Parse(peData[peHeaderStart:peHeaderEnd], peFields, "little") - if err != nil { - return nil, fmt.Errorf("PE: PE header parse error: %w", err) - } - - if peHeader["magic"] != peMagicValue { - return nil, fmt.Errorf("PE: invalid PE magic 0x%08X", peHeader["magic"]) - } - - machineStr, machineOK := knownMachineTypes[peHeader["machine"]] - if !machineOK { - return nil, fmt.Errorf("PE: unknown machine type 0x%X", peHeader["machine"]) - } - - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, machine type: %s", PEDescription, machineStr), - Confidence: ConfidenceMedium, - }, nil -} diff --git a/internal/leviathan/binsnoop/sig_filesystems.go b/internal/leviathan/binsnoop/sig_filesystems.go deleted file mode 100644 index 4f59ffb..0000000 --- a/internal/leviathan/binsnoop/sig_filesystems.go +++ /dev/null @@ -1,1289 +0,0 @@ -package binsnoop - -import ( - "bytes" - "encoding/binary" - "fmt" - "math" -) - -// --------------------------------------------------------------------------- -// SquashFS -// --------------------------------------------------------------------------- - -const SquashFSDescription = "SquashFS file system" - -func squashfs_magic() [][]byte { - return [][]byte{ - []byte("sqsh"), - []byte("hsqs"), - []byte("sqlz"), - []byte("qshs"), - []byte("tqsh"), - []byte("hsqt"), - []byte("shsq"), - } -} - -func squashfs_parser(data []byte, offset int) (*SignatureResult, error) { - const squashfsV4 = 4 - const maxSquashfsVersion uint16 = 4 - const minSquashfsHeaderSize = 120 - const versionStart = 28 - const versionEnd = 30 - - compressionTypes := map[int]string{ - 0: "unknown", 1: "gzip", 2: "lzma", 3: "lzo", 4: "xz", 5: "lz4", 6: "zstd", - } - - available := len(data) - offset - sqshData := data[offset:] - - if len(sqshData) <= minSquashfsHeaderSize { - return nil, fmt.Errorf("not enough data for squashfs header") - } - - // Determine endianness from version field at fixed offset - endianness := "little" - squashfsVersion := int(binary.LittleEndian.Uint16(sqshData[versionStart:versionEnd])) - if squashfsVersion == 0 || squashfsVersion > int(maxSquashfsVersion) { - endianness = "big" - squashfsVersion = int(binary.BigEndian.Uint16(sqshData[versionStart:versionEnd])) - } - if squashfsVersion <= 0 || squashfsVersion > int(maxSquashfsVersion) { - return nil, fmt.Errorf("invalid squashfs version") - } - - // Pick the right structure - var fields []StructField - if squashfsVersion == squashfsV4 { - fields = []StructField{ - {"magic", "u32"}, {"inode_count", "u32"}, {"modification_time", "u32"}, - {"block_size", "u32"}, {"fragment_count", "u32"}, {"compression_id", "u16"}, - {"block_log", "u16"}, {"flags", "u16"}, {"id_count", "u16"}, - {"major_version", "u16"}, {"minor_version", "u16"}, - {"root_inode_ref", "u64"}, {"image_size", "u64"}, {"uid_start", "u64"}, - } - } else { - fields = []StructField{ - {"magic", "u32"}, {"inode_count", "u32"}, - {"bytes_used_2", "u32"}, {"uid_start_2", "u32"}, {"guid_start_2", "u32"}, - {"inode_table_start_2", "u32"}, {"directory_table_start_2", "u32"}, - {"major_version", "u16"}, {"minor_version", "u16"}, - {"block_size_1", "u16"}, {"block_log", "u16"}, - {"flags", "u8"}, {"uid_count", "u8"}, {"guid_count", "u8"}, - {"modification_time", "u32"}, - {"root_inode_ref", "u64"}, {"block_size", "u32"}, - {"fragment_entry_count", "u32"}, {"fragment_table_start_2", "u32"}, - {"image_size", "u64"}, {"uid_start", "u64"}, {"guid_start", "u64"}, - {"inode_table_start", "u64"}, {"directory_table_start", "u64"}, - {"fragment_table_start", "u64"}, {"lookup_table_start", "u64"}, - } - } - - hdr, err := Parse(sqshData, fields, endianness) - if err != nil { - return nil, fmt.Errorf("squashfs header parse failed") - } - - // For v1/v2 images, adjust fields - if squashfsVersion < 3 { - hdr["uid_start"] = hdr["uid_start_2"] - hdr["image_size"] = hdr["bytes_used_2"] - } - - headerSize := StructSize(fields) - imageSize := hdr["image_size"] - - if imageSize <= minSquashfsHeaderSize { - return nil, fmt.Errorf("squashfs image size too small") - } - if imageSize > available { - return nil, fmt.Errorf("squashfs image size exceeds available data") - } - - // Validate block_size vs block_log - blockSize := hdr["block_size"] - blockLog := hdr["block_log"] - if blockSize <= 0 || blockLog != int(math.Log2(float64(blockSize))) { - return nil, fmt.Errorf("squashfs block_size/block_log mismatch") - } - - // Validate UID table - uidTableStart := offset + hdr["uid_start"] - if uidTableStart <= headerSize { - return nil, fmt.Errorf("squashfs uid table offset invalid") - } - if uidTableStart >= len(data) { - return nil, fmt.Errorf("squashfs uid table out of bounds") - } - - // Parse one UID entry - uidData := data[uidTableStart:] - var uidFields []StructField - if squashfsVersion == squashfsV4 { - uidFields = []StructField{{"uid_block_ptr", "u64"}} - } else { - uidFields = []StructField{{"uid_block_ptr", "u32"}} - } - uidEntry, err := Parse(uidData, uidFields, endianness) - if err != nil { - return nil, fmt.Errorf("squashfs uid entry parse failed") - } - uidPtr := uidEntry["uid_block_ptr"] - if uidPtr != 0 && (uidPtr <= headerSize || uidPtr > imageSize) { - return nil, fmt.Errorf("squashfs uid table pointer invalid") - } - - compressionID := hdr["compression_id"] - if squashfsVersion < 4 { - // v3 headers don't have compression_id; use 0 (unknown) - compressionID = 0 - } - compStr, ok := compressionTypes[compressionID] - if !ok { - return nil, fmt.Errorf("squashfs unknown compression type") - } - - createDate := EpochToString(uint32(hdr["modification_time"])) - - return &SignatureResult{ - Offset: offset, - Size: imageSize, - Confidence: ConfidenceHigh, - Description: fmt.Sprintf( - "%s, %s endian, version: %d.%d, compression: %s, inode count: %d, block size: %d, image size: %d bytes, created: %s", - SquashFSDescription, endianness, - hdr["major_version"], hdr["minor_version"], - compStr, hdr["inode_count"], blockSize, imageSize, createDate, - ), - }, nil -} - -// --------------------------------------------------------------------------- -// CramFS -// --------------------------------------------------------------------------- - -const CramFSDescription = "CramFS filesystem" - -func cramfs_magic() [][]byte { - return [][]byte{ - []byte("Compressed ROMFS"), - } -} - -func cramfs_parser(data []byte, offset int) (*SignatureResult, error) { - const signatureOffset = 16 - const crcStartOffset = 32 - const crcEndOffset = 36 - const bigEndianMagic = 0x453DCD28 - const littleEndianMagic = 0x28CD3D45 - - realOffset := offset - signatureOffset - if realOffset < 0 || realOffset >= len(data) { - return nil, fmt.Errorf("cramfs offset out of bounds") - } - - cramfsData := data[realOffset:] - - fields := []StructField{ - {"magic", "u32"}, {"size", "u32"}, {"flags", "u32"}, {"future", "u32"}, - {"signature_p1", "u64"}, {"signature_p2", "u64"}, - {"checksum", "u32"}, {"edition", "u32"}, {"block_count", "u32"}, {"file_count", "u32"}, - } - - endianness := "little" - hdr, err := Parse(cramfsData, fields, endianness) - if err != nil { - return nil, fmt.Errorf("cramfs parse failed") - } - - magic := hdr["magic"] - if magic != bigEndianMagic && magic != littleEndianMagic { - return nil, fmt.Errorf("cramfs invalid magic") - } - if magic == bigEndianMagic { - endianness = "big" - hdr, err = Parse(cramfsData, fields, endianness) - if err != nil { - return nil, fmt.Errorf("cramfs big endian parse failed") - } - } - - structSize := StructSize(fields) - if hdr["size"] <= structSize { - return nil, fmt.Errorf("cramfs reported size too small") - } - - imageSize := hdr["size"] - checksum := uint32(hdr["checksum"]) - fileCount := hdr["file_count"] - - if realOffset+imageSize > len(data) { - return nil, fmt.Errorf("cramfs image exceeds available data") - } - - // Copy image data and null out checksum field for CRC validation - cramfsImage := make([]byte, imageSize) - copy(cramfsImage, cramfsData[:imageSize]) - for i := crcStartOffset; i < crcEndOffset && i < imageSize; i++ { - cramfsImage[i] = 0 - } - - errorMsg := "" - confidence := ConfidenceHigh - if CRC32(cramfsImage) != checksum { - errorMsg = " (checksum error)" - confidence = ConfidenceMedium - } - - return &SignatureResult{ - Offset: realOffset, - Size: imageSize, - Confidence: confidence, - Description: fmt.Sprintf("%s, %s endian, %d files, total size: %d bytes%s", - CramFSDescription, endianness, fileCount, imageSize, errorMsg), - }, nil -} - -// --------------------------------------------------------------------------- -// JFFS2 -// --------------------------------------------------------------------------- - -const JFFS2Description = "JFFS2 filesystem" - -func jffs2_magic() [][]byte { - return [][]byte{ - {0x19, 0x85, 0xE0, 0x01}, - {0x19, 0x85, 0xE0, 0x02}, - {0x19, 0x85, 0x20, 0x03}, - {0x85, 0x19, 0x01, 0xE0}, - {0x85, 0x19, 0x02, 0xE0}, - {0x85, 0x19, 0x03, 0x20}, - } -} - -func jffs2_parser(data []byte, offset int) (*SignatureResult, error) { - const maxPageSize = 0x20000 - const minValidNodeCount = 2 - const jffs2CorrectMagic = 0x1985 - const headerCRCSize = 8 - - jffs2NodeFields := []StructField{ - {"magic", "u16"}, {"type", "u16"}, {"size", "u32"}, {"crc", "u32"}, - } - jffs2NodeStructSize := StructSize(jffs2NodeFields) // 12 - - // Parse first node - firstNode, endianness, err := parseJFFS2NodeHeader(data[offset:], jffs2NodeFields, headerCRCSize, jffs2CorrectMagic) - if err != nil { - return nil, err - } - - jffs2EOF := offset + jffs2Roundup(firstNode["size"]) - if jffs2EOF >= len(data) { - return nil, fmt.Errorf("jffs2 first node exceeds data") - } - - var nodeMagic []byte - if endianness == "big" { - nodeMagic = []byte{0x19, 0x85} - } else { - nodeMagic = []byte{0x85, 0x19} - } - - nodeCount := 1 - - // Scan for subsequent nodes - searchData := data[jffs2EOF:] - for i := 0; i < len(searchData); { - idx := bytes.Index(searchData[i:], nodeMagic) - if idx < 0 { - break - } - absPos := jffs2EOF + i + idx - if absPos < jffs2EOF { - i += idx + 1 - continue - } - if absPos-jffs2EOF > maxPageSize { - break - } - headerEnd := absPos + jffs2NodeStructSize - if headerEnd > len(data) { - break - } - nodeHdr, _, err := parseJFFS2NodeHeader(data[absPos:headerEnd], jffs2NodeFields, headerCRCSize, jffs2CorrectMagic) - if err == nil { - nodeCount++ - jffs2EOF = absPos + jffs2Roundup(nodeHdr["size"]) - } - i += idx + 1 - } - - if nodeCount <= minValidNodeCount { - return nil, fmt.Errorf("jffs2 too few valid nodes: %d", nodeCount) - } - - totalSize := jffs2EOF - offset - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Confidence: ConfidenceHigh, - Description: fmt.Sprintf("%s, %s endian, nodes: %d, total size: %d bytes", - JFFS2Description, endianness, nodeCount, totalSize), - }, nil -} - -func parseJFFS2NodeHeader(nodeData []byte, fields []StructField, headerCRCSize, correctMagic int) (map[string]int, string, error) { - endianness := "little" - hdr, err := Parse(nodeData, fields, endianness) - if err != nil { - return nil, "", fmt.Errorf("jffs2 node parse failed") - } - if hdr["magic"] != correctMagic { - endianness = "big" - hdr, err = Parse(nodeData, fields, endianness) - if err != nil { - return nil, "", fmt.Errorf("jffs2 node big endian parse failed") - } - } - if hdr["magic"] != correctMagic { - return nil, "", fmt.Errorf("jffs2 node invalid magic") - } - - // Validate header CRC - if headerCRCSize > len(nodeData) { - return nil, "", fmt.Errorf("jffs2 not enough data for crc") - } - calcCRC := int(jffs2NodeCRC(nodeData[:headerCRCSize])) - if calcCRC != hdr["crc"] { - return nil, "", fmt.Errorf("jffs2 node header crc mismatch") - } - - return hdr, endianness, nil -} - -func jffs2NodeCRC(data []byte) uint32 { - return CRC32(data) // CRC32 uses IEEE polynomial; JFFS2: crc32(0xFFFFFFFF, data) ^ 0xFFFFFFFF which is standard CRC32 -} - -func jffs2Roundup(num int) int { - base := 4 - return ((num + base - 1) / base) * base -} - -// --------------------------------------------------------------------------- -// YAFFS -// --------------------------------------------------------------------------- - -const YAFFSDescription = "YAFFSv2 filesystem" - -func yaffs_magic() [][]byte { - return [][]byte{ - {0x03, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF}, - {0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF}, - {0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF}, - {0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0xFF, 0xFF}, - } -} - -func yaffs_parser(data []byte, offset int) (*SignatureResult, error) { - const maxObjSize = 16896 - const minNumberOfObjs = 2 - const bigEndianFirstByte byte = 0 - const fileType = 1 - - available := len(data) - requiredMinOffset := offset + (maxObjSize * minNumberOfObjs) - if !IsOffsetSafe(available, requiredMinOffset, nil) { - return nil, fmt.Errorf("yaffs not enough data") - } - - endianness := "little" - if data[offset] == bigEndianFirstByte { - endianness = "big" - } - - yaffsData := data[offset:] - - // Determine page size - pageSize, err := yaffsGetPageSize(yaffsData) - if err != nil { - return nil, err - } - - // Determine spare size - spareSize, err := yaffsGetSpareSize(yaffsData, pageSize, endianness) - if err != nil { - return nil, err - } - - // Get total image size - imageSize, err := yaffsGetImageSize(yaffsData, pageSize, spareSize, endianness) - if err != nil { - return nil, err - } - - return &SignatureResult{ - Offset: offset, - Size: imageSize, - Confidence: ConfidenceMedium, - Description: fmt.Sprintf("%s, %s endian, page size: %d, spare size: %d, image size: %d bytes", - YAFFSDescription, endianness, pageSize, spareSize, imageSize), - }, nil -} - -func yaffsGetPageSize(fileData []byte) (int, error) { - spareMagics := [][]byte{ - {0x00, 0x00, 0x10, 0x00}, - {0x00, 0x10, 0x00, 0x00}, - {0xFF, 0xFF, 0x00, 0x00, 0x10, 0x00}, - {0xFF, 0xFF, 0x00, 0x10, 0x00, 0x00}, - } - pageSizes := []int{512, 1024, 2048, 4096, 8192, 16384} - - for _, ps := range pageSizes { - for _, sm := range spareMagics { - start := ps - end := start + len(sm) - if end <= len(fileData) { - if bytes.Equal(fileData[start:end], sm) { - return ps, nil - } - } - } - } - return 0, fmt.Errorf("yaffs page size not detected") -} - -func yaffsGetSpareSize(fileData []byte, pageSize int, endianness string) (int, error) { - const minNumberOfObjs = 2 - spareSizes := []int{16, 32, 64, 128, 256, 512} - - yaffsObjFields := []StructField{ - {"type", "u32"}, {"parent_id", "u32"}, {"name_checksum", "u16"}, - } - - for _, ss := range spareSizes { - nextObjOffset := (pageSize + ss) * minNumberOfObjs - if nextObjOffset < len(fileData) { - hdr, err := Parse(fileData[nextObjOffset:], yaffsObjFields, endianness) - if err == nil { - allowedTypes := []int{0, 1, 2, 3, 4, 5} - if containsInt(allowedTypes, hdr["type"]) && hdr["parent_id"] > 0 && hdr["name_checksum"] == 0xFFFF { - return ss, nil - } - } - } - } - return 0, fmt.Errorf("yaffs spare size not detected") -} - -func yaffsGetImageSize(fileData []byte, pageSize, spareSize int, endianness string) (int, error) { - const fileType = 1 - const minNumberOfObjs = 2 - const infoStructStart = 268 - - yaffsObjFields := []StructField{ - {"type", "u32"}, {"parent_id", "u32"}, {"name_checksum", "u16"}, - } - yaffsFileInfo := []StructField{ - {"mode", "u32"}, {"uid", "u32"}, {"gid", "u32"}, - {"atime", "u32"}, {"mtime", "u32"}, {"ctime", "u32"}, {"file_size", "u32"}, - } - - imageSize := 0 - nextObjOffset := 0 - var previousObjOffset *int - availableData := len(fileData) - blockSize := pageSize + spareSize - - for IsOffsetSafe(availableData, nextObjOffset, previousObjOffset) { - objData := fileData[nextObjOffset:] - hdr, err := Parse(objData, yaffsObjFields, endianness) - if err != nil { - break - } - allowedTypes := []int{0, 1, 2, 3, 4, 5} - if !containsInt(allowedTypes, hdr["type"]) || hdr["parent_id"] <= 0 || hdr["name_checksum"] != 0xFFFF { - break - } - - dataBlocks := 1 - if hdr["type"] == fileType { - if infoStructStart < len(objData) { - fileInfo, err := Parse(objData[infoStructStart:], yaffsFileInfo, endianness) - if err != nil { - return 0, fmt.Errorf("yaffs file header parse failed") - } - fileBlockCount := int(math.Ceil(float64(fileInfo["file_size"]) / float64(pageSize))) - dataBlocks += fileBlockCount - } else { - return 0, fmt.Errorf("yaffs not enough data for file header") - } - } - - prev := nextObjOffset - previousObjOffset = &prev - imageSize += dataBlocks * blockSize - nextObjOffset = imageSize - } - - if imageSize > blockSize*minNumberOfObjs && imageSize <= availableData { - return imageSize, nil - } - return 0, fmt.Errorf("yaffs image size invalid") -} - -// --------------------------------------------------------------------------- -// RomFS -// --------------------------------------------------------------------------- - -const RomFSDescription = "RomFS filesystem" - -func romfs_magic() [][]byte { - return [][]byte{ - []byte("-rom1fs-"), - } -} - -func romfs_parser(data []byte, offset int) (*SignatureResult, error) { - const maxHeaderCRCDataLen = 512 - - headerFields := []StructField{ - {"magic", "u64"}, {"image_size", "u32"}, {"checksum", "u32"}, - } - - romfsData := data[offset:] - headerSize := StructSize(headerFields) - - hdr, err := Parse(romfsData, headerFields, "big") - if err != nil { - return nil, fmt.Errorf("romfs header parse failed") - } - - if hdr["image_size"] <= headerSize { - return nil, fmt.Errorf("romfs image size too small") - } - - // Get volume name - volumeName := "" - if headerSize < len(romfsData) { - volumeName = GetCString(romfsData[headerSize:]) - } - - // Validate CRC - crcDataLen := maxHeaderCRCDataLen - if hdr["image_size"] < crcDataLen { - crcDataLen = hdr["image_size"] - } - if crcDataLen > len(romfsData) { - return nil, fmt.Errorf("romfs not enough data for crc") - } - if !romfsCRCValid(romfsData[:crcDataLen]) { - return nil, fmt.Errorf("romfs crc invalid") - } - - return &SignatureResult{ - Offset: offset, - Size: hdr["image_size"], - Confidence: ConfidenceHigh, - Description: fmt.Sprintf(`%s, volume name: "%s", total size: %d bytes`, - RomFSDescription, volumeName, hdr["image_size"]), - }, nil -} - -func romfsCRCValid(crcData []byte) bool { - wordSize := 4 - if len(crcData)%wordSize != 0 { - return false - } - var sum uint32 - for i := 0; i < len(crcData); i += wordSize { - sum += binary.BigEndian.Uint32(crcData[i : i+wordSize]) - } - return sum == 0 -} - -// --------------------------------------------------------------------------- -// EXT -// --------------------------------------------------------------------------- - -const EXTDescription = "EXT filesystem" - -func ext_magic() [][]byte { - return [][]byte{ - {0x53, 0xEF, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00}, - {0x53, 0xEF, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00}, - {0x53, 0xEF, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00}, - {0x53, 0xEF, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00}, - {0x53, 0xEF, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00}, - {0x53, 0xEF, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00}, - } -} - -const EXTMagicOffset = 1080 - -func ext_parser(data []byte, offset int) (*SignatureResult, error) { - const magicOffset = 1080 - const superblockOffset = 1024 - const superblockSize = 1024 - const maxBlockLog = 2 - - realOffset := offset - magicOffset - if realOffset < 0 { - return nil, fmt.Errorf("ext offset out of bounds") - } - - extData := data[realOffset:] - if len(extData) < superblockOffset+superblockSize { - return nil, fmt.Errorf("ext not enough data for superblock") - } - - extSBFields := []StructField{ - {"inodes_count", "u32"}, {"blocks_count", "u32"}, {"reserved_blocks_count", "u32"}, - {"free_blocks_count", "u32"}, {"free_inodes_count", "u32"}, {"first_data_block", "u32"}, - {"log_block_size", "u32"}, {"log_frag_size", "u32"}, {"blocks_per_group", "u32"}, - {"frags_per_group", "u32"}, {"inodes_per_group", "u32"}, {"modification_time", "u32"}, - {"write_time", "u32"}, {"mount_count", "u16"}, {"max_mount_count", "u16"}, - {"magic", "u16"}, {"state", "u16"}, {"errors", "u16"}, {"s_minor_rev_level", "u16"}, - {"last_check", "u32"}, {"check_interval", "u32"}, {"creator_os", "u32"}, - {"s_rev_level", "u32"}, {"resuid", "u16"}, {"resgid", "u16"}, - } - - hdr, err := Parse(extData[superblockOffset:], extSBFields, "little") - if err != nil { - return nil, fmt.Errorf("ext superblock parse failed") - } - - supportedOS := map[int]string{0: "Linux", 1: "GNU HURD", 2: "MASIX", 3: "FreeBSD", 4: "Lites"} - osName, osOk := supportedOS[hdr["creator_os"]] - if !osOk { - return nil, fmt.Errorf("ext unknown OS") - } - - allowedRevLevels := []int{0, 1} - if !containsInt(allowedRevLevels, hdr["s_rev_level"]) { - return nil, fmt.Errorf("ext invalid rev level") - } - - allowedFirstDataBlocks := []int{0, 1} - if !containsInt(allowedFirstDataBlocks, hdr["first_data_block"]) { - return nil, fmt.Errorf("ext invalid first_data_block") - } - - if hdr["log_block_size"] > maxBlockLog { - return nil, fmt.Errorf("ext invalid log_block_size") - } - - blockSize := 1024 << hdr["log_block_size"] - imageSize := blockSize * hdr["blocks_count"] - - return &SignatureResult{ - Offset: realOffset, - Size: imageSize, - Confidence: ConfidenceMedium, - Description: fmt.Sprintf( - "%s for %s, inodes: %d, block size: %d, block count: %d, free blocks: %d, reserved blocks: %d, total size: %d bytes", - EXTDescription, osName, hdr["inodes_count"], blockSize, - hdr["free_blocks_count"], hdr["reserved_blocks_count"], hdr["blocks_count"], imageSize), - }, nil -} - -// --------------------------------------------------------------------------- -// FAT -// --------------------------------------------------------------------------- - -const FATDescription = "FAT file system" -const FATMagicOffset = 0x01FE - -func fat_magic() [][]byte { - return [][]byte{ - {0x55, 0xAA}, - } -} - -func fat_parser(data []byte, offset int) (*SignatureResult, error) { - const expectedFATCount = 2 - - validOpcode1 := []int{0xEB, 0xE9} - validSectorSizes := []int{512, 1024, 2048, 4096} - validSectorsPerCluster := []int{1, 2, 4, 8, 16, 32, 64, 128} - validMediaTypes := []int{0xF0, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE} - - if offset < FATMagicOffset { - return nil, fmt.Errorf("fat offset too small") - } - realOffset := offset - FATMagicOffset - - fatData := data[realOffset:] - - fields := []StructField{ - {"opcode1", "u8"}, {"opcode2", "u8"}, {"opcode3", "u8"}, - {"oem_name", "u64"}, {"bytes_per_sector", "u16"}, {"sectors_per_cluster", "u8"}, - {"reserved_sectors", "u16"}, {"fat_count", "u8"}, {"root_entries_count_16", "u16"}, - {"total_sectors_16", "u16"}, {"media_type", "u8"}, {"fat_size_16", "u16"}, - {"sectors_per_track", "u16"}, {"number_of_heads", "u16"}, - {"hidden_sectors", "u32"}, {"total_sectors_32", "u32"}, - } - - hdr, err := Parse(fatData, fields, "little") - if err != nil { - return nil, fmt.Errorf("fat header parse failed") - } - - if !containsInt(validOpcode1, hdr["opcode1"]) { - return nil, fmt.Errorf("fat invalid opcode1") - } - if !containsInt(validSectorSizes, hdr["bytes_per_sector"]) { - return nil, fmt.Errorf("fat invalid sector size") - } - if !containsInt(validSectorsPerCluster, hdr["sectors_per_cluster"]) { - return nil, fmt.Errorf("fat invalid sectors per cluster") - } - if hdr["reserved_sectors"] <= 0 { - return nil, fmt.Errorf("fat invalid reserved sectors") - } - if hdr["fat_count"] != expectedFATCount { - return nil, fmt.Errorf("fat invalid fat count") - } - if !containsInt(validMediaTypes, hdr["media_type"]) { - return nil, fmt.Errorf("fat invalid media type") - } - - isFAT32 := hdr["fat_size_16"] == 0 - - totalSize := 0 - if hdr["total_sectors_16"] != 0 { - totalSize = hdr["total_sectors_16"] * hdr["bytes_per_sector"] - } else { - totalSize = hdr["total_sectors_32"] * hdr["bytes_per_sector"] - } - if totalSize <= 0 { - return nil, fmt.Errorf("fat total size is zero") - } - - fatTypeDesc := "FAT12/16" - if isFAT32 { - fatTypeDesc = "FAT32" - } - - return &SignatureResult{ - Offset: realOffset, - Size: totalSize, - Confidence: ConfidenceMedium, - Description: fmt.Sprintf("%s, type: %s, total size: %d bytes", - FATDescription, fatTypeDesc, totalSize), - }, nil -} - -// --------------------------------------------------------------------------- -// NTFS -// --------------------------------------------------------------------------- - -const NTFSDescription = "NTFS partition" - -func ntfs_magic() [][]byte { - return [][]byte{ - {0xEB, 0x52, 0x90, 0x4E, 0x54, 0x46, 0x53, 0x20, 0x20, 0x20, 0x20}, - } -} - -func ntfs_parser(data []byte, offset int) (*SignatureResult, error) { - ntfsFields := []StructField{ - {"opcodes", "u24"}, {"magic", "u64"}, - {"bytes_per_sector", "u16"}, {"sectors_per_cluster", "u8"}, - {"unused1", "u16"}, {"unused2", "u24"}, {"unused3", "u16"}, - {"media_type", "u8"}, {"unused4", "u16"}, - {"sectors_per_track", "u16"}, {"head_count", "u16"}, - {"hidden_sector_count", "u32"}, {"unused5", "u32"}, - {"unknown", "u32"}, {"sector_count", "u64"}, - } - - ntfsData := data[offset:] - hdr, err := Parse(ntfsData, ntfsFields, "little") - if err != nil { - return nil, fmt.Errorf("ntfs header parse failed") - } - - if hdr["unused1"] != 0 || hdr["unused2"] != 0 || hdr["unused3"] != 0 || - hdr["unused4"] != 0 || hdr["unused5"] != 0 { - return nil, fmt.Errorf("ntfs unused fields are not zero") - } - - sectorSize := hdr["bytes_per_sector"] - sectorCount := hdr["sector_count"] - // The reported sector count does not include the NTFS boot sector itself - totalSize := sectorSize * (sectorCount + 1) - - if totalSize <= sectorSize { - return nil, fmt.Errorf("ntfs total size too small") - } - - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Confidence: ConfidenceMedium, - Description: fmt.Sprintf("%s, number of sectors: %d, bytes per sector: %d, total size: %d bytes", - NTFSDescription, sectorCount, sectorSize, totalSize), - }, nil -} - -// --------------------------------------------------------------------------- -// APFS -// --------------------------------------------------------------------------- - -const APFSDescription = "APple File System" -const APFSMagicOffset = 0x20 - -func apfs_magic() [][]byte { - return [][]byte{ - []byte("NXSB"), - } -} - -func apfs_parser(data []byte, offset int) (*SignatureResult, error) { - const mbrBlockSize = 512 - const maxFSCount = 100 - const fsCountBlockSize = 512 - - if offset < APFSMagicOffset { - return nil, fmt.Errorf("apfs offset too small") - } - realOffset := offset - APFSMagicOffset - available := len(data) - realOffset - - apfsData := data[realOffset:] - apfsStructStart := APFSMagicOffset - - fields := []StructField{ - {"magic", "u32"}, {"block_size", "u32"}, {"block_count", "u64"}, - {"nx_features", "u64"}, {"nx_ro_compat_features", "u64"}, {"nx_incompat_features", "u64"}, - {"nx_uuid_p1", "u64"}, {"nx_uuid_p2", "u64"}, - {"nx_next_oid", "u64"}, {"nx_next_xid", "u64"}, - {"nx_xp_desc_blocks", "u32"}, {"nx_xp_data_blocks", "u32"}, - {"nx_xp_desc_base", "u64"}, {"nx_xp_data_base", "u64"}, - {"nx_xp_desc_next", "u32"}, {"nx_xp_data_next", "u32"}, - {"nx_xp_desc_index", "u32"}, {"nx_xp_desc_len", "u32"}, - {"nx_xp_data_index", "u32"}, {"nx_xp_data_len", "u32"}, - {"nx_spaceman_oid", "u64"}, {"nx_omap_oid", "u64"}, {"nx_reaper_oid", "u64"}, - {"nx_xp_test_type", "u32"}, {"nx_xp_max_file_systems", "u32"}, - } - - structEnd := apfsStructStart + StructSize(fields) - if structEnd > len(apfsData) { - return nil, fmt.Errorf("apfs not enough data for header") - } - - hdr, err := Parse(apfsData[apfsStructStart:structEnd], fields, "little") - if err != nil { - return nil, fmt.Errorf("apfs header parse failed") - } - - if hdr["block_size"] == 0 || hdr["block_count"] == 0 { - return nil, fmt.Errorf("apfs zero block size or count") - } - - allowedFeatureFlags := []int{0, 1, 2, 3} - allowedIncompatFlags := []int{0, 1, 2, 3, 0x100, 0x101, 0x102, 0x103} - allowedROCompatFlags := []int{0} - - if !containsInt(allowedFeatureFlags, hdr["nx_features"]) || - !containsInt(allowedROCompatFlags, hdr["nx_ro_compat_features"]) || - !containsInt(allowedIncompatFlags, hdr["nx_incompat_features"]) { - return nil, fmt.Errorf("apfs invalid feature flags") - } - - if hdr["nx_xp_test_type"] != 0 { - return nil, fmt.Errorf("apfs test_type must be null") - } - - fsCount := int(math.Ceil(float64(hdr["nx_xp_max_file_systems"]) / float64(fsCountBlockSize))) - if fsCount <= 0 || fsCount > maxFSCount { - return nil, fmt.Errorf("apfs invalid filesystem count") - } - - totalSize := hdr["block_count"] * hdr["block_size"] - truncatedMsg := "" - - if totalSize > available { - truncatedSize := totalSize - available - if truncatedSize == mbrBlockSize { - totalSize -= truncatedSize - truncatedMsg = fmt.Sprintf(" (truncated by %d bytes)", truncatedSize) - } - } - - return &SignatureResult{ - Offset: realOffset, - Size: totalSize, - Confidence: ConfidenceMedium, - Description: fmt.Sprintf("%s, block size: %d bytes, block count: %d, total size: %d bytes%s", - APFSDescription, hdr["block_size"], hdr["block_count"], totalSize, truncatedMsg), - }, nil -} - -// --------------------------------------------------------------------------- -// BTRFS -// --------------------------------------------------------------------------- - -const BTRFSDescription = "BTRFS file system" -const BTRFSMagicOffset = 0x10040 - -func btrfs_magic() [][]byte { - return [][]byte{ - []byte("_BHRfS_M"), - } -} - -func btrfs_parser(data []byte, offset int) (*SignatureResult, error) { - const superblockOffset = 0x10000 - const superblockEnd = superblockOffset + 0x1000 - const crcStart = 0x20 - - if offset < BTRFSMagicOffset { - return nil, fmt.Errorf("btrfs offset too small") - } - realOffset := offset - BTRFSMagicOffset - - btrfsData := data[realOffset:] - - if len(btrfsData) < superblockEnd { - return nil, fmt.Errorf("btrfs not enough data") - } - - fields := []StructField{ - {"header_checksum", "u32"}, {"unused1", "u32"}, {"unused2", "u64"}, - {"unused3", "u64"}, {"unused4", "u64"}, - {"uuid_p1", "u64"}, {"uuid_p2", "u64"}, - {"block_phys_addr", "u64"}, {"flags", "u64"}, - {"magic", "u64"}, {"generation", "u64"}, - {"root_tree_address", "u64"}, {"chunk_tree_address", "u64"}, - {"log_tree_address", "u64"}, {"log_root_transid", "u64"}, - {"total_bytes", "u64"}, {"bytes_used", "u64"}, - {"root_dir_objid", "u64"}, {"num_devices", "u64"}, - {"sector_size", "u32"}, {"node_size", "u32"}, - {"leaf_size", "u32"}, {"stripe_size", "u32"}, - } - - headerData := btrfsData[superblockOffset:superblockEnd] - hdr, err := Parse(headerData, fields, "little") - if err != nil { - return nil, fmt.Errorf("btrfs header parse failed") - } - - // Validate CRC32C - calcCRC := crc32c(headerData[crcStart:]) - if hdr["header_checksum"] != int(calcCRC) { - return nil, fmt.Errorf("btrfs crc mismatch") - } - - return &SignatureResult{ - Offset: realOffset, - Size: hdr["total_bytes"], - Confidence: ConfidenceMedium, - Description: fmt.Sprintf( - "%s, node size: %d, sector size: %d, leaf size: %d, stripe size: %d, bytes used: %d, total size: %d bytes", - BTRFSDescription, hdr["node_size"], hdr["sector_size"], - hdr["leaf_size"], hdr["stripe_size"], hdr["bytes_used"], hdr["total_bytes"]), - }, nil -} - -// crc32c computes a CRC-32C (Castagnoli) checksum. -func crc32c(data []byte) uint32 { - // CRC-32C polynomial: 0x1EDC6F41 - const poly = 0x82F63B78 // reversed representation - crc := uint32(0xFFFFFFFF) - for _, b := range data { - crc ^= uint32(b) - for i := 0; i < 8; i++ { - if crc&1 != 0 { - crc = (crc >> 1) ^ poly - } else { - crc >>= 1 - } - } - } - return crc ^ 0xFFFFFFFF -} - -// --------------------------------------------------------------------------- -// LogFS -// --------------------------------------------------------------------------- - -const LogFSDescription = "LogFS file system" -const LogFSMagicOffset = 0x18 - -func logfs_magic() [][]byte { - return [][]byte{ - {0x7A, 0x3A, 0x8E, 0x5C, 0xB9, 0xD5, 0xBF, 0x67}, - } -} - -func logfs_parser(data []byte, offset int) (*SignatureResult, error) { - if offset < LogFSMagicOffset { - return nil, fmt.Errorf("logfs offset too small") - } - realOffset := offset - LogFSMagicOffset - - logfsData := data[realOffset:] - - fields := []StructField{ - {"magic", "u64"}, {"crc32", "u32"}, - {"ifile_levels", "u8"}, {"iblock_levels", "u8"}, - {"data_levels", "u8"}, {"segment_shift", "u8"}, - {"block_shift", "u8"}, {"write_shift", "u8"}, - {"pad0", "u32"}, {"pad1", "u16"}, - {"filesystem_size", "u64"}, {"segment_size", "u32"}, - {"bad_seg_reserved", "u32"}, - {"feature_incompat", "u64"}, {"feature_ro_compat", "u64"}, - {"feature_compat", "u64"}, {"feature_flags", "u64"}, - {"root_reserve", "u64"}, {"speed_reserve", "u64"}, - } - - if LogFSMagicOffset >= len(logfsData) { - return nil, fmt.Errorf("logfs not enough data") - } - - hdr, err := Parse(logfsData[LogFSMagicOffset:], fields, "big") - if err != nil { - return nil, fmt.Errorf("logfs superblock parse failed") - } - - if hdr["pad0"] != 0 || hdr["pad1"] != 0 { - return nil, fmt.Errorf("logfs padding fields not zero") - } - - totalSize := hdr["filesystem_size"] - - return &SignatureResult{ - Offset: realOffset, - Size: totalSize, - Confidence: ConfidenceMedium, - Description: fmt.Sprintf("%s, total size: %d bytes", - LogFSDescription, totalSize), - }, nil -} - -// --------------------------------------------------------------------------- -// UBI -// --------------------------------------------------------------------------- - -const UBIImageDescription = "UBI image" - -func ubi_magic() [][]byte { - return [][]byte{ - {0x55, 0x42, 0x49, 0x23, 0x01}, // "UBI#" + version 1 - } -} - -func ubi_parser(data []byte, offset int) (*SignatureResult, error) { - ubiECFields := []StructField{ - {"magic", "u32"}, {"version", "u8"}, {"padding1", "u24"}, - {"ec", "u64"}, {"volume_id_header_offset", "u32"}, {"data_offset", "u32"}, - {"image_sequence_number", "u32"}, - {"padding2", "u64"}, {"padding3", "u64"}, {"padding4", "u64"}, {"padding5", "u64"}, - {"header_crc", "u32"}, - } - - ecHeaderSize := StructSize(ubiECFields) - crcDataSize := ecHeaderSize - 4 // minus the header_crc field - - ubiData := data[offset:] - ecHdr, err := Parse(ubiData, ubiECFields, "big") - if err != nil { - return nil, fmt.Errorf("ubi ec header parse failed") - } - - if ecHdr["data_offset"] < ecHeaderSize || ecHdr["volume_id_header_offset"] < ecHeaderSize { - return nil, fmt.Errorf("ubi invalid offsets") - } - - // Validate header CRC - if crcDataSize > len(ubiData) { - return nil, fmt.Errorf("ubi not enough data for crc") - } - if ubiCRC(ubiData[:crcDataSize]) != ecHdr["header_crc"] { - return nil, fmt.Errorf("ubi ec header crc mismatch") - } - - dataOffset := offset + ecHdr["data_offset"] - volumeOffset := offset + ecHdr["volume_id_header_offset"] - if dataOffset >= len(data) || volumeOffset >= len(data) { - return nil, fmt.Errorf("ubi offsets out of bounds") - } - - // Get image size by scanning volume headers - imageSize, err := getUBIImageSize(data[offset:]) - if err != nil { - return nil, err - } - - return &SignatureResult{ - Offset: offset, - Size: imageSize, - Confidence: ConfidenceHigh, - Description: fmt.Sprintf("%s, version: %d, image size: %d bytes", - UBIImageDescription, ecHdr["version"], imageSize), - }, nil -} - -func getUBIImageSize(ubiData []byte) (int, error) { - volMagic := []byte{0x55, 0x42, 0x49, 0x21, 0x01} // "UBI!" + version 1 - - ubiVolFields := []StructField{ - {"magic", "u32"}, {"version", "u8"}, {"volume_type", "u8"}, - {"copy_flag", "u8"}, {"compat_type", "u8"}, - {"volume_id", "u32"}, {"logical_erase_block_number", "u32"}, - {"padding1", "u32"}, {"data_size", "u32"}, - {"used_erase_block_count", "u32"}, {"data_padding_size", "u32"}, - {"data_crc", "u32"}, {"padding2", "u32"}, - {"sequence_number", "u64"}, - {"padding3", "u64"}, {"padding4", "u32"}, {"header_crc", "u32"}, - } - - volHeaderSize := StructSize(ubiVolFields) - volCRCDataSize := volHeaderSize - 4 - - blockCount := 0 - previousVolumeOffset := 0 - possibleLEBSizes := make(map[int]int) - - // Scan for volume headers - for i := 0; i <= len(ubiData)-len(volMagic); { - idx := bytes.Index(ubiData[i:], volMagic) - if idx < 0 { - break - } - thisVolumeOffset := i + idx - - if thisVolumeOffset+volHeaderSize > len(ubiData) { - break - } - - volHdr, err := Parse(ubiData[thisVolumeOffset:], ubiVolFields, "big") - if err == nil && - volHdr["padding1"] == 0 && volHdr["padding2"] == 0 && - volHdr["padding3"] == 0 && volHdr["padding4"] == 0 { - // Validate CRC - if thisVolumeOffset+volCRCDataSize <= len(ubiData) { - if ubiCRC(ubiData[thisVolumeOffset:thisVolumeOffset+volCRCDataSize]) == volHdr["header_crc"] { - blockCount++ - if previousVolumeOffset != 0 { - lebSize := thisVolumeOffset - previousVolumeOffset - possibleLEBSizes[lebSize]++ - } - previousVolumeOffset = thisVolumeOffset - } - } - } - i = thisVolumeOffset + 1 - } - - // Pick the most common LEB size - lebSize := 0 - bestCount := 0 - for size, count := range possibleLEBSizes { - if count > bestCount { - lebSize = size - bestCount = count - } - } - - if lebSize != 0 && blockCount != 0 { - return blockCount * lebSize, nil - } - return 0, fmt.Errorf("ubi could not determine image size") -} - -// --------------------------------------------------------------------------- -// UBIFS -// --------------------------------------------------------------------------- - -const UBIFSDescription = "UBIFS image" - -func ubifs_magic() [][]byte { - return [][]byte{ - {0x31, 0x18, 0x10, 0x06}, - } -} - -func ubifs_parser(data []byte, offset int) (*SignatureResult, error) { - const maxGroupType = 2 - const crcStartOffset = 8 - const superblockNodeType = 6 - const superblockStructExtraSize = 3968 - - ubiSBFields := []StructField{ - {"magic", "u32"}, {"header_crc", "u32"}, - {"sequence_number", "u64"}, {"node_len", "u32"}, - {"node_type", "u8"}, {"group_type", "u8"}, - {"padding1", "u32"}, {"key_hash", "u8"}, {"key_format", "u8"}, - {"flags", "u32"}, {"min_io_size", "u32"}, - {"leb_size", "u32"}, {"leb_count", "u32"}, {"max_leb_count", "u32"}, - {"max_bud_bytes", "u64"}, {"log_lebs", "u32"}, {"lpt_lebs", "u32"}, - {"orph_lebs", "u32"}, {"jhead_count", "u32"}, {"fanout", "u32"}, - {"lsave_count", "u32"}, {"fmt_version", "u32"}, - {"default_compression", "u16"}, {"padding2", "u16"}, - {"rp_uid", "u32"}, {"rp_gid", "u32"}, {"rp_size", "u64"}, - {"time_gran", "u32"}, {"uuid_p1", "u64"}, {"uuid_p2", "u64"}, - {"ro_compat_version", "u32"}, - } - - sbStructSize := StructSize(ubiSBFields) + superblockStructExtraSize - - ubisData := data[offset:] - hdr, err := Parse(ubisData, ubiSBFields, "little") - if err != nil { - return nil, fmt.Errorf("ubifs superblock parse failed") - } - - if hdr["padding1"] != 0 || hdr["padding2"] != 0 { - return nil, fmt.Errorf("ubifs padding fields not zero") - } - if hdr["node_type"] != superblockNodeType { - return nil, fmt.Errorf("ubifs not a superblock node") - } - if hdr["group_type"] > maxGroupType { - return nil, fmt.Errorf("ubifs invalid group type") - } - - // Validate header CRC - if crcStartOffset+sbStructSize-crcStartOffset > len(ubisData) { - return nil, fmt.Errorf("ubifs not enough data for crc") - } - crcData := ubisData[crcStartOffset:sbStructSize] - if ubiCRC(crcData) != hdr["header_crc"] { - return nil, fmt.Errorf("ubifs header crc mismatch") - } - - imageSize := hdr["leb_count"] * hdr["leb_size"] - - return &SignatureResult{ - Offset: offset, - Size: imageSize, - Confidence: ConfidenceHigh, - Description: fmt.Sprintf("%s, total size: %d bytes", - UBIFSDescription, imageSize), - }, nil -} - -// ubiCRC computes a UBI checksum: NOT(CRC32(data)) & 0xFFFFFFFF -func ubiCRC(data []byte) int { - const ubiCRCInit = uint32(0xFFFFFFFF) - return int((^CRC32(data)) & ubiCRCInit) -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -func containsInt(slice []int, val int) bool { - for _, s := range slice { - if s == val { - return true - } - } - return false -} diff --git a/internal/leviathan/binsnoop/sig_firmware.go b/internal/leviathan/binsnoop/sig_firmware.go deleted file mode 100644 index e214ed5..0000000 --- a/internal/leviathan/binsnoop/sig_firmware.go +++ /dev/null @@ -1,1333 +0,0 @@ -package binsnoop - -import ( - "encoding/hex" - "fmt" -) - -// ============================================================================ -// uImage -// ============================================================================ - -const uimageDescription = "uImage firmware image" - -func uimageMagic() [][]byte { - return [][]byte{ - {0x27, 0x05, 0x19, 0x56}, - []byte("OKLI"), - } -} - -func uimageParser(data []byte, offset int) (*SignatureResult, error) { - if offset+64 > len(data) { - return nil, fmt.Errorf("insufficient data for uImage header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_crc", "u32"}, {"timestamp", "u32"}, - {"data_size", "u32"}, {"load_address", "u32"}, {"entry_point", "u32"}, - {"data_crc", "u32"}, {"os_type", "u8"}, {"cpu_type", "u8"}, - {"image_type", "u8"}, {"compression_type", "u8"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - headerSize := 64 - dataSize := parsed["data_size"] - if dataSize == 0 { - return nil, fmt.Errorf("uImage data size is 0") - } - totalSize := headerSize + dataSize - if offset+totalSize > len(data) { - return nil, fmt.Errorf("uImage extends beyond EOF") - } - - // Validate header CRC: zero out the header_crc field, calc CRC32 over 64 bytes - headerCopy := make([]byte, headerSize) - copy(headerCopy, hdr[:headerSize]) - headerCopy[4] = 0 - headerCopy[5] = 0 - headerCopy[6] = 0 - headerCopy[7] = 0 - calcCRC := CRC32(headerCopy) - headerCRCValid := calcCRC == uint32(parsed["header_crc"]) - - osMap := map[int]string{0: "OpenBSD", 1: "NetBSD", 2: "FreeBSD", 3: "4.4BSD", 4: "Linux", 5: "SVR4", 6: "Esix", 7: "Solaris", 8: "Irix", 9: "SCO", 10: "Dell", 11: "NCR", 12: "LynxOS", 13: "VxWorks", 14: "pSOS", 15: "QNX", 16: "U-Boot", 17: "RTEMS", 18: "ARTOS", 19: "Unity OS", 20: "INTEGRITY", 21: "OSE", 22: "Plan 9", 23: "OpenRTOS", 24: "ARM Trusted Firmware", 25: "Trusted Execution Environment", 26: "RISC-V OpenSBI", 27: "EFI Firmware"} - cpuMap := map[int]string{0: "Invalid", 1: "Alpha", 2: "ARM", 3: "Intel x86", 4: "IA64", 5: "MIPS", 6: "MIPS 64-bit", 7: "PowerPC", 8: "IBM S390", 9: "SuperH", 10: "Sparc", 11: "Sparc 64-bit", 12: "M68K", 13: "Nios-32", 14: "MicroBlaze", 15: "Nios-II", 16: "Blackfin", 17: "AVR32", 18: "ST200", 19: "Sandbox", 20: "NDS32", 21: "OpenRISC 1000", 22: "ARM64", 23: "ARC", 24: "x86_64", 25: "Xtensa", 26: "RISC-V"} - compMap := map[int]string{0: "none", 1: "gzip", 2: "bzip2", 3: "lzma", 4: "lzo", 5: "lz4", 6: "zstd"} - imgMap := map[int]string{0: "Invalid", 1: "Standalone", 2: "Kernel", 3: "RAMDisk", 4: "Multi-File", 5: "Firmware", 6: "Script", 7: "Filesystem", 8: "Flat DT", 9: "Kwb", 10: "IMX", 11: "AIS", 12: "Kernel (no load)", 13: "PBL", 14: "MXS", 15: "GP Header", 16: "ATMEL", 17: "IMX (Silicon Labs)", 18: "TEE", 19: "FW IVT", 20: "PMC", 21: "STM32", 22: "Renesas SPKG", 23: "ST SunXI", 24: "FDT Legacy"} - - osType := lookupMap(osMap, parsed["os_type"], "Unknown") - cpuType := lookupMap(cpuMap, parsed["cpu_type"], "Unknown") - compType := lookupMap(compMap, parsed["compression_type"], "Unknown") - imageType := lookupMap(imgMap, parsed["image_type"], "Unknown") - name := GetCString(hdr[32:64]) - - confidence := ConfidenceHigh - desc := fmt.Sprintf("%s, header size: %d bytes, data size: %d bytes, compression: %s, CPU: %s, OS: %s, image type: %s, load address: 0x%X, entry point: 0x%X, creation time: %s, image name: \"%s\"", - uimageDescription, headerSize, dataSize, compType, cpuType, osType, imageType, - parsed["load_address"], parsed["entry_point"], - EpochToString(uint32(parsed["timestamp"])), name) - - if !headerCRCValid { - if offset == 0 { - confidence = ConfidenceMedium - } else { - confidence = ConfidenceLow - } - desc += ", invalid checksum" - } - - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: desc, - Confidence: confidence, - ExtractionDeclined: !headerCRCValid || dataSize == 0, - }, nil -} - -// ============================================================================ -// U-Boot version string -// ============================================================================ - -const ubootDescription = "U-Boot version string" - -func ubootMagic() [][]byte { - return [][]byte{[]byte("U-Boot ")} -} - -func ubootParser(data []byte, offset int) (*SignatureResult, error) { - numOff := offset + 7 - if numOff >= len(data) { - return nil, fmt.Errorf("insufficient data") - } - if !IsASCIINumber(data[numOff]) { - return nil, fmt.Errorf("expected version number") - } - ver := GetCString(data[numOff:]) - if len(ver) == 0 { - return nil, fmt.Errorf("empty version string") - } - if len(ver) > 100 { - ver = ver[:100] - } - return &SignatureResult{ - Offset: offset, - Size: len(ver), - Description: fmt.Sprintf("%s: %s", ubootDescription, ver), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// TRX -// ============================================================================ - -const trxDescription = "TRX firmware image" - -func trxMagic() [][]byte { - return [][]byte{[]byte("HDR0")} -} - -func trxParser(data []byte, offset int) (*SignatureResult, error) { - if offset+28 > len(data) { - return nil, fmt.Errorf("insufficient data for TRX header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"total_size", "u32"}, {"header_crc", "u32"}, - {"version", "u16"}, {"flags", "u16"}, - {"partition1", "u32"}, {"partition2", "u32"}, {"partition3", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - totalSize := parsed["total_size"] - if totalSize <= 28 || offset+totalSize > len(data) { - return nil, fmt.Errorf("invalid TRX size") - } - // CRC32 validation (over data after the CRC field) - if offset+12 < len(data) { - calcCRC := CRC32(hdr[12:totalSize]) - if int(calcCRC) != parsed["header_crc"] { - return nil, fmt.Errorf("TRX CRC mismatch") - } - } - partCount := 0 - for _, p := range []string{"partition1", "partition2", "partition3"} { - if parsed[p] > 0 { - partCount++ - } - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, version %d, partition count: %d, header size: 28 bytes, total size: %d bytes", trxDescription, parsed["version"], partCount, totalSize), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// TP-Link firmware header -// ============================================================================ - -const tplinkDescription = "TP-Link firmware header" - -func tplinkMagic() [][]byte { - return [][]byte{ - {0x01, 0x00, 0x00, 0x00, 0x54, 0x50, 0x2D, 0x4C, 0x49, 0x4E, 0x4B, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6E, 0x6F, 0x6C, 0x6F, 0x67, 0x69, 0x65, 0x73, 0x00, 0x00, 0x00, 0x00, 0x76, 0x65, 0x72, 0x2E, 0x20, 0x31, 0x2E, 0x30}, - } -} - -func tplinkParser(data []byte, offset int) (*SignatureResult, error) { - if offset+0x200 > len(data) { - return nil, fmt.Errorf("insufficient data for TP-Link header") - } - hdr := data[offset:] - fields := []StructField{ - {"version", "u32"}, {"fw_version", "u32"}, {"hw_id", "u32"}, {"hw_rev", "u32"}, - {"unk1", "u32"}, {"kernel_data_offset", "u32"}, {"kernel_load_address", "u32"}, - {"kernel_entry_point", "u32"}, {"kernel_data_length", "u32"}, - } - parsed, err := Parse(hdr[0x40:], fields, "big") - if err != nil { - return nil, err - } - headerSize := 0x200 - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf("%s, kernel load address: 0x%X, kernel entry point: 0x%X, header size: %d bytes", tplinkDescription, parsed["kernel_load_address"], parsed["kernel_entry_point"], headerSize), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// TP-Link RTOS firmware -// ============================================================================ - -const tplinkRTOSDescription = "TP-Link RTOS firmware" - -func tplinkRTOSMagic() [][]byte { - return [][]byte{{0x00, 0x14, 0x2F, 0xC0}} -} - -func tplinkRTOSParser(data []byte, offset int) (*SignatureResult, error) { - if offset+0x200 > len(data) { - return nil, fmt.Errorf("insufficient data for TP-Link RTOS header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_size", "u32"}, {"total_size", "u32"}, - {"model_number", "u32"}, {"hw_rev_major", "u32"}, {"hw_rev_minor", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - return &SignatureResult{ - Offset: offset, - Size: parsed["header_size"], - Description: fmt.Sprintf("%s, model number: %X, hardware version: %X.%X, header size: %d bytes, total size: %d bytes", tplinkRTOSDescription, parsed["model_number"], parsed["hw_rev_major"], parsed["hw_rev_minor"], parsed["header_size"], parsed["total_size"]), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// DLOB firmware header -// ============================================================================ - -const dlobDescription = "DLOB firmware header" - -func dlobMagic() [][]byte { - return [][]byte{{0x5e, 0xa3, 0xa4, 0x17}} -} - -func dlobParser(data []byte, offset int) (*SignatureResult, error) { - if offset+16 > len(data) { - return nil, fmt.Errorf("insufficient data for DLOB header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_size", "u32"}, {"data_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - availableData := len(data) - offset - headerSize := parsed["header_size"] - dataSize := parsed["data_size"] - if availableData < headerSize+dataSize { - return nil, fmt.Errorf("DLOB size exceeds available data") - } - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d", dlobDescription, headerSize, dataSize), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// Arcadyan obfuscated LZMA -// ============================================================================ - -const arcadyanDescription = "Arcadyan obfuscated LZMA" - -func arcadyanMagic() [][]byte { - return [][]byte{{0x00, 0xD5, 0x08, 0x00}} -} - -func arcadyanParser(data []byte, offset int) (*SignatureResult, error) { - const magicOffset = 0x68 - if offset < magicOffset { - return nil, fmt.Errorf("arcadyan magic offset too small") - } - startOffset := offset - magicOffset - return &SignatureResult{ - Offset: startOffset, - Description: arcadyanDescription, - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// CHK firmware header (Netgear) -// ============================================================================ - -const chkDescription = "CHK firmware header" - -func chkMagic() [][]byte { - return [][]byte{{0x2A, 0x23, 0x24, 0x5E}} -} - -func chkParser(data []byte, offset int) (*SignatureResult, error) { - if offset+40 > len(data) { - return nil, fmt.Errorf("insufficient data for CHK header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_size", "u32"}, {"reserved", "u32"}, - {"kernel_checksum", "u32"}, {"rootfs_checksum", "u32"}, - {"kernel_size", "u32"}, {"rootfs_size", "u32"}, - {"image_checksum", "u32"}, {"header_checksum", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - kernelSize := parsed["kernel_size"] - rootfsSize := parsed["rootfs_size"] - totalSize := headerSize + kernelSize + rootfsSize - availableData := len(data) - offset - if availableData < totalSize || totalSize <= headerSize { - return nil, fmt.Errorf("invalid CHK sizes") - } - boardID := GetCString(hdr[40:]) - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf("%s, board ID: %s, header size: %d bytes, data size: %d bytes", chkDescription, boardID, headerSize, kernelSize+rootfsSize), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// SEAMA firmware header -// ============================================================================ - -const seamaDescription = "SEAMA firmware header" - -func seamaMagic() [][]byte { - return [][]byte{ - {0x5E, 0xA3, 0xA4, 0x17, 0x00, 0x00}, - {0x17, 0xA4, 0xA3, 0x5E, 0x00, 0x00}, - } -} - -func seamaParser(data []byte, offset int) (*SignatureResult, error) { - if offset+12 > len(data) { - return nil, fmt.Errorf("insufficient data for SEAMA header") - } - hdr := data[offset:] - endianness := "big" - if hdr[0] == 0x17 { - endianness = "little" - } - fields := []StructField{ - {"magic", "u32"}, {"meta_size", "u16"}, {"data_size", "u32"}, - } - parsed, err := Parse(hdr, fields, endianness) - if err != nil { - return nil, err - } - headerSize := 12 + parsed["meta_size"] - dataSize := parsed["data_size"] - totalSize := headerSize + dataSize - if offset+totalSize > len(data) { - return nil, fmt.Errorf("SEAMA size exceeds data") - } - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d bytes", seamaDescription, headerSize, dataSize), - Confidence: ConfidenceLow, - }, nil -} - -// ============================================================================ -// CFE bootloader -// ============================================================================ - -const cfeDescription = "CFE bootloader" - -func cfeMagic() [][]byte { - return [][]byte{[]byte("CFE1CFE1")} -} - -func cfeParser(_ []byte, offset int) (*SignatureResult, error) { - const cfeMagicOffset = 28 - if offset < cfeMagicOffset { - return nil, fmt.Errorf("CFE magic offset too small") - } - confidence := ConfidenceLow - startOffset := offset - cfeMagicOffset - if startOffset == 0 { - confidence = ConfidenceMedium - } - return &SignatureResult{ - Offset: startOffset, - Description: cfeDescription, - Confidence: confidence, - }, nil -} - -// ============================================================================ -// JBOOT ARM firmware -// ============================================================================ - -const jbootARMDescription = "JBOOT firmware header" - -func jbootARMMagic() [][]byte { - return [][]byte{ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x48}, - } -} - -func jbootARMParser(data []byte, offset int) (*SignatureResult, error) { - const magicOff = 48 - if offset < magicOff { - return nil, fmt.Errorf("JBOOT ARM magic offset too small") - } - headerStart := offset - magicOff - if headerStart+80 > len(data) { - return nil, fmt.Errorf("insufficient data for JBOOT ARM header") - } - hdr := data[headerStart:] - fields := []StructField{ - {"rom_id_size", "u32"}, {"rom_id", "u32"}, {"product_id", "u32"}, - {"func_id", "u32"}, {"erase_offset", "u32"}, {"erase_size", "u32"}, - {"data_offset", "u32"}, {"data_size", "u32"}, - } - parsed, err := Parse(hdr[20:], fields, "big") - if err != nil { - return nil, err - } - headerSize := 80 - return &SignatureResult{ - Offset: headerStart, - Size: headerSize, - Description: fmt.Sprintf("%s, header size: %d bytes, ROM ID: %d, erase offset: 0x%X, erase size: 0x%X, data flash offset: 0x%X, data size: 0x%X", jbootARMDescription, headerSize, parsed["rom_id"], parsed["erase_offset"], parsed["erase_size"], parsed["data_offset"], parsed["data_size"]), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// JBOOT STAG header -// ============================================================================ - -const jbootSTAGDescription = "JBOOT STAG header" - -func jbootSTAGMagic() [][]byte { - return [][]byte{ - {0x04, 0x04, 0x24, 0x2B}, - {0xFF, 0x04, 0x24, 0x2B}, - } -} - -func jbootSTAGParser(data []byte, offset int) (*SignatureResult, error) { - if offset+16 > len(data) { - return nil, fmt.Errorf("insufficient data for JBOOT STAG header") - } - hdr := data[offset:] - fields := []StructField{ - {"tag", "u32"}, {"header_size", "u16"}, {"flags", "u16"}, {"image_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - imageSize := parsed["image_size"] - if offset+headerSize+imageSize >= len(data) { - return nil, fmt.Errorf("JBOOT STAG size exceeds data") - } - imageType := "factory image" - if parsed["flags"]&0x01 != 0 { - imageType = "system upgrade image" - } - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf("%s, %s, header size: %d bytes, kernel data size: %d bytes", jbootSTAGDescription, imageType, headerSize, imageSize), - Confidence: ConfidenceLow, - }, nil -} - -// ============================================================================ -// JBOOT SCH2 header -// ============================================================================ - -const jbootSCH2Description = "JBOOT SCH2 header" - -func jbootSCH2Magic() [][]byte { - return [][]byte{ - {0x24, 0x21, 0x00, 0x02}, - {0x24, 0x21, 0x01, 0x02}, - {0x24, 0x21, 0x02, 0x02}, - {0x24, 0x21, 0x03, 0x02}, - } -} - -func jbootSCH2Parser(data []byte, offset int) (*SignatureResult, error) { - if offset+32 > len(data) { - return nil, fmt.Errorf("insufficient data for JBOOT SCH2 header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_size", "u32"}, {"kernel_size", "u32"}, - {"kernel_checksum", "u32"}, {"kernel_entry_point", "u32"}, - {"compression", "u8"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - compTypes := map[int]string{0: "none", 1: "gzip", 2: "bzip2", 3: "lzma"} - comp := lookupMap(compTypes, parsed["compression"], "unknown") - headerSize := parsed["header_size"] - kernelSize := parsed["kernel_size"] - totalSize := headerSize + kernelSize - if offset+totalSize > len(data) { - return nil, fmt.Errorf("JBOOT SCH2 extends beyond EOF") - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, header size: %d bytes, kernel size: %d bytes, kernel compression: %s, kernel entry point: 0x%X", jbootSCH2Description, headerSize, kernelSize, comp, parsed["kernel_entry_point"]), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// PackImg firmware header -// ============================================================================ - -const packimgDescription = "PackImg firmware header" - -func packimgMagic() [][]byte { - return [][]byte{[]byte("--PaCkImGs--")} -} - -func packimgParser(data []byte, offset int) (*SignatureResult, error) { - if offset+24 > len(data) { - return nil, fmt.Errorf("insufficient data for PackImg header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic1", "u32"}, {"magic2", "u32"}, {"magic3", "u32"}, - {"data_size", "u32"}, {"header_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - dataSize := parsed["data_size"] - if len(data)-offset < headerSize+dataSize { - return nil, fmt.Errorf("PackImg size exceeds data") - } - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d bytes", packimgDescription, headerSize, dataSize), - Confidence: ConfidenceLow, - }, nil -} - -// ============================================================================ -// BIN firmware header -// ============================================================================ - -const binhdrDescription = "BIN firmware header" - -func binhdrMagic() [][]byte { - return [][]byte{[]byte("U2ND")} -} - -func binhdrParser(data []byte, offset int) (*SignatureResult, error) { - const magicOff = 14 - if offset < magicOff { - return nil, fmt.Errorf("BIN header magic offset too small") - } - startOffset := offset - magicOff - if startOffset+24 > len(data) { - return nil, fmt.Errorf("insufficient data for BIN header") - } - hdr := data[startOffset:] - boardID := GetCString(hdr[:14]) - fields := []StructField{ - {"magic", "u32"}, {"hw_rev", "u16"}, {"fw_major", "u8"}, {"fw_minor", "u8"}, - } - parsed, err := Parse(hdr[14:], fields, "big") - if err != nil { - return nil, err - } - return &SignatureResult{ - Offset: startOffset, - Description: fmt.Sprintf("%s, board ID: %s, hardware revision: %d, firmware version: %d.%d", binhdrDescription, boardID, parsed["hw_rev"], parsed["fw_major"], parsed["fw_minor"]), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// Autel obfuscated firmware -// ============================================================================ - -const autelDescription = "Autel obfuscated firmware" - -func autelMagic() [][]byte { - return [][]byte{[]byte("ECC0101\x00")} -} - -func autelParser(data []byte, offset int) (*SignatureResult, error) { - if offset+16 > len(data) { - return nil, fmt.Errorf("insufficient data for Autel header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u64"}, {"data_size", "u32"}, {"header_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - dataSize := parsed["data_size"] - totalSize := headerSize + dataSize - if offset+totalSize > len(data) { - return nil, fmt.Errorf("Autel size exceeds data") - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d, total size: %d", autelDescription, headerSize, dataSize, totalSize), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// D-Link TLV firmware -// ============================================================================ - -const dlinkTLVDescription = "D-Link TLV firmware" - -func dlinkTLVMagic() [][]byte { - return [][]byte{{0x64, 0x80, 0x19, 0x40}} -} - -func dlinkTLVParser(data []byte, offset int) (*SignatureResult, error) { - if offset+32 > len(data) { - return nil, fmt.Errorf("insufficient data for D-Link TLV header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_size", "u32"}, {"data_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - dataSize := parsed["data_size"] - totalSize := headerSize + dataSize - if offset+totalSize > len(data) { - return nil, fmt.Errorf("D-Link TLV size exceeds data") - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, header size: %d bytes, data size: %d bytes", dlinkTLVDescription, headerSize, dataSize), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// DLKE encrypted firmware -// ============================================================================ - -const dlkeDescription = "DLK encrypted firmware" - -func dlkeMagic() [][]byte { - return [][]byte{ - []byte("DLK6E8202001"), - []byte("DLK6E6110002"), - } -} - -func dlkeParser(data []byte, offset int) (*SignatureResult, error) { - if offset+80 > len(data) { - return nil, fmt.Errorf("insufficient data for DLKE header") - } - // Parse first JBOOT-like header - hdr1 := data[offset:] - fields := []StructField{ - {"rom_id_size", "u32"}, {"rom_id", "u32"}, {"product_id", "u32"}, - {"func_id", "u32"}, {"erase_offset", "u32"}, {"erase_size", "u32"}, - {"data_offset", "u32"}, {"data_size", "u32"}, - } - parsed1, err := Parse(hdr1[20:], fields, "big") - if err != nil { - return nil, err - } - hdr1Size := 80 - sig1DataSize := parsed1["data_size"] - - nextOffset := offset + hdr1Size + sig1DataSize - if nextOffset+80 > len(data) { - return nil, fmt.Errorf("DLKE second header beyond EOF") - } - hdr2 := data[nextOffset:] - parsed2, err := Parse(hdr2[20:], fields, "big") - if err != nil { - return nil, err - } - hdr2Size := 80 - cryptDataSize := parsed2["data_size"] - - totalSize := hdr1Size + sig1DataSize + hdr2Size + cryptDataSize - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, signature size: %d bytes, encrypted data size: %d bytes", dlkeDescription, sig1DataSize, cryptDataSize), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// SHRS encrypted firmware -// ============================================================================ - -const shrsDescription = "SHRS encrypted firmware" - -func shrsMagic() [][]byte { - return [][]byte{[]byte("SHRS")} -} - -func shrsParser(data []byte, offset int) (*SignatureResult, error) { - if offset+32 > len(data) { - return nil, fmt.Errorf("insufficient data for SHRS header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_size", "u32"}, {"data_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - dataSize := parsed["data_size"] - totalSize := headerSize + dataSize - if offset+totalSize > len(data) { - return nil, fmt.Errorf("SHRS size exceeds data") - } - // Extract IV (16 bytes after the fixed header) - ivStr := "" - if headerSize >= 28 && offset+28 <= len(data) { - iv := hdr[12:28] - ivStr = hex.EncodeToString(iv) - } - confidence := ConfidenceLow - if offset == 0 { - confidence = ConfidenceMedium - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, header size: %d bytes, encrypted data size: %d bytes, IV: %s", shrsDescription, headerSize, dataSize, ivStr), - Confidence: confidence, - }, nil -} - -// ============================================================================ -// DKBS firmware header -// ============================================================================ - -const dkbsDescription = "DKBS firmware header" - -func dkbsMagic() [][]byte { - return [][]byte{[]byte("_dkbs_")} -} - -func dkbsParser(data []byte, offset int) (*SignatureResult, error) { - const magicOff = 7 - if offset < magicOff { - return nil, fmt.Errorf("DKBS magic offset too small") - } - startOffset := offset - magicOff - if startOffset+64 > len(data) { - return nil, fmt.Errorf("insufficient data for DKBS header") - } - hdr := data[startOffset:] - fields := []StructField{ - {"version", "u32"}, {"magic", "u32"}, {"padding", "u32"}, - {"header_size", "u32"}, {"data_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - dataSize := parsed["data_size"] - availableData := len(data) - startOffset - if availableData < headerSize+dataSize { - return nil, fmt.Errorf("DKBS size exceeds data") - } - confidence := ConfidenceMedium - if startOffset == 0 { - confidence = ConfidenceHigh - } - boardID := GetCString(hdr[24:]) - return &SignatureResult{ - Offset: startOffset, - Size: headerSize, - Description: fmt.Sprintf("%s, board ID: %s, header size: %d bytes, data size: %d", dkbsDescription, boardID, headerSize, dataSize), - Confidence: confidence, - }, nil -} - -// ============================================================================ -// DMS firmware image -// ============================================================================ - -const dmsDescription = "DMS firmware image" - -func dmsMagic() [][]byte { - return [][]byte{[]byte("0><1")} -} - -func dmsParser(data []byte, offset int) (*SignatureResult, error) { - const magicOff = 4 - const minSize = 0x100 - if offset < magicOff { - return nil, fmt.Errorf("DMS magic offset too small") - } - startOffset := offset - magicOff - if startOffset+minSize > len(data) { - return nil, fmt.Errorf("insufficient data for DMS header") - } - - // DMS images have byte-swapped u16 pairs - dmsData := make([]byte, minSize) - copy(dmsData, data[startOffset:startOffset+minSize]) - for i := 0; i+1 < len(dmsData); i += 2 { - dmsData[i], dmsData[i+1] = dmsData[i+1], dmsData[i] - } - - fields := []StructField{ - {"magic", "u32"}, {"unk1", "u32"}, {"image_size", "u32"}, - } - parsed, err := Parse(dmsData, fields, "big") - if err != nil { - return nil, err - } - imageSize := parsed["image_size"] - if imageSize <= 0 || startOffset+imageSize > len(data) { - return nil, fmt.Errorf("invalid DMS image size") - } - return &SignatureResult{ - Offset: startOffset, - Size: imageSize, - Description: fmt.Sprintf("%s, total size: %d bytes", dmsDescription, imageSize), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// Known encrypted firmware (encfw) -// ============================================================================ - -const encfwDescription = "Known encrypted firmware" - -func encfwKnownFirmware() map[string]string { - return map[string]string{ - "\xdf\x8c\x39\x0d": "D-Link DIR-822 rev C", - "\x35\x66\x6f\x68": "D-Link DAP-1665", - "\xf5\x2a\xa0\xb4": "D-Link DIR-842 rev C", - "\xe3\x13\x00\x5b": "D-Link DIR-850 rev A", - "\x0a\x14\xe4\x24": "D-Link DIR-850 rev B", - } -} - -func encfwMagic() [][]byte { - var magics [][]byte - for k := range encfwKnownFirmware() { - magics = append(magics, []byte(k)) - } - return magics -} - -func encfwParser(data []byte, offset int) (*SignatureResult, error) { - if offset+4 > len(data) { - return nil, fmt.Errorf("insufficient data for encfw") - } - key := string(data[offset : offset+4]) - known := encfwKnownFirmware() - model, ok := known[key] - if !ok { - return nil, fmt.Errorf("unknown encfw magic") - } - confidence := ConfidenceMedium - if offset != 0 { - confidence = ConfidenceLow - } - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, %s", encfwDescription, model), - Confidence: confidence, - }, nil -} - -// ============================================================================ -// D-Link encrpted_img -// ============================================================================ - -const encrptedImgDescription = "D-Link Encrpted Image" - -func encrptedImgMagic() [][]byte { - return [][]byte{[]byte("encrpted_img")} -} - -func encrptedImgParser(_ []byte, offset int) (*SignatureResult, error) { - confidence := ConfidenceMedium - if offset != 0 { - confidence = ConfidenceLow - } - return &SignatureResult{ - Offset: offset, - Description: encrptedImgDescription, - Confidence: confidence, - }, nil -} - -// ============================================================================ -// Android boot image -// ============================================================================ - -const androidBootimgDescription = "Android boot image" - -func androidBootimgMagic() [][]byte { - return [][]byte{[]byte("ANDROID!")} -} - -func androidBootimgParser(data []byte, offset int) (*SignatureResult, error) { - if offset+40 > len(data) { - return nil, fmt.Errorf("insufficient data for Android boot image header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u64"}, {"kernel_size", "u32"}, {"kernel_load_addr", "u32"}, - {"ramdisk_size", "u32"}, {"ramdisk_load_addr", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - confidence := ConfidenceLow - if offset == 0 { - confidence = ConfidenceMedium - } - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, kernel size: %d bytes, kernel load address: 0x%X, ramdisk size: %d bytes, ramdisk load address: 0x%X", androidBootimgDescription, parsed["kernel_size"], parsed["kernel_load_addr"], parsed["ramdisk_size"], parsed["ramdisk_load_addr"]), - Confidence: confidence, - }, nil -} - -// ============================================================================ -// Android sparse image -// ============================================================================ - -const androidSparseDescription = "Android sparse image" - -func androidSparseMagic() [][]byte { - return [][]byte{{0x3A, 0xFF, 0x26, 0xED}} -} - -func androidSparseParser(data []byte, offset int) (*SignatureResult, error) { - if offset+28 > len(data) { - return nil, fmt.Errorf("insufficient data for Android sparse header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"major_version", "u16"}, {"minor_version", "u16"}, - {"header_size", "u16"}, {"chunk_header_size", "u16"}, - {"block_size", "u32"}, {"total_blocks", "u32"}, - {"total_chunks", "u32"}, {"checksum", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf("%s, version %d.%d, header size: %d, block size: %d, chunk count: %d", androidSparseDescription, parsed["major_version"], parsed["minor_version"], headerSize, parsed["block_size"], parsed["total_chunks"]), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// Windows CE binary image -// ============================================================================ - -const winceDescription = "Windows CE binary image" - -func winceMagic() [][]byte { - return [][]byte{[]byte("B000FF\n")} -} - -func winceParser(data []byte, offset int) (*SignatureResult, error) { - if offset+16 > len(data) { - return nil, fmt.Errorf("insufficient data for WinCE header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic1", "u32"}, {"magic2", "u32"}, - {"base_address", "u32"}, {"image_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - return &SignatureResult{ - Offset: offset, - Size: parsed["image_size"], - Description: fmt.Sprintf("%s, base address: 0x%X, image size: %d bytes", winceDescription, parsed["base_address"], parsed["image_size"]), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// D-Link MH01 firmware -// ============================================================================ - -const mh01Description = "D-Link MH01 firmware image" - -func mh01Magic() [][]byte { - return [][]byte{[]byte("MH01")} -} - -func mh01Parser(data []byte, offset int) (*SignatureResult, error) { - if offset+32 > len(data) { - return nil, fmt.Errorf("insufficient data for MH01 header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_size", "u32"}, {"total_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - totalSize := parsed["total_size"] - if offset+totalSize > len(data) { - return nil, fmt.Errorf("MH01 size exceeds data") - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, total size: %d bytes", mh01Description, totalSize), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// CSman DAT -// ============================================================================ - -const csmanDescription = "CSman DAT file" - -func csmanMagic() [][]byte { - return [][]byte{[]byte("SC"), []byte("CS")} -} - -func csmanParser(data []byte, offset int) (*SignatureResult, error) { - if offset+8 > len(data) { - return nil, fmt.Errorf("insufficient data for CSman header") - } - hdr := data[offset:] - endianness := "big" - if hdr[0] == 'C' && hdr[1] == 'S' { - endianness = "little" - } - fields := []StructField{ - {"magic", "u16"}, {"count", "u16"}, {"size", "u32"}, - } - parsed, err := Parse(hdr, fields, endianness) - if err != nil { - return nil, err - } - totalSize := parsed["size"] - if totalSize <= 0 || offset+totalSize > len(data) { - return nil, fmt.Errorf("invalid CSman size") - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, total size: %d bytes", csmanDescription, totalSize), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// DXBC (DirectX shader bytecode) -// ============================================================================ - -const dxbcDescription = "DirectX shader bytecode" - -func dxbcMagic() [][]byte { - return [][]byte{[]byte("DXBC")} -} - -func dxbcParser(data []byte, offset int) (*SignatureResult, error) { - if offset+32 > len(data) { - return nil, fmt.Errorf("insufficient data for DXBC header") - } - hdr := data[offset:] - // DXBC header: 4-byte magic, 16-byte hash, 4-byte version, 4-byte total_size, 4-byte chunk_count - fields := []StructField{ - {"magic", "u32"}, {"hash1", "u32"}, {"hash2", "u32"}, - {"hash3", "u32"}, {"hash4", "u32"}, - {"version", "u32"}, {"total_size", "u32"}, {"chunk_count", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - totalSize := parsed["total_size"] - if totalSize <= 32 || offset+totalSize > len(data) { - return nil, fmt.Errorf("invalid DXBC size") - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, total size: %d bytes", dxbcDescription, totalSize), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// DTB (Device Tree Blob) -// ============================================================================ - -const dtbDescription = "Device tree blob (DTB)" - -func dtbMagic() [][]byte { - return [][]byte{{0xD0, 0x0D, 0xFE, 0xED}} -} - -func dtbParser(data []byte, offset int) (*SignatureResult, error) { - if offset+40 > len(data) { - return nil, fmt.Errorf("insufficient data for DTB header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"total_size", "u32"}, {"struct_offset", "u32"}, - {"strings_offset", "u32"}, {"mem_reserve_offset", "u32"}, - {"version", "u32"}, {"last_comp_version", "u32"}, - {"cpu_id", "u32"}, {"strings_size", "u32"}, {"struct_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - totalSize := parsed["total_size"] - structEnd := offset + parsed["struct_offset"] + parsed["struct_size"] - stringsEnd := offset + parsed["strings_offset"] + parsed["strings_size"] - if structEnd > len(data) || stringsEnd > len(data) { - return nil, fmt.Errorf("DTB offsets exceed data") - } - if totalSize <= 0 || offset+totalSize > len(data) { - return nil, fmt.Errorf("invalid DTB total size") - } - return &SignatureResult{ - Offset: offset, - Size: totalSize, - Description: fmt.Sprintf("%s, version: %d, CPU ID: %d, total size: %d bytes", dtbDescription, parsed["version"], parsed["cpu_id"], totalSize), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// Matter OTA firmware -// ============================================================================ - -const matterOTADescription = "Matter OTA firmware" - -func matterOTAMagic() [][]byte { - return [][]byte{{0x1e, 0xf1, 0xee, 0x1b}} -} - -func matterOTAParser(data []byte, offset int) (*SignatureResult, error) { - if offset+32 > len(data) { - return nil, fmt.Errorf("insufficient data for Matter OTA header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"total_size", "u32"}, {"header_size", "u32"}, - {"header_tlv_size", "u32"}, {"vendor_id", "u16"}, {"product_id", "u16"}, - {"version", "u32"}, {"min_version", "u32"}, - {"max_version", "u32"}, {"payload_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - headerSize := parsed["header_size"] - return &SignatureResult{ - Offset: offset, - Size: headerSize, - Description: fmt.Sprintf("%s, total size: %d bytes, header size: %d bytes, vendor id: 0x%x, product id: 0x%x, version: %d, payload size: %d bytes", matterOTADescription, parsed["total_size"], headerSize, parsed["vendor_id"], parsed["product_id"], parsed["version"], parsed["payload_size"]), - Confidence: ConfidenceHigh, - }, nil -} - -// ============================================================================ -// RTK firmware header -// ============================================================================ - -const rtkDescription = "RTK firmware header" - -func rtkMagic() [][]byte { - return [][]byte{[]byte("RTK0")} -} - -func rtkParser(data []byte, offset int) (*SignatureResult, error) { - if offset+16 > len(data) { - return nil, fmt.Errorf("insufficient data for RTK header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"header_size", "u32"}, {"image_size", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - availableData := len(data) - offset - if parsed["image_size"] != availableData { - return nil, fmt.Errorf("RTK image size mismatch") - } - return &SignatureResult{ - Offset: offset, - Size: parsed["header_size"], - Description: fmt.Sprintf("%s, header size: %d bytes, image size: %d", rtkDescription, parsed["header_size"], parsed["image_size"]), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// DPAPI blob data -// ============================================================================ - -const dpapiDescription = "DPAPI blob data" - -func dpapiMagic() [][]byte { - return [][]byte{ - {0x01, 0x00, 0x00, 0x00, 0xD0, 0x8c, 0x9d, 0xdf, 0x01, 0x15, 0xd1, 0x11, 0x8c, 0x7a, 0x00, 0xc0, 0x4f, 0xc2, 0x97, 0xeb}, - } -} - -func dpapiParser(data []byte, offset int) (*SignatureResult, error) { - if offset+44 > len(data) { - return nil, fmt.Errorf("insufficient data for DPAPI blob header") - } - hdr := data[offset:] - fields := []StructField{ - {"version", "u32"}, {"provider_guid1", "u32"}, {"provider_guid2", "u32"}, - {"provider_guid3", "u32"}, {"provider_guid4", "u32"}, - {"mk_version", "u32"}, {"mk_guid1", "u32"}, {"mk_guid2", "u32"}, - {"mk_guid3", "u32"}, {"mk_guid4", "u32"}, {"flags", "u32"}, - } - parsed, err := Parse(hdr, fields, "little") - if err != nil { - return nil, err - } - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, version: %d, flags: %d", dpapiDescription, parsed["version"], parsed["flags"]), - Confidence: ConfidenceMedium, - }, nil -} - -// ============================================================================ -// QEMU QCOW Image -// ============================================================================ - -const qcowDescription = "QEMU QCOW Image" - -func qcowMagic() [][]byte { - return [][]byte{[]byte("QFI\xFB")} -} - -func qcowParser(data []byte, offset int) (*SignatureResult, error) { - if offset+24 > len(data) { - return nil, fmt.Errorf("insufficient data for QCOW header") - } - hdr := data[offset:] - fields := []StructField{ - {"magic", "u32"}, {"version", "u32"}, {"backing_file_offset", "u64"}, - {"backing_file_size", "u32"}, {"cluster_bits", "u32"}, - {"disk_size", "u64"}, {"encryption_method", "u32"}, - } - parsed, err := Parse(hdr, fields, "big") - if err != nil { - return nil, err - } - encMap := map[int]string{0: "none", 1: "AES", 2: "LUKS"} - enc := lookupMap(encMap, parsed["encryption_method"], "unknown") - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, version: %d, storage media size: 0x%x bytes, cluster block size: 0x%x bytes, encryption method: %s", qcowDescription, parsed["version"], parsed["disk_size"], 1<= len(data) { - return nil, fmt.Errorf("PNG: not enough data for header") - } - - // Walk chunks starting after the 8-byte PNG header - chunkData := data[offset+pngHeaderLen:] - available := len(chunkData) - chunkOffset := 0 - var previousOffset *int - - for IsOffsetSafe(available, chunkOffset, previousOffset) { - if chunkOffset+chunkHeaderSize > available { - break - } - hdr, err := Parse(chunkData[chunkOffset:], chunkFields, "big") - if err != nil { - break - } - isLast := hdr["type"] == iendChunkType - totalChunkSize := chunkHeaderSize + hdr["length"] + crcSize - prev := chunkOffset - previousOffset = &prev - chunkOffset += totalChunkSize - if isLast { - pngSize := pngHeaderLen + chunkOffset - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, total size: %d bytes", PNGDescription, pngSize), - Confidence: ConfidenceHigh, - Size: pngSize, - }, nil - } - } - - return nil, fmt.Errorf("PNG: failed to walk chunks to IEND") -} - -// --------------------------------------------------------------------------- -// JPEG -// --------------------------------------------------------------------------- - -const JPEGDescription = "JPEG image" - -func JPEGMagic() [][]byte { - return [][]byte{ - {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00}, // JFIF - {0xFF, 0xD8, 0xFF, 0xE1}, // EXIF - {0xFF, 0xD8, 0xFF, 0xDB}, // DQT - } -} - -func JPEGParser(data []byte, offset int) (*SignatureResult, error) { - const markerMagic byte = 0xFF - const sosMarker byte = 0xDA - const eofMarker byte = 0xD9 - - noLengthMarkers := []byte{ - 0x00, 0x01, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, eofMarker, - } - sosSkipMarkers := []byte{ - 0x00, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, - } - - jpegData := data[offset:] - nextMarkerOffset := 0 - - for { - if nextMarkerOffset >= len(jpegData) { - break - } - magic := jpegData[nextMarkerOffset] - if magic != markerMagic { - break - } - nextMarkerOffset++ - - if nextMarkerOffset >= len(jpegData) { - break - } - markerID := jpegData[nextMarkerOffset] - nextMarkerOffset++ - - // If this marker has a length field - if !byteInSlice(markerID, noLengthMarkers) { - if nextMarkerOffset+2 > len(jpegData) { - break - } - segLen := int(jpegData[nextMarkerOffset])<<8 | int(jpegData[nextMarkerOffset+1]) - nextMarkerOffset += segLen - } - - // SOS marker: scan until next valid marker - if markerID == sosMarker { - for { - if nextMarkerOffset+2 > len(jpegData) { - break - } - if jpegData[nextMarkerOffset] == markerMagic && !byteInSlice(jpegData[nextMarkerOffset+1], sosSkipMarkers) { - break - } - nextMarkerOffset++ - } - } - - // EOF marker - if markerID == eofMarker { - jpegSize := nextMarkerOffset - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, total size: %d bytes", JPEGDescription, jpegSize), - Confidence: ConfidenceMedium, - Size: jpegSize, - }, nil - } - } - - return nil, fmt.Errorf("JPEG: failed to find EOI marker") -} - -// --------------------------------------------------------------------------- -// BMP -// --------------------------------------------------------------------------- - -const BMPDescription = "BMP image (Bitmap)" - -func BMPMagic() [][]byte { - return [][]byte{ - {0x42, 0x4D}, // "BM" - } -} - -func BMPParser(data []byte, offset int) (*SignatureResult, error) { - const bmpFileHeaderSize = 14 - - bmpHeaderFields := []StructField{ - {"bfType", "u16"}, - {"bfSize", "u32"}, - {"bfReserved1", "u16"}, - {"bfReserved2", "u16"}, - {"bfOffBits", "u32"}, - } - - validDIBHeaderSizes := []int{12, 40, 108, 124} - - bmpData := data[offset:] - if len(bmpData) < StructSize(bmpHeaderFields) { - return nil, fmt.Errorf("BMP: not enough data for header") - } - - hdr, err := Parse(bmpData, bmpHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("BMP: parse error: %w", err) - } - - bmpDataSize := len(bmpData) - - // The BMP file size cannot be bigger than available data - if bmpDataSize < hdr["bfSize"] { - return nil, fmt.Errorf("BMP: reported size %d exceeds available data %d", hdr["bfSize"], bmpDataSize) - } - if hdr["bfSize"] == 0 { - return nil, fmt.Errorf("BMP: file size is 0") - } - if hdr["bfOffBits"] == 0 { - return nil, fmt.Errorf("BMP: bitmap bits offset is 0") - } - if hdr["bfOffBits"] > bmpDataSize { - return nil, fmt.Errorf("BMP: bitmap bits offset exceeds available data") - } - - // Validate DIB header size - if offset+bmpFileHeaderSize+4 > len(data) { - return nil, fmt.Errorf("BMP: not enough data for DIB header size") - } - dibHeaderSize := int(LE32(data, offset+bmpFileHeaderSize)) - validDIB := false - for _, vs := range validDIBHeaderSizes { - if dibHeaderSize == vs { - validDIB = true - break - } - } - if !validDIB { - return nil, fmt.Errorf("BMP: invalid DIB header size %d", dibHeaderSize) - } - - // The offset that points to the image data cannot point into the second header - if hdr["bfOffBits"] < bmpFileHeaderSize+dibHeaderSize { - return nil, fmt.Errorf("BMP: bitmap bits offset points into header") - } - - totalSize := hdr["bfSize"] - return &SignatureResult{ - Offset: offset, - Name: "bmp", - Description: fmt.Sprintf("BMP image, total size: %d", totalSize), - Confidence: ConfidenceMedium, - Size: totalSize, - }, nil -} - -// --------------------------------------------------------------------------- -// GIF -// --------------------------------------------------------------------------- - -const GIFDescription = "GIF image" - -func GIFMagic() [][]byte { - return [][]byte{ - {0x47, 0x49, 0x46, 0x38, 0x37, 0x61}, // GIF87a - {0x47, 0x49, 0x46, 0x38, 0x39, 0x61}, // GIF89a - } -} - -func GIFParser(data []byte, offset int) (*SignatureResult, error) { - gifHeaderFields := []StructField{ - {"magic_p1", "u24"}, - {"magic_p2", "u24"}, - {"image_width", "u16"}, - {"image_height", "u16"}, - {"flags", "u8"}, - {"bg_color_index", "u8"}, - {"aspect_ratio", "u8"}, - } - - gifData := data[offset:] - if len(gifData) < StructSize(gifHeaderFields) { - return nil, fmt.Errorf("GIF: not enough data for header") - } - - hdr, err := Parse(gifData, gifHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("GIF: parse error: %w", err) - } - - // Calculate header size including optional global color table - headerSize := StructSize(gifHeaderFields) + gifColorTableSize(hdr["flags"]) - - if offset+headerSize > len(data) { - return nil, fmt.Errorf("GIF: not enough data for header + color table") - } - - // Walk GIF blocks to find terminator - blockData := data[offset+headerSize:] - gifDataSize, err := getGIFDataSize(blockData) - if err != nil { - return nil, fmt.Errorf("GIF: %w", err) - } - - totalSize := headerSize + gifDataSize - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, %dx%d pixels, total size: %d bytes", GIFDescription, hdr["image_width"], hdr["image_height"], totalSize), - Confidence: ConfidenceHigh, - Size: totalSize, - }, nil -} - -func gifColorTableSize(flags int) int { - const hasColorTable = 0x80 - const colorTableSizeMask = 0x07 - - if flags&hasColorTable == 0 { - return 0 - } - encodedSize := (flags & colorTableSizeMask) + 1 - size := 3 - for i := 0; i < encodedSize; i++ { - size *= 2 - } - return size -} - -func getGIFDataSize(gifData []byte) (int, error) { - const extensionBlock byte = 0x21 - const terminatorBlock byte = 0x3B - const imageDescriptorBlock byte = 0x2C - - nextOffset := 0 - var previousOffset *int - available := len(gifData) - - for IsOffsetSafe(available, nextOffset, previousOffset) { - if nextOffset >= available { - break - } - blockType := gifData[nextOffset] - - var blockSize int - var err error - - switch blockType { - case imageDescriptorBlock: - blockSize, err = parseGIFImageDescriptor(gifData[nextOffset:]) - case extensionBlock: - blockSize, err = parseGIFExtension(gifData[nextOffset:]) - case terminatorBlock: - return nextOffset + 1, nil - default: - return 0, fmt.Errorf("unknown GIF block type 0x%02X at offset %d", blockType, nextOffset) - } - - if err != nil { - return 0, err - } - prev := nextOffset - previousOffset = &prev - nextOffset += blockSize - } - - return 0, fmt.Errorf("failed to find GIF terminator block") -} - -func parseGIFImageDescriptor(gifData []byte) (int, error) { - const lzwCodeSize = 1 - - imgDescFields := []StructField{ - {"magic", "u8"}, - {"image_left", "u16"}, - {"image_top", "u16"}, - {"image_width", "u16"}, - {"image_height", "u16"}, - {"flags", "u8"}, - } - - if len(gifData) < StructSize(imgDescFields) { - return 0, fmt.Errorf("GIF image descriptor: not enough data") - } - - hdr, err := Parse(gifData, imgDescFields, "little") - if err != nil { - return 0, err - } - - totalSize := StructSize(imgDescFields) + gifColorTableSize(hdr["flags"]) + lzwCodeSize - - if totalSize > len(gifData) { - return 0, fmt.Errorf("GIF image descriptor: not enough data for sub-blocks") - } - - subBlocksSize, err := parseGIFSubBlocks(gifData[totalSize:]) - if err != nil { - return 0, err - } - totalSize += subBlocksSize - return totalSize, nil -} - -func parseGIFExtension(extensionData []byte) (int, error) { - const plainText = 1 - const application = 0xFF - const headerSize = 2 - - extFields := []StructField{ - {"magic", "u8"}, - {"extension_type", "u8"}, - {"sub_block_offset", "u8"}, - } - - if len(extensionData) < StructSize(extFields) { - return 0, fmt.Errorf("GIF extension: not enough data") - } - - hdr, err := Parse(extensionData, extFields, "little") - if err != nil { - return 0, err - } - - subBlocksOffset := headerSize - extType := hdr["extension_type"] - if extType == application || extType == plainText { - subBlocksOffset += hdr["sub_block_offset"] + 1 - } - - if subBlocksOffset > len(extensionData) { - return 0, fmt.Errorf("GIF extension: sub-blocks offset exceeds data") - } - - subBlocksSize, err := parseGIFSubBlocks(extensionData[subBlocksOffset:]) - if err != nil { - return 0, err - } - return subBlocksOffset + subBlocksSize, nil -} - -func parseGIFSubBlocks(subBlockData []byte) (int, error) { - const subBlockTerminator byte = 0 - - available := len(subBlockData) - nextOffset := 0 - var previousOffset *int - - for IsOffsetSafe(available, nextOffset, previousOffset) { - if nextOffset >= available { - break - } - subBlockSize := subBlockData[nextOffset] - if subBlockSize == subBlockTerminator { - return nextOffset + 1, nil - } - prev := nextOffset - previousOffset = &prev - nextOffset += int(subBlockSize) + 1 - } - - return 0, fmt.Errorf("GIF sub-blocks: terminator not found") -} - -// --------------------------------------------------------------------------- -// SVG -// --------------------------------------------------------------------------- - -const SVGDescription = "SVG image" - -const svgOpenTag = "' and declare the SVG xmlns (which the loop below - // requires anyway via headTagCount). Verifying this up front, with a cap - // on how far we look for the closing '>', keeps the parser O(1) per call - // regardless of file size — so a buffer densely packed with " maxRootTagLen { - rootLimit = maxRootTagLen - } - rootEnd := bytes.IndexByte(svgData[:rootLimit], '>') - if rootEnd < 0 { - return nil, fmt.Errorf("SVG: root tag not terminated") - } - if !bytes.Contains(svgData[:rootEnd+1], headMagic) { - return nil, fmt.Errorf("SVG: root tag missing SVG namespace") - } - - headTagCount := 0 - unclosedSVGTags := 0 - - // Search for all occurrences - searchIdx := 0 - for searchIdx < len(svgData) { - // Find next occurrence of either tag - openIdx := bytes.Index(svgData[searchIdx:], openTag) - closeIdx := bytes.Index(svgData[searchIdx:], closeTag) - - var tagStart int - if openIdx == -1 && closeIdx == -1 { - break - } else if openIdx == -1 { - tagStart = searchIdx + closeIdx - } else if closeIdx == -1 { - tagStart = searchIdx + openIdx - } else if openIdx <= closeIdx { - tagStart = searchIdx + openIdx - } else { - tagStart = searchIdx + closeIdx - } - - tagBytes := svgData[tagStart:] - - // Find the end '>' of this tag - endIdx := bytes.IndexByte(tagBytes, '>') - if endIdx == -1 { - break - } - fullTag := tagBytes[:endIdx+1] - - isOpen := bytes.HasPrefix(fullTag, openTag) - isClose := bytes.HasPrefix(fullTag, closeTag) - isHead := bytes.Contains(fullTag, headMagic) - - if isHead { - headTagCount++ - } - if isOpen { - unclosedSVGTags++ - } - if isClose { - unclosedSVGTags-- - } - - // More than one head tag is invalid - if headTagCount > 1 { - break - } - - // If one head tag and all tags closed, that's EOF - if headTagCount == 1 && unclosedSVGTags == 0 { - totalSize := tagStart + len(svgCloseTag) - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, total size: %d bytes", SVGDescription, totalSize), - Confidence: ConfidenceMedium, - Size: totalSize, - }, nil - } - - searchIdx = tagStart + 1 - } - - return nil, fmt.Errorf("SVG: failed to find matching closing tag") -} - -// --------------------------------------------------------------------------- -// RIFF -// --------------------------------------------------------------------------- - -const RIFFDescription = "RIFF image" - -func RIFFMagic() [][]byte { - return [][]byte{ - {0x52, 0x49, 0x46, 0x46}, // "RIFF" - } -} - -func RIFFParser(data []byte, offset int) (*SignatureResult, error) { - const riffMagicValue = 0x46464952 // "RIFF" in LE - const chunkTypeStart = 8 - const chunkTypeEnd = 12 - const fileSizeOffset = 8 - - riffFields := []StructField{ - {"magic", "u32"}, - {"file_size", "u32"}, - {"chunk_type", "u32"}, - } - - riffData := data[offset:] - if len(riffData) < StructSize(riffFields) { - return nil, fmt.Errorf("RIFF: not enough data for header") - } - - hdr, err := Parse(riffData, riffFields, "little") - if err != nil { - return nil, fmt.Errorf("RIFF: parse error: %w", err) - } - - if hdr["magic"] != riffMagicValue { - return nil, fmt.Errorf("RIFF: invalid magic") - } - - if chunkTypeEnd > len(riffData) { - return nil, fmt.Errorf("RIFF: not enough data for chunk type string") - } - - // Get the RIFF type string (e.g., "WAVE", "AVI ") - typeBytes := riffData[chunkTypeStart:chunkTypeEnd] - typeString := strings.TrimSpace(string(typeBytes)) - - // Validate that the type string is valid UTF-8 / printable - for _, b := range typeBytes { - if b < 0x20 || b > 0x7E { - return nil, fmt.Errorf("RIFF: non-printable chunk type") - } - } - - totalSize := hdr["file_size"] + fileSizeOffset - - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, encoding type: %s, total size: %d bytes", RIFFDescription, typeString, totalSize), - Confidence: ConfidenceMedium, - Size: totalSize, - }, nil -} - -// --------------------------------------------------------------------------- -// PDF -// --------------------------------------------------------------------------- - -const PDFDescription = "PDF document" - -func PDFMagic() [][]byte { - return [][]byte{ - {0x25, 0x50, 0x44, 0x46, 0x2D, 0x31, 0x2E}, // "%PDF-1." - } -} - -func PDFParser(data []byte, offset int) (*SignatureResult, error) { - const minPDFSize = 16 - const newlineOffset = 8 - const minorNumberOffset = 7 - - const asciiZero byte = 0x30 - const asciiNine byte = 0x39 - const asciiNewline byte = 0x0A - const asciiPercent byte = 0x25 - const asciiCarriageReturn byte = 0x0D - - if offset+minPDFSize > len(data) { - return nil, fmt.Errorf("PDF: not enough data for header") - } - - pdfHeader := data[offset : offset+minPDFSize] - - // Get the minor version number - versionMinor := pdfHeader[minorNumberOffset] - if versionMinor < asciiZero || versionMinor > asciiNine { - return nil, fmt.Errorf("PDF: invalid minor version number") - } - - // After the version line, expect newline(s) followed by '%' - for _, b := range pdfHeader[newlineOffset:] { - if b == asciiNewline || b == asciiCarriageReturn { - continue - } else if b == asciiPercent { - return &SignatureResult{ - Offset: offset, - Description: fmt.Sprintf("%s, version 1.%d", PDFDescription, versionMinor-asciiZero), - Confidence: ConfidenceLow, - Size: 0, - }, nil - } else { - break - } - } - - return nil, fmt.Errorf("PDF: invalid header format") -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -func byteInSlice(b byte, s []byte) bool { - return bytes.ContainsRune(s, rune(b)) -} diff --git a/internal/leviathan/binsnoop/sig_misc.go b/internal/leviathan/binsnoop/sig_misc.go deleted file mode 100644 index b353db1..0000000 --- a/internal/leviathan/binsnoop/sig_misc.go +++ /dev/null @@ -1,1399 +0,0 @@ -package binsnoop - -import ( - "bytes" - "fmt" - "strings" -) - -// ---- Copyright ---- - -const DescriptionCopyright = "Copyright text" - -// copyrightMagic returns magic bytes for copyright strings. -func copyrightMagic() [][]byte { - return [][]byte{ - []byte("copyright"), - []byte("Copyright"), - []byte("COPYRIGHT"), - } -} - -// copyrightParser validates copyright text signatures. -func copyrightParser(data []byte, offset int) (*SignatureResult, error) { - const magicSize = 9 - - result := &SignatureResult{ - Offset: offset, - Description: DescriptionCopyright, - Confidence: ConfidenceHigh, - } - - copyrightStr := GetCString(data[offset:]) - if len(copyrightStr) <= magicSize { - return nil, fmt.Errorf("copyright string too short") - } - - result.Size = len(copyrightStr) - truncated := copyrightStr - if len(truncated) > 100 { - truncated = truncated[:100] - } - result.Description = fmt.Sprintf("%s: \"%s\"", result.Description, truncated) - return result, nil -} - -// ---- VxWorks ---- - -const ( - DescriptionVxWorksSymTab = "VxWorks symbol table" - DescriptionWINDKernelVersion = "VxWorks WIND kernel version" -) - -// windKernelMagic returns magic bytes for VxWorks WIND kernel version. -func windKernelMagic() [][]byte { - return [][]byte{ - []byte("WIND version "), - } -} - -// vxworksSymtabMagic returns magic bytes for VxWorks symbol tables. -func vxworksSymtabMagic() [][]byte { - return [][]byte{ - {0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - {0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, - } -} - -// windKernelParser validates VxWorks WIND kernel version signatures. -func windKernelParser(data []byte, offset int) (*SignatureResult, error) { - const magicSize = 13 - - result := &SignatureResult{ - Offset: offset, - Description: DescriptionWINDKernelVersion, - Confidence: ConfidenceLow, - } - - versionOffset := offset + magicSize - if versionOffset >= len(data) { - return nil, fmt.Errorf("WIND kernel version offset out of bounds") - } - - versionStr := GetCString(data[versionOffset:]) - if versionStr == "" { - return nil, fmt.Errorf("WIND kernel version string empty") - } - - result.Size = magicSize + len(versionStr) - result.Description = fmt.Sprintf("%s %s", result.Description, versionStr) - return result, nil -} - -// vxworksSymtabParser validates VxWorks symbol table signatures. -func vxworksSymtabParser(data []byte, offset int) (*SignatureResult, error) { - const symtabMagicOffset = 8 - - result := &SignatureResult{ - Description: DescriptionVxWorksSymTab, - Confidence: ConfidenceHigh, - } - - if offset < symtabMagicOffset { - return nil, fmt.Errorf("VxWorks symbol table: offset too small") - } - - symtabStart := offset - symtabMagicOffset - - // Determine endianness from the type field at offset 9 from symtab start - endianness := "little" - typeFieldOffset := symtabStart + 9 - if typeFieldOffset < len(data) && data[typeFieldOffset] == 0 { - endianness = "big" - } - - // Validate the first symbol table entry - symtabFields := []StructField{ - {"name_ptr", "u32"}, - {"value_ptr", "u32"}, - {"type", "u32"}, - {"group", "u32"}, - } - entrySize := StructSize(symtabFields) - - allowedTypes := map[int]string{ - 0x500: "function", - 0x700: "initialized data", - 0x900: "uninitialized data", - } - - if symtabStart+entrySize > len(data) { - return nil, fmt.Errorf("VxWorks symbol table: insufficient data") - } - - entry, err := Parse(data[symtabStart:], symtabFields, endianness) - if err != nil { - return nil, fmt.Errorf("VxWorks symbol table: parse failed") - } - - if _, ok := allowedTypes[entry["type"]]; !ok { - return nil, fmt.Errorf("VxWorks symbol table: invalid type %#x", entry["type"]) - } - if entry["name_ptr"] == 0 || entry["value_ptr"] == 0 { - return nil, fmt.Errorf("VxWorks symbol table: null pointers") - } - - result.Offset = symtabStart - result.Size = entrySize - result.Description = fmt.Sprintf("%s, total size: %d bytes", result.Description, result.Size) - return result, nil -} - -// ---- eCos ---- - -const DescriptionECosExceptionHandler = "eCos kernel exception handler" - -// ecosExceptionHandlerMagic returns magic bytes for eCos exception handlers (MIPS). -func ecosExceptionHandlerMagic() [][]byte { - return [][]byte{ - {0x00, 0x68, 0x1A, 0x40, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x5A, 0x33}, - {0x00, 0x68, 0x1A, 0x40, 0x7F, 0x00, 0x5A, 0x33}, - {0x40, 0x1A, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x5A, 0x00, 0x7F}, - {0x40, 0x1A, 0x68, 0x00, 0x33, 0x5A, 0x00, 0x7F}, - } -} - -// ecosExceptionHandlerParser validates eCos exception handler signatures. -func ecosExceptionHandlerParser(data []byte, offset int) (*SignatureResult, error) { - result := &SignatureResult{ - Offset: offset, - Description: DescriptionECosExceptionHandler, - Confidence: ConfidenceLow, - } - - endianness := "big" - if offset < len(data) && data[offset] == 0 { - endianness = "little" - } - - result.Description = fmt.Sprintf("%s, MIPS %s endian", result.Description, endianness) - return result, nil -} - -// ---- Linux ---- - -const ( - DescriptionLinuxARMzImage = "Linux ARM boot executable zImage" - DescriptionLinuxBootImage = "Linux kernel boot image" - DescriptionLinuxKernelVersion = "Linux kernel version" - DescriptionLinuxARM64BootImage = "Linux kernel ARM64 boot image" -) - -// linuxBootImageMagic returns magic bytes for Linux boot images. -func linuxBootImageMagic() [][]byte { - return [][]byte{ - {0xb8, 0xc0, 0x07, 0x8e, 0xd8, 0xb8, 0x00, 0x90, 0x8e, 0xc0, 0xb9, 0x00, 0x01, 0x29, 0xf6, 0x29}, - } -} - -// linuxKernelVersionMagic returns magic bytes for Linux kernel version strings. -func linuxKernelVersionMagic() [][]byte { - return [][]byte{ - []byte("Linux version "), - } -} - -// linuxARM64BootImageMagic returns magic bytes for Linux ARM64 boot images. -func linuxARM64BootImageMagic() [][]byte { - return [][]byte{ - {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x52, 0x4D, 0x64}, // "\x00\x00\x00\x00\x00\x00\x00\x00ARMd" - } -} - -// linuxARMzImageMagic returns magic bytes for Linux ARM zImage. -func linuxARMzImageMagic() [][]byte { - return [][]byte{ - {0x18, 0x28, 0x6F, 0x01}, - {0x01, 0x6F, 0x28, 0x18}, - } -} - -// linuxARMzImageParser validates Linux ARM zImage signatures. -func linuxARMzImageParser(data []byte, offset int) (*SignatureResult, error) { - const magicOffset = 36 - const nopLE = 0xE1A00000 - const nopBE = 0x0000A0E1 - - result := &SignatureResult{ - Confidence: ConfidenceMedium, - Description: DescriptionLinuxARMzImage, - } - - if offset < magicOffset { - return nil, fmt.Errorf("ARM zImage: offset too small") - } - - result.Offset = offset - magicOffset - zimageData := data[result.Offset:] - - // Parse 8 NOP instructions at the start - nopFields := []StructField{ - {"nop1", "u32"}, {"nop2", "u32"}, {"nop3", "u32"}, {"nop4", "u32"}, - {"nop5", "u32"}, {"nop6", "u32"}, {"nop7", "u32"}, {"nop8", "u32"}, - } - - if len(zimageData) < StructSize(nopFields) { - return nil, fmt.Errorf("ARM zImage: insufficient data") - } - - nops, err := Parse(zimageData, nopFields, "little") - if err != nil { - return nil, fmt.Errorf("ARM zImage: parse failed") - } - - nop1 := nops["nop1"] - if nop1 != nops["nop2"] || nop1 != nops["nop3"] || nop1 != nops["nop4"] || - nop1 != nops["nop5"] || nop1 != nops["nop6"] || nop1 != nops["nop7"] || nop1 != nops["nop8"] { - return nil, fmt.Errorf("ARM zImage: NOP instructions not uniform") - } - - var endianness string - if nop1 == nopLE { - endianness = "little" - } else if nop1 == nopBE { - endianness = "big" - } else { - return nil, fmt.Errorf("ARM zImage: unrecognized NOP value") - } - - result.Description = fmt.Sprintf("%s, %s endian", result.Description, endianness) - return result, nil -} - -// linuxARM64BootImageParser validates Linux ARM64 boot image signatures. -func linuxARM64BootImageParser(data []byte, offset int) (*SignatureResult, error) { - const magicOffset = 0x30 - const flagsEndianMask = 1 - const bigEndianFlag = 1 - - result := &SignatureResult{ - Confidence: ConfidenceMedium, - Description: DescriptionLinuxARM64BootImage, - } - - if offset < magicOffset { - return nil, fmt.Errorf("ARM64 boot image: offset too small") - } - - result.Offset = offset - magicOffset - imgData := data[result.Offset:] - - bootImgFields := []StructField{ - {"code0", "u32"}, - {"code1", "u32"}, - {"image_load_offset", "u64"}, - {"image_size", "u64"}, - {"flags", "u64"}, - {"reserved1", "u64"}, - {"reserved2", "u64"}, - {"reserved3", "u64"}, - {"magic", "u32"}, - {"pe_offset", "u32"}, - } - - structSize := StructSize(bootImgFields) - if len(imgData) < structSize { - return nil, fmt.Errorf("ARM64 boot image: insufficient data") - } - - imgHeader, err := Parse(imgData, bootImgFields, "little") - if err != nil { - return nil, fmt.Errorf("ARM64 boot image: parse failed") - } - - if imgHeader["reserved1"] != 0 || imgHeader["reserved2"] != 0 || imgHeader["reserved3"] != 0 { - return nil, fmt.Errorf("ARM64 boot image: reserved fields not zero") - } - - peOffset := imgHeader["pe_offset"] - peEnd := peOffset + 2 - if peEnd > len(imgData) { - return nil, fmt.Errorf("ARM64 boot image: PE offset out of bounds") - } - if imgData[peOffset] != 'P' || imgData[peOffset+1] != 'E' { - return nil, fmt.Errorf("ARM64 boot image: PE signature not found") - } - - flags := imgHeader["flags"] - // Reserved bits are the upper 60 bits (mask 0xFFFFFFFFFFFFFFF0) - if (uint64(flags) & 0xFFFFFFFFFFFFFFF0) != 0 { - return nil, fmt.Errorf("ARM64 boot image: reserved flag bits set") - } - - endianness := "little" - if (flags & flagsEndianMask) == bigEndianFlag { - endianness = "big" - } - - imageSize := imgHeader["image_size"] - result.Size = structSize - result.Description = fmt.Sprintf("%s, %s endian, effective image size: %d bytes", - result.Description, endianness, imageSize) - return result, nil -} - -// linuxBootImageParser validates Linux boot image signatures. -func linuxBootImageParser(data []byte, offset int) (*SignatureResult, error) { - const hdrsOffset = 514 - const hdrsExpected = "!HdrS" - - result := &SignatureResult{ - Offset: offset, - Description: DescriptionLinuxBootImage, - } - - hdrsStart := offset + hdrsOffset - hdrsEnd := hdrsStart + len(hdrsExpected) - - if hdrsEnd > len(data) { - return nil, fmt.Errorf("Linux boot image: insufficient data for HdrS check") - } - - actual := string(data[hdrsStart:hdrsEnd]) - if actual != hdrsExpected { - return nil, fmt.Errorf("Linux boot image: HdrS signature not found") - } - - return result, nil -} - -// linuxKernelVersionParser validates Linux kernel version string signatures. -func linuxKernelVersionParser(data []byte, offset int) (*SignatureResult, error) { - const periodByte byte = 0x2E - const periodOffset1 = 15 - const periodOffset2 = 17 - const periodOffset3 = 18 - const minFileSize = 100 * 1024 - const minVersionStrLen = 75 - const gccVersionStr = "gcc " - - result := &SignatureResult{ - Offset: offset, - Confidence: ConfidenceLow, - } - - fileSize := len(data) - if fileSize <= minFileSize { - return nil, fmt.Errorf("Linux kernel version: file too small") - } - - kvString := GetCString(data[offset:]) - if len(kvString) <= minVersionStrLen { - return nil, fmt.Errorf("Linux kernel version: string too short") - } - - if !strings.Contains(kvString, gccVersionStr) { - return nil, fmt.Errorf("Linux kernel version: no gcc version string") - } - - if !strings.Contains(kvString, "@") { - return nil, fmt.Errorf("Linux kernel version: no ampersand") - } - - if !strings.HasSuffix(kvString, "\n") { - return nil, fmt.Errorf("Linux kernel version: does not end with newline") - } - - kvBytes := []byte(kvString) - if periodOffset1 >= len(kvBytes) || periodOffset3 >= len(kvBytes) { - return nil, fmt.Errorf("Linux kernel version: string too short for period checks") - } - - if kvBytes[periodOffset1] != periodByte || (kvBytes[periodOffset2] != periodByte && kvBytes[periodOffset3] != periodByte) { - return nil, fmt.Errorf("Linux kernel version: periods not at expected offsets") - } - - // Search for Linux kernel symbol table - symtabMagic := []byte("\x000\x001\x002\x003\x004\x005\x006\x007\x008\x009\x00") - symtabPresent := bytes.Count(data, symtabMagic) == 1 - - if symtabPresent { - result.Offset = 0 - result.Size = len(data) - } else { - result.Size = len(kvString) - result.ExtractionDeclined = true - } - - result.Description = fmt.Sprintf("%s, has symbol table: %t", - strings.TrimSpace(kvString), symtabPresent) - return result, nil -} - -// ---- EFI GPT ---- - -const DescriptionEFIGPT = "EFI Global Partition Table" - -// efigptMagic returns magic bytes for EFI GPT headers. -func efigptMagic() [][]byte { - return [][]byte{ - {0x55, 0xAA, 0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54}, // "\x55\xAAEFI PART" - } -} - -func lbaToOffset(lba int) int { - return lba * 512 -} - -// efigptParser validates EFI GPT header signatures. -func efigptParser(data []byte, offset int) (*SignatureResult, error) { - const magicOffset = 0x01FE - const blockSize = 512 - const expectedRevision = 0x00010000 - - result := &SignatureResult{ - Description: DescriptionEFIGPT, - Confidence: ConfidenceHigh, - } - - availableData := len(data) - offset - if offset < magicOffset { - return nil, fmt.Errorf("EFI GPT: offset too small") - } - - result.Offset = offset - magicOffset - efiData := data[result.Offset:] - - if len(efiData) <= blockSize { - return nil, fmt.Errorf("EFI GPT: insufficient data for GPT structure") - } - - gptData := efiData[blockSize:] - - gptFields := []StructField{ - {"magic", "u64"}, - {"revision", "u32"}, - {"header_size", "u32"}, - {"header_crc", "u32"}, - {"reserved", "u32"}, - {"my_lba", "u64"}, - {"alternate_lba", "u64"}, - {"first_usable_lba", "u64"}, - {"last_usable_lba", "u64"}, - {"disk_guid_p1", "u64"}, - {"disk_guid_p2", "u64"}, - {"partition_entry_lba", "u64"}, - {"partition_entry_count", "u32"}, - {"partition_entry_size", "u32"}, - {"partition_entries_crc", "u32"}, - } - - if len(gptData) < StructSize(gptFields) { - return nil, fmt.Errorf("EFI GPT: insufficient GPT header data") - } - - gptHeader, err := Parse(gptData, gptFields, "little") - if err != nil { - return nil, fmt.Errorf("EFI GPT: parse failed") - } - - if gptHeader["reserved"] != 0 { - return nil, fmt.Errorf("EFI GPT: reserved field not zero") - } - if gptHeader["revision"] != expectedRevision { - return nil, fmt.Errorf("EFI GPT: unexpected revision") - } - - partEntriesStart := lbaToOffset(gptHeader["partition_entry_lba"]) - partEntriesEnd := partEntriesStart + (gptHeader["partition_entry_count"] * gptHeader["partition_entry_size"]) - - if partEntriesEnd > len(efiData) || partEntriesStart >= partEntriesEnd { - return nil, fmt.Errorf("EFI GPT: partition entries out of bounds") - } - - partEntriesData := efiData[partEntriesStart:partEntriesEnd] - - // Validate partition entries CRC - if CRC32(partEntriesData) != uint32(gptHeader["partition_entries_crc"]) { - return nil, fmt.Errorf("EFI GPT: partition entries CRC mismatch") - } - - // Parse partition entries to find total size - entryFields := []StructField{ - {"type_guid_p1", "u64"}, - {"type_guid_p2", "u64"}, - {"partition_guid_p1", "u64"}, - {"partition_guid_p2", "u64"}, - {"starting_lba", "u64"}, - {"ending_lba", "u64"}, - {"attributes", "u64"}, - } - - entrySize := gptHeader["partition_entry_size"] - totalSize := 0 - - for i := 0; i+entrySize <= len(partEntriesData); i += entrySize { - entry, err := Parse(partEntriesData[i:], entryFields, "little") - if err != nil { - continue - } - if entry["type_guid_p1"] == 0 && entry["type_guid_p2"] == 0 { - continue - } - startOff := lbaToOffset(entry["starting_lba"]) - endOff := lbaToOffset(entry["ending_lba"]) - if startOff < endOff && endOff > totalSize { - totalSize = endOff - } - } - - if totalSize == 0 { - return nil, fmt.Errorf("EFI GPT: no valid partitions found") - } - - if totalSize > availableData { - result.Size = availableData - } else { - result.Size = totalSize - } - result.Description = fmt.Sprintf("%s, total size: %d", result.Description, result.Size) - return result, nil -} - -// ---- MBR ---- - -const DescriptionMBR = "DOS Master Boot Record" -const MBRMagicOffset = 0x01FE - -// mbrMagic returns magic bytes for MBR signatures. -func mbrMagic() [][]byte { - return [][]byte{ - {0x55, 0xAA}, - } -} - -// mbrParser validates MBR header signatures. -func mbrParser(data []byte, offset int) (*SignatureResult, error) { - const blockSize = 512 - const minImageSize = blockSize * 2 - const partitionCount = 4 - const partitionTableOffset = 446 - - result := &SignatureResult{ - Description: DescriptionMBR, - Confidence: ConfidenceMedium, - } - - if offset != MBRMagicOffset { - return nil, fmt.Errorf("MBR: magic not at expected offset") - } - - result.Offset = offset - MBRMagicOffset - - partEntryFields := []StructField{ - {"status", "u8"}, - {"chs_start", "u24"}, - {"os_type", "u8"}, - {"chs_end", "u24"}, - {"lba_start", "u32"}, - {"lba_size", "u32"}, - } - - knownOSTypes := map[int]string{ - 0x07: "NTFS_IFS_HPFS_exFAT", - 0x0B: "FAT32", - 0x0C: "FAT32", - 0x43: "Linux", - 0x4D: "QNX Primary Volume", - 0x4E: "QNX Secondary Volume", - 0x81: "Minix", - 0x83: "Linux", - 0x8E: "Linux LVM", - 0x96: "ISO-9660", - 0xB1: "QNXv6 File System", - 0xB2: "QNXv6 File System", - 0xB3: "QNXv6 File System", - 0xEE: "EFI GPT Protective", - 0xEF: "EFI System Partition", - } - - allowedStatus := map[int]bool{0: true, 0x80: true} - entrySize := StructSize(partEntryFields) - - partTableStart := partitionTableOffset - partTableEnd := partTableStart + (entrySize * partitionCount) - - if partTableEnd > len(data) { - return nil, fmt.Errorf("MBR: insufficient data for partition table") - } - - partTable := data[partTableStart:partTableEnd] - - type mbrPartition struct { - start int - size int - name string - } - - var partitions []mbrPartition - imageSize := 0 - - for i := 0; i < partitionCount; i++ { - entryStart := i * entrySize - entry, err := Parse(partTable[entryStart:], partEntryFields, "little") - if err != nil { - return nil, fmt.Errorf("MBR: partition entry parse failed") - } - - if entry["os_type"] == 0 && entry["lba_size"] == 0 { - continue - } - - if !allowedStatus[entry["status"]] { - continue - } - - partName := "Unknown" - if name, ok := knownOSTypes[entry["os_type"]]; ok { - partName = name - } - - partStart := entry["lba_start"] * blockSize - partSize := entry["lba_size"] * blockSize - partEnd := partStart + partSize - - if partEnd > len(data) { - continue - } - - if partStart != result.Offset { - partitions = append(partitions, mbrPartition{ - start: partStart, - size: partSize, - name: partName, - }) - } - - if partEnd > imageSize { - imageSize = partEnd - } - } - - if len(partitions) == 0 { - return nil, fmt.Errorf("MBR: no valid partitions") - } - - if imageSize <= minImageSize { - return nil, fmt.Errorf("MBR: image size too small") - } - - result.Size = imageSize - for _, p := range partitions { - if p.start == result.Offset { - result.ExtractionDeclined = true - } - result.Description = fmt.Sprintf("%s, partition: %s", result.Description, p.name) - } - result.Description = fmt.Sprintf("%s, image size: %d bytes", result.Description, result.Size) - return result, nil -} - -// ---- UEFI ---- - -const ( - DescriptionUEFIVolume = "UEFI PI firmware volume" - DescriptionUEFICapsule = "UEFI capsule image" -) - -// uefiVolumeMagic returns magic bytes for UEFI PI firmware volumes. -func uefiVolumeMagic() [][]byte { - return [][]byte{ - []byte("_FVH"), - } -} - -// uefiCapsuleMagic returns magic bytes for UEFI capsule images. -func uefiCapsuleMagic() [][]byte { - return [][]byte{ - // EFI capsule GUID - {0xBD, 0x86, 0x66, 0x3B, 0x76, 0x0D, 0x30, 0x40, 0xB7, 0x0E, 0xB5, 0x51, 0x9E, 0x2F, 0xC5, 0xA0}, - // EFI2 capsule GUID - {0x8B, 0xA6, 0x3C, 0x4A, 0x23, 0x77, 0xFB, 0x48, 0x80, 0x3D, 0x57, 0x8C, 0xC1, 0xFE, 0xC4, 0x4D}, - // UEFI capsule GUID - {0xB9, 0x82, 0x91, 0x53, 0xB5, 0xAB, 0x91, 0x43, 0xB6, 0x9A, 0xE3, 0xA9, 0x43, 0xF7, 0x2F, 0xCC}, - } -} - -// uefiVolumeParser validates UEFI PI firmware volume signatures. -func uefiVolumeParser(data []byte, offset int) (*SignatureResult, error) { - const uefiMagicOffset = 40 - - result := &SignatureResult{ - Description: DescriptionUEFIVolume, - Confidence: ConfidenceMedium, - } - - if offset < uefiMagicOffset { - return nil, fmt.Errorf("UEFI volume: offset too small") - } - - result.Offset = offset - uefiMagicOffset - uefiData := data[result.Offset:] - - validRevisions := map[int]bool{1: true, 2: true} - - volFields := []StructField{ - {"volume_size", "u64"}, - {"magic", "u32"}, - {"attributes", "u32"}, - {"header_size", "u16"}, - {"header_crc", "u16"}, - {"extended_header_offset", "u16"}, - {"reserved", "u8"}, - {"revision", "u8"}, - } - - if len(uefiData) < StructSize(volFields) { - return nil, fmt.Errorf("UEFI volume: insufficient data") - } - - volHeader, err := Parse(uefiData, volFields, "little") - if err != nil { - return nil, fmt.Errorf("UEFI volume: parse failed") - } - - if volHeader["header_size"] >= volHeader["volume_size"] { - return nil, fmt.Errorf("UEFI volume: header size >= volume size") - } - if volHeader["reserved"] != 0 { - return nil, fmt.Errorf("UEFI volume: reserved field not zero") - } - if !validRevisions[volHeader["revision"]] { - return nil, fmt.Errorf("UEFI volume: invalid revision") - } - - volumeSize := volHeader["volume_size"] - if len(data) < result.Offset+volumeSize { - return nil, fmt.Errorf("UEFI volume: volume size exceeds data") - } - - result.Size = volumeSize - result.Description = fmt.Sprintf("%s, header CRC: %#X, header size: %d bytes, total size: %d bytes", - result.Description, uint32(volHeader["header_crc"]), volHeader["header_size"], volumeSize) - return result, nil -} - -// uefiCapsuleParser validates UEFI capsule image signatures. -func uefiCapsuleParser(data []byte, offset int) (*SignatureResult, error) { - result := &SignatureResult{ - Offset: offset, - Description: DescriptionUEFICapsule, - Confidence: ConfidenceMedium, - } - - capsuleFields := []StructField{ - {"guid_p1", "u64"}, - {"guid_p2", "u64"}, - {"header_size", "u32"}, - {"flags", "u32"}, - {"total_size", "u32"}, - } - - if offset+StructSize(capsuleFields) > len(data) { - return nil, fmt.Errorf("UEFI capsule: insufficient data") - } - - capsuleHeader, err := Parse(data[offset:], capsuleFields, "little") - if err != nil { - return nil, fmt.Errorf("UEFI capsule: parse failed") - } - - headerSize := capsuleHeader["header_size"] - totalSize := capsuleHeader["total_size"] - availableData := len(data) - offset - - if headerSize >= totalSize { - return nil, fmt.Errorf("UEFI capsule: header size >= total size") - } - - if totalSize < availableData { - return nil, fmt.Errorf("UEFI capsule: total size smaller than available data") - } - - result.Size = totalSize - result.Description = fmt.Sprintf("%s, header size: %d bytes, total size: %d bytes", - result.Description, headerSize, totalSize) - return result, nil -} - -// ---- PCH ROM ---- - -const DescriptionPCHRom = "Intel serial flash for PCH ROM" - -// pchRomMagic returns magic bytes for PCH ROM signatures. -func pchRomMagic() [][]byte { - return [][]byte{ - {0x5a, 0xa5, 0xf0, 0x0f}, - } -} - -// pchRomParser validates PCH ROM signatures. -func pchRomParser(data []byte, offset int) (*SignatureResult, error) { - const pchMagicOffset = 16 - const headerStructOffset = 16 - const headerStructSize = 8 - const expectedFCBA = 3 - const expectedFRBA = 4 - const flashRegionCount = 5 - const flashRegionEntrySize = 4 - - result := &SignatureResult{ - Description: DescriptionPCHRom, - } - - if offset < pchMagicOffset { - return nil, fmt.Errorf("PCH ROM: offset too small") - } - - result.Offset = offset - pchMagicOffset - pchData := data[result.Offset:] - - structStart := headerStructOffset - structEnd := structStart + headerStructSize - if structEnd > len(pchData) { - return nil, fmt.Errorf("PCH ROM: insufficient data for header") - } - - pchHeaderFields := []StructField{ - {"flmagic", "u32"}, - {"flmap0_fcba", "u8"}, - {"flmap0_nc", "u8"}, - {"flmap0_frba_nr", "u16"}, - } - - pchHeader, err := Parse(pchData[structStart:structEnd], pchHeaderFields, "little") - if err != nil { - return nil, fmt.Errorf("PCH ROM: header parse failed") - } - - if pchHeader["flmap0_fcba"] != expectedFCBA { - return nil, fmt.Errorf("PCH ROM: unexpected FCBA value") - } - if pchHeader["flmap0_frba_nr"] != expectedFRBA { - return nil, fmt.Errorf("PCH ROM: unexpected FRBA value") - } - - nc := pchHeader["flmap0_nc"] - if nc != 0 && nc != 1 { - return nil, fmt.Errorf("PCH ROM: unexpected NC value") - } - - // Parse flash region entries - fcba := pchHeader["flmap0_fcba"] - flashRegionBaseAddr := ((fcba >> 16) & 0xFF) << 4 - - imageSize := 0 - for i := 0; i < flashRegionCount; i++ { - regionEntryStart := flashRegionBaseAddr + (i * flashRegionEntrySize) - regionEntryEnd := regionEntryStart + flashRegionEntrySize - - if regionEntryEnd > len(pchData) { - return nil, fmt.Errorf("PCH ROM: region entry out of bounds") - } - - regionFields := []StructField{{"region_value", "u32"}} - regionEntry, err := Parse(pchData[regionEntryStart:regionEntryEnd], regionFields, "little") - if err != nil { - return nil, fmt.Errorf("PCH ROM: region entry parse failed") - } - - regionValue := regionEntry["region_value"] - regionBase := (regionValue & 0x1FFF) << 12 - regionLimit := (((regionValue & 0x1FFF0000) >> 4) | 0xFFFF) + 1 - regionSize := regionLimit - regionBase - - if regionSize > 0 && regionLimit > imageSize { - imageSize = regionLimit - } - } - - if imageSize == 0 { - return nil, fmt.Errorf("PCH ROM: no valid flash regions") - } - - result.Size = headerStructOffset + imageSize - return result, nil -} - -// ---- PCAP ---- - -const DescriptionPcapNG = "Pcap-NG capture file" - -// pcapngMagic returns magic bytes for Pcap-NG files. -func pcapngMagic() [][]byte { - return [][]byte{ - {0x0A, 0x0D, 0x0D, 0x0A}, - } -} - -// pcapngParser validates Pcap-NG capture file signatures. -func pcapngParser(data []byte, offset int) (*SignatureResult, error) { - const sectionHeaderBlockType = 0x0A0D0D0A - - result := &SignatureResult{ - Offset: offset, - Description: DescriptionPcapNG, - Confidence: ConfidenceHigh, - } - - sectionFields := []StructField{ - {"block_type", "u32"}, - {"block_size", "u32"}, - {"endian_magic", "u32"}, - {"major_version", "u16"}, - {"minor_version", "u16"}, - {"section_length", "u32"}, - } - - if offset+StructSize(sectionFields) > len(data) { - return nil, fmt.Errorf("Pcap-NG: insufficient data") - } - - sectionHeader, err := Parse(data[offset:], sectionFields, "little") - if err != nil { - return nil, fmt.Errorf("Pcap-NG: parse failed") - } - - // Determine endianness - endianMagic := sectionHeader["endian_magic"] - var endianness string - switch endianMagic { - case 0x1A2B3C4D: - endianness = "little" - case 0x4D3C2B1A: - endianness = "big" - default: - return nil, fmt.Errorf("Pcap-NG: invalid endian magic") - } - - // Re-parse with correct endianness - blockFields := []StructField{ - {"block_type", "u32"}, - {"block_size", "u32"}, - } - footerFields := []StructField{{"block_size", "u32"}} - footerSize := StructSize(footerFields) - - blockHeader, err := Parse(data[offset:], blockFields, endianness) - if err != nil { - return nil, fmt.Errorf("Pcap-NG: block header parse failed") - } - - if blockHeader["block_type"] != sectionHeaderBlockType { - return nil, fmt.Errorf("Pcap-NG: unexpected block type") - } - - blockSize := blockHeader["block_size"] - if blockSize < footerSize { - return nil, fmt.Errorf("Pcap-NG: block size too small") - } - - // Validate block size via footer - footerStart := offset + blockSize - footerSize - if footerStart+footerSize > len(data) { - return nil, fmt.Errorf("Pcap-NG: block footer out of bounds") - } - - blockFooter, err := Parse(data[footerStart:], footerFields, endianness) - if err != nil { - return nil, fmt.Errorf("Pcap-NG: block footer parse failed") - } - - if blockFooter["block_size"] != blockSize { - return nil, fmt.Errorf("Pcap-NG: block size mismatch between header and footer") - } - - // Reserved bit check - if (blockHeader["block_type"] & 0x80000000) != 0 { - return nil, fmt.Errorf("Pcap-NG: reserved bit set in block type") - } - - result.Size = blockSize - result.Description = fmt.Sprintf("%s, total size: %d bytes", result.Description, result.Size) - - if offset == 0 && result.Size == len(data) { - result.ExtractionDeclined = true - } - - return result, nil -} - -// ---- PJL ---- - -const DescriptionPJL = "HP Printer Job Language data" - -// pjlMagic returns magic bytes for PJL data. -func pjlMagic() [][]byte { - return [][]byte{ - {0x1B, 0x25, 0x2D, 0x31, 0x32, 0x33, 0x34, 0x35, 0x58, 0x40, 0x50, 0x4A, 0x4C}, // "\x1B%-12345X@PJL" - } -} - -// pjlParser validates PJL data signatures. -func pjlParser(data []byte, offset int) (*SignatureResult, error) { - const pjlCommandsOffset = 9 - - result := &SignatureResult{ - Offset: offset, - Description: DescriptionPJL, - Confidence: ConfidenceLow, - } - - if offset+pjlCommandsOffset >= len(data) { - return nil, fmt.Errorf("PJL: insufficient data") - } - - pjlText := GetCString(data[offset+pjlCommandsOffset:]) - if len(pjlText) == 0 { - return nil, fmt.Errorf("PJL: no command text found") - } - - result.Size = len(pjlText) - displayText := strings.ReplaceAll(pjlText, "\r", " ") - displayText = strings.ReplaceAll(displayText, "\n", "") - result.Description = fmt.Sprintf("%s: \"%s\"", result.Description, displayText) - return result, nil -} - -// ---- S-record ---- - -const ( - DescriptionSRecord = "Motorola S-record" - DescriptionSRecordGeneric = "Motorola S-record (generic)" -) - -// srecordMagic returns magic bytes for Motorola S-records. -func srecordMagic() [][]byte { - return [][]byte{ - []byte("S00600004844521B"), - } -} - -// srecordGenericMagic returns short/generic magic bytes for S-records. -func srecordGenericMagic() [][]byte { - return [][]byte{ - []byte("S0"), - } -} - -// srecordParser validates Motorola S-record signatures. -func srecordParser(data []byte, offset int) (*SignatureResult, error) { - const unixTermChar byte = 0x0A - const windowsTermChar byte = 0x0D - - result := &SignatureResult{ - Offset: offset, - Description: DescriptionSRecord, - Confidence: ConfidenceHigh, - } - - availableData := len(data) - - // Search for srec footer lines - srecFooters := [][]byte{[]byte("\nS9"), []byte("\nS8"), []byte("\nS7")} - footerLen := 3 - - searchData := data[offset:] - - for _, footer := range srecFooters { - idx := bytes.Index(searchData, footer) - if idx < 0 { - continue - } - - osType := "Unix" - srecEOF := offset + idx + footerLen - - for srecEOF < availableData { - b := data[srecEOF] - if b == windowsTermChar || b == unixTermChar { - if b == windowsTermChar { - srecEOF++ - osType = "Windows" - } - if srecEOF >= availableData { - return nil, fmt.Errorf("S-record: unexpected end after footer") - } - if data[srecEOF] != unixTermChar { - return nil, fmt.Errorf("S-record: invalid termination") - } - srecEOF++ - result.Size = srecEOF - offset - result.Description = fmt.Sprintf("%s, origin OS: %s, total size: %d bytes", - result.Description, osType, result.Size) - return result, nil - } - srecEOF++ - } - } - - return nil, fmt.Errorf("S-record: no valid footer found") -} - -// ---- DMG ---- - -const DescriptionDMG = "Apple Disk iMaGe" - -// dmgMagic returns magic bytes for DMG files (footer signature). -func dmgMagic() [][]byte { - return [][]byte{ - {0x6B, 0x6F, 0x6C, 0x79, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x02, 0x00}, // "koly\x00\x00\x00\x04\x00\x00\x02\x00" - } -} - -// dmgParser validates DMG footer signatures. -func dmgParser(data []byte, offset int) (*SignatureResult, error) { - result := &SignatureResult{ - Description: DescriptionDMG, - Confidence: ConfidenceHigh + 1, - } - - dmgFooterFields := []StructField{ - {"magic", "u32"}, - {"version", "u32"}, - {"header_size", "u32"}, - {"flags", "u32"}, - {"running_data_fork_offset", "u64"}, - {"data_fork_offset", "u64"}, - {"data_fork_length", "u64"}, - {"rsrc_fork_offset", "u64"}, - {"rsrc_fork_length", "u64"}, - {"segment_number", "u32"}, - {"segment_count", "u32"}, - {"segment_id_p1", "u64"}, - {"segment_id_p2", "u64"}, - {"data_checksum_type", "u32"}, - {"data_checksum_size", "u32"}, - {"data_checksum_1", "u32"}, - {"data_checksum_2", "u32"}, - {"data_checksum_3", "u32"}, - {"data_checksum_4", "u32"}, - {"data_checksum_5", "u32"}, - {"data_checksum_6", "u32"}, - {"data_checksum_7", "u32"}, - {"data_checksum_8", "u32"}, - {"data_checksum_9", "u32"}, - {"data_checksum_10", "u32"}, - {"data_checksum_11", "u32"}, - {"data_checksum_12", "u32"}, - {"data_checksum_13", "u32"}, - {"data_checksum_14", "u32"}, - {"data_checksum_15", "u32"}, - {"data_checksum_16", "u32"}, - {"data_checksum_17", "u32"}, - {"data_checksum_18", "u32"}, - {"data_checksum_19", "u32"}, - {"data_checksum_20", "u32"}, - {"data_checksum_21", "u32"}, - {"data_checksum_22", "u32"}, - {"data_checksum_23", "u32"}, - {"data_checksum_24", "u32"}, - {"data_checksum_25", "u32"}, - {"data_checksum_26", "u32"}, - {"data_checksum_27", "u32"}, - {"data_checksum_28", "u32"}, - {"data_checksum_29", "u32"}, - {"data_checksum_30", "u32"}, - {"data_checksum_31", "u32"}, - {"data_checksum_32", "u32"}, - {"xml_offset", "u64"}, - {"xml_length", "u64"}, - {"reserved_1", "u64"}, - {"reserved_2", "u64"}, - {"reserved_3", "u64"}, - {"reserved_4", "u64"}, - {"reserved_5", "u64"}, - {"reserved_6", "u64"}, - {"reserved_7", "u64"}, - {"reserved_8", "u64"}, - {"reserved_9", "u64"}, - {"reserved_10", "u64"}, - {"reserved_11", "u64"}, - {"reserved_12", "u64"}, - {"reserved_13", "u64"}, - {"reserved_14", "u64"}, - {"reserved_15", "u64"}, - {"checksum_type", "u32"}, - {"checksum_size", "u32"}, - {"checksum_1", "u32"}, - {"checksum_2", "u32"}, - {"checksum_3", "u32"}, - {"checksum_4", "u32"}, - {"checksum_5", "u32"}, - {"checksum_6", "u32"}, - {"checksum_7", "u32"}, - {"checksum_8", "u32"}, - {"checksum_9", "u32"}, - {"checksum_10", "u32"}, - {"checksum_11", "u32"}, - {"checksum_12", "u32"}, - {"checksum_13", "u32"}, - {"checksum_14", "u32"}, - {"checksum_15", "u32"}, - {"checksum_16", "u32"}, - {"checksum_17", "u32"}, - {"checksum_18", "u32"}, - {"checksum_19", "u32"}, - {"checksum_20", "u32"}, - {"checksum_21", "u32"}, - {"checksum_22", "u32"}, - {"checksum_23", "u32"}, - {"checksum_24", "u32"}, - {"checksum_25", "u32"}, - {"checksum_26", "u32"}, - {"checksum_27", "u32"}, - {"checksum_28", "u32"}, - {"checksum_29", "u32"}, - {"checksum_30", "u32"}, - {"checksum_31", "u32"}, - {"checksum_32", "u32"}, - {"image_variant", "u32"}, - {"sector_count", "u64"}, - {"reserved_16", "u32"}, - {"reserved_17", "u32"}, - {"reserved_18", "u32"}, - } - - footerSize := StructSize(dmgFooterFields) - - if offset+footerSize > len(data) { - return nil, fmt.Errorf("DMG: insufficient data for footer") - } - - dmgFooter, err := Parse(data[offset:], dmgFooterFields, "big") - if err != nil { - return nil, fmt.Errorf("DMG: footer parse failed") - } - - if dmgFooter["header_size"] != footerSize { - return nil, fmt.Errorf("DMG: header size mismatch (got %d, expected %d)", dmgFooter["header_size"], footerSize) - } - - dataLength := dmgFooter["data_fork_length"] - xmlLength := dmgFooter["xml_length"] - - if dataLength+xmlLength > offset { - return nil, fmt.Errorf("DMG: data + xml length exceeds offset") - } - - // Search for XML property list with blkx key - xmlSig := []byte("blkx" - const minXMLLen = 1024 - - xmlOffset := -1 - searchStart := 0 - for { - idx := bytes.Index(data[searchStart:], xmlSig) - if idx < 0 { - break - } - xmlStart := searchStart + idx - xmlEnd := xmlStart + minXMLLen - if xmlEnd > len(data) { - searchStart = xmlStart + len(xmlSig) - continue - } - xmlStr := string(data[xmlStart:xmlEnd]) - if strings.Contains(xmlStr, blkxKey) { - xmlOffset = xmlStart - break - } - searchStart = xmlStart + len(xmlSig) - } - - if xmlOffset < 0 { - return nil, fmt.Errorf("DMG: XML property list not found") - } - - if xmlOffset < dataLength { - return nil, fmt.Errorf("DMG: XML data before image data") - } - - result.Size = offset + footerSize - result.Offset = xmlOffset - dataLength - result.Description = fmt.Sprintf("%s, total size: %d bytes", result.Description, result.Size) - return result, nil -} - -// ---- QNX IFS ---- - -const DescriptionQNXIFS = "QNX IFS image" - -// qnxIFSMagic returns magic bytes for QNX IFS images. -func qnxIFSMagic() [][]byte { - return [][]byte{ - {0xEB, 0x7E, 0xFF, 0x00, 0x01, 0x00}, - } -} - -// qnxIFSParser validates QNX IFS image signatures. -func qnxIFSParser(data []byte, offset int) (*SignatureResult, error) { - result := &SignatureResult{ - Offset: offset, - Description: DescriptionQNXIFS, - } - - ifsFields := []StructField{ - {"magic", "u32"}, - {"version", "u16"}, - {"flags1", "u8"}, - {"flags2", "u8"}, - {"header_size", "u16"}, - {"machine", "u16"}, - {"startup_vaddr", "u32"}, - {"paddr_bias", "u32"}, - {"image_paddr", "u32"}, - {"ram_paddr", "u32"}, - {"ram_size", "u32"}, - {"startup_size", "u32"}, - {"stored_size", "u32"}, - {"imagefs_paddr", "u32"}, - {"imagefs_size", "u32"}, - {"preboot_size", "u16"}, - {"zero_0", "u16"}, - {"zero_1", "u32"}, - {"zero_2", "u32"}, - {"zero_3", "u32"}, - } - - if offset+StructSize(ifsFields) > len(data) { - return nil, fmt.Errorf("QNX IFS: insufficient data") - } - - ifsHeader, err := Parse(data[offset:], ifsFields, "little") - if err != nil { - return nil, fmt.Errorf("QNX IFS: parse failed") - } - - if ifsHeader["flags2"] != 0 { - return nil, fmt.Errorf("QNX IFS: flags2 not zero") - } - if ifsHeader["zero_0"] != 0 || ifsHeader["zero_1"] != 0 || ifsHeader["zero_2"] != 0 || ifsHeader["zero_3"] != 0 { - return nil, fmt.Errorf("QNX IFS: zero fields not zero") - } - - totalSize := ifsHeader["stored_size"] - availableData := len(data) - offset - if totalSize > availableData { - return nil, fmt.Errorf("QNX IFS: total size exceeds available data") - } - - result.Size = totalSize - result.Description = fmt.Sprintf("%s, total size: %d bytes", result.Description, result.Size) - return result, nil -} diff --git a/internal/leviathan/binsnoop/types.go b/internal/leviathan/binsnoop/types.go deleted file mode 100644 index 10d043c..0000000 --- a/internal/leviathan/binsnoop/types.go +++ /dev/null @@ -1,117 +0,0 @@ -package binsnoop - -import ( - "crypto/rand" - "fmt" - "sort" -) - -// Confidence levels for SignatureResult -const ( - ConfidenceLow uint8 = 0 - ConfidenceMedium uint8 = 128 - ConfidenceHigh uint8 = 250 -) - -// SignatureResult describes a validated file signature found during scanning. -type SignatureResult struct { - Offset int `json:"offset"` - ID string `json:"id"` - Size int `json:"size"` - Name string `json:"name"` - Confidence uint8 `json:"confidence"` - Description string `json:"description"` - AlwaysDisplay bool `json:"always_display"` - ExtractionDeclined bool `json:"extraction_declined"` - PreferredExtractor *Extractor `json:"-"` -} - -// DeepScanResult is a validated signature retained by a non-suppressing scan. -// ParentOffset is -1 for a top-level hit. -type DeepScanResult struct { - SignatureResult - End int `json:"end"` - Depth int `json:"depth"` - ParentOffset int `json:"parent_offset"` - Overlap bool `json:"overlap"` -} - -// DeepScan is a bounded nested-signature analysis result. -type DeepScan struct { - Results []DeepScanResult `json:"results"` - Truncated bool `json:"truncated"` -} - -// SignatureParser validates a magic byte match and returns a SignatureResult or error. -type SignatureParser func(data []byte, offset int) (*SignatureResult, error) - -// Signature defines a file signature to search for. -type Signature struct { - Name string - Short bool - Magic [][]byte - MagicOffset int - Description string - AlwaysDisplay bool - Parser SignatureParser - Extractor *Extractor -} - -// ExtractorType defines the type of extractor. -type ExtractorType int - -const ( - ExtractorTypeNone ExtractorType = iota - ExtractorTypeInternal - ExtractorTypeExternal -) - -// SourceFilePlaceholder is replaced at runtime with the actual file path. -const SourceFilePlaceholder = "%e" - -// InternalExtractorFunc is the function signature for built-in extractors. -type InternalExtractorFunc func(data []byte, offset int, outputDir string) *ExtractionResult - -// Extractor describes how to extract a file type. -type Extractor struct { - Type ExtractorType - Command string - Function InternalExtractorFunc - Extension string - Arguments []string - ExitCodes []int - DoNotRecurse bool -} - -// ExtractionResult stores information about a completed extraction. -type ExtractionResult struct { - Size *int `json:"size,omitempty"` - Success bool `json:"success"` - ExtractorName string `json:"extractor"` - DoNotRecurse bool `json:"do_not_recurse"` - OutputDirectory string `json:"output_directory"` -} - -// AnalysisResults contains all results from analysing a file or buffer. -type AnalysisResults struct { - FilePath string `json:"file_path"` - FileMap []SignatureResult `json:"file_map"` - Extractions map[string]*ExtractionResult `json:"extractions"` -} - -// sortSignatureResults sorts by offset for proper file mapping. -func sortSignatureResults(results []SignatureResult) { - sort.Slice(results, func(i, j int) bool { - return results[i].Offset < results[j].Offset - }) -} - -// newUUID generates a v4 UUID string with no external dependencies. -func newUUID() string { - b := make([]byte, 16) - _, _ = rand.Read(b) - b[6] = (b[6] & 0x0f) | 0x40 - b[8] = (b[8] & 0x3f) | 0x80 - return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x", - b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]) -} diff --git a/internal/leviathan/disasm/aarch64.go b/internal/leviathan/disasm/aarch64.go deleted file mode 100644 index e67495c..0000000 --- a/internal/leviathan/disasm/aarch64.go +++ /dev/null @@ -1,288 +0,0 @@ -package disasm - -import ( - "encoding/binary" - "fmt" -) - -// diAArch64 is a heuristic AArch64 (ARMv8 A64) decoder. It recognises a -// subset of instructions commonly seen in disassembly and falls back to -// ".word 0x…" for everything else. 32-bit fixed-width little-endian. -func diAArch64(buf []byte, base int64, maxInstr int) []map[string]any { - out := []map[string]any{} - i := 0 - for i+4 <= len(buf) && len(out) < maxInstr { - w := binary.LittleEndian.Uint32(buf[i : i+4]) - mn, ops, conf := decodeA64(w, base+int64(i)) - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i : i+4]), - "mnemonic": mn, - "operands": ops, - "confident": conf, - }) - i += 4 - } - // Tail bytes (not multiple of 4). - for i < len(buf) && len(out) < maxInstr { - end := i + 1 - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i:end]), - "mnemonic": "db", - "operands": hexCommaList(buf[i:end]), - "confident": false, - }) - i++ - } - return out -} - -func decodeA64(w uint32, pc int64) (string, string, bool) { - // NOP - if w == 0xd503201f { - return "nop", "", true - } - // RET Xn (default x30) - if w&0xfffffc1f == 0xd65f0000 { - rn := (w >> 5) & 0x1f - if rn == 30 { - return "ret", "", true - } - return "ret", regX(rn), true - } - // BR Xn - if w&0xfffffc1f == 0xd61f0000 { - return "br", regX((w >> 5) & 0x1f), true - } - // BLR Xn - if w&0xfffffc1f == 0xd63f0000 { - return "blr", regX((w >> 5) & 0x1f), true - } - // B / BL (unconditional branch, imm26 * 4, signed) - if w&0x7c000000 == 0x14000000 { - imm := signExtend64(uint64(w&0x03ffffff)<<2, 28) - target := pc + int64(imm) - if w&0x80000000 != 0 { - return "bl", fmt.Sprintf("0x%x", target), true - } - return "b", fmt.Sprintf("0x%x", target), true - } - // B.cond (conditional branch, imm19*4) - if w&0xff000010 == 0x54000000 { - cond := int(w & 0xf) - imm := signExtend64(uint64((w>>5)&0x7ffff)<<2, 21) - return "b." + condName(cond), fmt.Sprintf("0x%x", pc+int64(imm)), true - } - // CBZ / CBNZ - if w&0x7e000000 == 0x34000000 { - sf := (w >> 31) & 1 - op := (w >> 24) & 1 - rt := w & 0x1f - imm := signExtend64(uint64((w>>5)&0x7ffff)<<2, 21) - mn := "cbz" - if op == 1 { - mn = "cbnz" - } - r := regX(rt) - if sf == 0 { - r = regW(rt) - } - return mn, fmt.Sprintf("%s, 0x%x", r, pc+int64(imm)), true - } - // MOVZ / MOVN / MOVK (wide immediate) - if w&0x1f800000 == 0x12800000 { - sf := (w >> 31) & 1 - opc := (w >> 29) & 0x3 - hw := (w >> 21) & 0x3 - imm16 := (w >> 5) & 0xffff - rd := w & 0x1f - mn := "movn" - switch opc { - case 0b10: - mn = "movz" - case 0b11: - mn = "movk" - } - shift := hw * 16 - r := regX(rd) - if sf == 0 { - r = regW(rd) - } - if shift == 0 { - return mn, fmt.Sprintf("%s, #0x%x", r, imm16), true - } - return mn, fmt.Sprintf("%s, #0x%x, lsl #%d", r, imm16, shift), true - } - // ADR / ADRP - if w&0x1f000000 == 0x10000000 { - page := (w >> 31) & 1 - rd := w & 0x1f - immlo := uint64((w >> 29) & 0x3) - immhi := uint64((w >> 5) & 0x7ffff) - imm := (immhi << 2) | immlo - mn := "adr" - shift := uint(21) - signed := signExtend64(imm, shift) - if page == 1 { - mn = "adrp" - signed = signExtend64(imm<<12, 33) - // adrp target is page-aligned: pc & ~0xFFF - return mn, fmt.Sprintf("%s, 0x%x", regX(rd), (pc & ^int64(0xfff))+int64(signed)), true - } - return mn, fmt.Sprintf("%s, 0x%x", regX(rd), pc+int64(signed)), true - } - // ADD / SUB (immediate) - if w&0x1f000000 == 0x11000000 { - sf := (w >> 31) & 1 - op := (w >> 30) & 1 - sh := (w >> 22) & 1 - imm12 := (w >> 10) & 0xfff - rn := (w >> 5) & 0x1f - rd := w & 0x1f - mn := "add" - if op == 1 { - mn = "sub" - } - lhs := regX(rd) - mid := regX(rn) - if sf == 0 { - lhs = regW(rd) - mid = regW(rn) - } - if sh == 1 { - return mn, fmt.Sprintf("%s, %s, #0x%x, lsl #12", lhs, mid, imm12), true - } - return mn, fmt.Sprintf("%s, %s, #0x%x", lhs, mid, imm12), true - } - // LDR / STR (unsigned offset, 32/64-bit int) - if w&0x3fc00000 == 0x39400000 || w&0x3fc00000 == 0x39000000 { - size := (w >> 30) & 0x3 - l := (w >> 22) & 1 - imm12 := (w >> 10) & 0xfff - rn := (w >> 5) & 0x1f - rt := w & 0x1f - var mn string - if l == 1 { - mn = "ldr" - } else { - mn = "str" - } - scale := int(1 << size) - imm := int(imm12) * scale - var reg string - if size == 3 { - reg = regX(rt) - } else { - reg = regW(rt) - } - base := regXorSP(rn) - if imm == 0 { - return mn, fmt.Sprintf("%s, [%s]", reg, base), true - } - return mn, fmt.Sprintf("%s, [%s, #0x%x]", reg, base, imm), true - } - // LDR (literal / PC-rel) - if w&0xbf000000 == 0x18000000 { - sf := (w >> 30) & 1 - imm := signExtend64(uint64((w>>5)&0x7ffff)<<2, 21) - rt := w & 0x1f - reg := regW(rt) - if sf == 1 { - reg = regX(rt) - } - return "ldr", fmt.Sprintf("%s, 0x%x", reg, pc+int64(imm)), true - } - // ORR / AND / EOR (shifted reg) — simplified detection, no shifts reported. - if w&0x7f200000 == 0x2a000000 { // ORR (32/64) w/ shift=0 - sf := (w >> 31) & 1 - rm := (w >> 16) & 0x1f - rn := (w >> 5) & 0x1f - rd := w & 0x1f - // MOV (register) alias: ORR Xd, XZR, Xm → MOV Xd, Xm (when Rn==31 and shift==0) - if rn == 31 && ((w>>10)&0x3f) == 0 && ((w>>22)&0x3) == 0 { - if sf == 1 { - return "mov", fmt.Sprintf("%s, %s", regX(rd), regX(rm)), true - } - return "mov", fmt.Sprintf("%s, %s", regW(rd), regW(rm)), true - } - if sf == 1 { - return "orr", fmt.Sprintf("%s, %s, %s", regX(rd), regX(rn), regX(rm)), true - } - return "orr", fmt.Sprintf("%s, %s, %s", regW(rd), regW(rn), regW(rm)), true - } - // SVC #imm16 - if w&0xffe0001f == 0xd4000001 { - imm16 := (w >> 5) & 0xffff - return "svc", fmt.Sprintf("#0x%x", imm16), true - } - // BRK #imm16 - if w&0xffe0001f == 0xd4200000 { - imm16 := (w >> 5) & 0xffff - return "brk", fmt.Sprintf("#0x%x", imm16), true - } - // HINT family → treat common ones - if w&0xfffff01f == 0xd503201f { - op := (w >> 5) & 0x7f - switch op { - case 0: - return "nop", "", true - case 1: - return "yield", "", true - case 2: - return "wfe", "", true - case 3: - return "wfi", "", true - case 4: - return "sev", "", true - case 5: - return "sevl", "", true - } - return "hint", fmt.Sprintf("#0x%x", op), true - } - // Fallback - return ".word", fmt.Sprintf("0x%08x", w), false -} - -func regX(r uint32) string { - r &= 0x1f - if r == 31 { - return "xzr" - } - return fmt.Sprintf("x%d", r) -} -func regW(r uint32) string { - r &= 0x1f - if r == 31 { - return "wzr" - } - return fmt.Sprintf("w%d", r) -} - -// regXorSP is used for the "Rn" slot in memory ops where 31 means SP, not XZR. -func regXorSP(r uint32) string { - r &= 0x1f - if r == 31 { - return "sp" - } - return fmt.Sprintf("x%d", r) -} - -func condName(c int) string { - names := []string{ - "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc", - "hi", "ls", "ge", "lt", "gt", "le", "al", "nv", - } - if c >= 0 && c < 16 { - return names[c] - } - return fmt.Sprintf("c%d", c) -} - -func signExtend64(v uint64, bits uint) int64 { - mask := uint64(1) << (bits - 1) - if v&mask != 0 { - v |= ^uint64(0) << bits - } - return int64(v) -} diff --git a/internal/leviathan/disasm/arm32.go b/internal/leviathan/disasm/arm32.go deleted file mode 100644 index f4518bc..0000000 --- a/internal/leviathan/disasm/arm32.go +++ /dev/null @@ -1,209 +0,0 @@ -package disasm - -import ( - "encoding/binary" - "fmt" - "strings" -) - -// diARM32 is a heuristic ARM state decoder for 32-bit little-endian ARM -// instructions. Thumb/T32 is intentionally not decoded here. -func diARM32(buf []byte, base int64, maxInstr int) []map[string]any { - out := []map[string]any{} - i := 0 - for i+4 <= len(buf) && len(out) < maxInstr { - w := binary.LittleEndian.Uint32(buf[i : i+4]) - mn, ops, conf := decodeARM32(w, base+int64(i)) - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i : i+4]), - "mnemonic": mn, - "operands": ops, - "confident": conf, - }) - i += 4 - } - for i < len(buf) && len(out) < maxInstr { - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i : i+1]), - "mnemonic": "db", - "operands": hexCommaList(buf[i : i+1]), - "confident": false, - }) - i++ - } - return out -} - -func decodeARM32(w uint32, pc int64) (string, string, bool) { - cond := (w >> 28) & 0xf - suf := armCondSuffix(cond) - if cond == 0xf { - suf = ".nv" - } - // BX Rn - if w&0x0ffffff0 == 0x012fff10 { - return "bx" + suf, armReg(w & 0xf), true - } - // SVC #imm24 - if w&0x0f000000 == 0x0f000000 { - return "svc" + suf, fmt.Sprintf("#0x%x", w&0x00ffffff), true - } - // B / BL, offset is sign-extended imm24 << 2 from PC+8. - if w&0x0e000000 == 0x0a000000 { - imm := signExtend64(uint64(w&0x00ffffff)<<2, 26) - target := pc + 8 + int64(imm) - if w&0x01000000 != 0 { - return "bl" + suf, fmt.Sprintf("0x%x", target), true - } - return "b" + suf, fmt.Sprintf("0x%x", target), true - } - // PUSH/POP common aliases. - if w&0xffff0000 == 0xe92d0000 { - return "push", armRegList(w & 0xffff), true - } - if w&0xffff0000 == 0xe8bd0000 { - return "pop", armRegList(w & 0xffff), true - } - // LDR/STR immediate offset. - if w&0x0c000000 == 0x04000000 { - p := (w >> 24) & 1 - u := (w >> 23) & 1 - b := (w >> 22) & 1 - wb := (w >> 21) & 1 - l := (w >> 20) & 1 - rn := (w >> 16) & 0xf - rd := (w >> 12) & 0xf - imm := w & 0xfff - mn := "str" - if l == 1 { - mn = "ldr" - } - if b == 1 { - mn += "b" - } - mn += suf - sign := "" - if u == 0 { - sign = "-" - } - addr := "" - switch { - case p == 1 && imm == 0 && wb == 0: - addr = fmt.Sprintf("[%s]", armReg(rn)) - case p == 1: - addr = fmt.Sprintf("[%s, #%s0x%x]", armReg(rn), sign, imm) - if wb == 1 { - addr += "!" - } - default: - addr = fmt.Sprintf("[%s], #%s0x%x", armReg(rn), sign, imm) - } - return mn, fmt.Sprintf("%s, %s", armReg(rd), addr), true - } - // Data processing immediate/register subset. - if w&0x0c000000 == 0x00000000 || w&0x0e000000 == 0x02000000 { - immediate := w&0x02000000 != 0 - opcode := (w >> 21) & 0xf - s := (w >> 20) & 1 - rn := (w >> 16) & 0xf - rd := (w >> 12) & 0xf - mn := armDataProcName(opcode) - if mn == "" { - return ".word", fmt.Sprintf("0x%08x", w), false - } - mn += suf - if s == 1 && opcode != 8 && opcode != 9 && opcode != 10 && opcode != 11 { - mn += "s" - } - op2 := "" - if immediate { - imm8 := w & 0xff - rot := ((w >> 8) & 0xf) * 2 - imm := (imm8 >> rot) | (imm8 << ((32 - rot) & 31)) - op2 = fmt.Sprintf("#0x%x", imm) - } else { - rm := w & 0xf - shift := (w >> 4) & 0xff - if shift == 0 { - op2 = armReg(rm) - } else { - op2 = fmt.Sprintf("%s, %s", armReg(rm), armShift(shift)) - } - } - switch opcode { - case 8, 9, 10, 11: // tst/teq/cmp/cmn - return mn, fmt.Sprintf("%s, %s", armReg(rn), op2), true - case 13, 15: // mov/mvn - return mn, fmt.Sprintf("%s, %s", armReg(rd), op2), true - default: - return mn, fmt.Sprintf("%s, %s, %s", armReg(rd), armReg(rn), op2), true - } - } - return ".word", fmt.Sprintf("0x%08x", w), false -} - -func armCondSuffix(cond uint32) string { - return [...]string{".eq", ".ne", ".cs", ".cc", ".mi", ".pl", ".vs", ".vc", ".hi", ".ls", ".ge", ".lt", ".gt", ".le", "", ""}[cond&0xf] -} - -func armDataProcName(op uint32) string { - return [...]string{"and", "eor", "sub", "rsb", "add", "adc", "sbc", "rsc", "tst", "teq", "cmp", "cmn", "orr", "mov", "bic", "mvn"}[op&0xf] -} - -func armReg(r uint32) string { - switch r & 0xf { - case 13: - return "sp" - case 14: - return "lr" - case 15: - return "pc" - default: - return fmt.Sprintf("r%d", r&0xf) - } -} - -func armRegList(mask uint32) string { - regs := []string{} - start := -1 - flush := func(end int) { - if start < 0 { - return - } - if start == end { - regs = append(regs, armReg(uint32(start))) - } else { - regs = append(regs, fmt.Sprintf("%s-%s", armReg(uint32(start)), armReg(uint32(end)))) - } - start = -1 - } - for i := 0; i < 16; i++ { - if mask&(1<> 1) & 0x3 - imm := (v >> 3) & 0x1f - names := [...]string{"lsl", "lsr", "asr", "ror"} - if imm == 0 { - if typ == 0 { - return "lsl #0" - } - if typ == 3 { - return "rrx" - } - imm = 32 - } - return fmt.Sprintf("%s #%d", names[typ], imm) -} diff --git a/internal/leviathan/disasm/cfg.go b/internal/leviathan/disasm/cfg.go deleted file mode 100644 index ec0625d..0000000 --- a/internal/leviathan/disasm/cfg.go +++ /dev/null @@ -1,541 +0,0 @@ -package disasm - -import ( - "sort" - "strconv" - "strings" -) - -// cfg.go builds control-flow graphs and cross-references on top of the -// per-architecture decoders. It performs a recursive-descent sweep from an -// entry point: it follows direct branches and the fall-through after calls -// (calls leave the function, so their targets are recorded as xrefs rather -// than followed), splits the decoded instructions into basic blocks, and -// records every direct code/data reference it can resolve statically. - -// Block is one basic block: a maximal straight-line instruction run. -type Block struct { - Start int64 `json:"start"` - End int64 `json:"end"` // address after the last instruction - Instrs []map[string]any `json:"instrs"` - Succs []int64 `json:"succs"` // successor block start addresses - Kind string `json:"kind"` // ret | jmp | cond | call | fallthrough -} - -// Xref is a single static reference from one address to another. -type Xref struct { - From int64 `json:"from"` - To int64 `json:"to"` - Kind string `json:"kind"` // call | jump | data -} - -// CFG is the analysis result for one entry point. -type CFG struct { - Entry int64 `json:"entry"` - Arch string `json:"arch"` - Blocks []*Block `json:"blocks"` - Calls []int64 `json:"calls"` // distinct call targets (in or out of range) - Xrefs []Xref `json:"xrefs"` // all resolved references - Unknown int `json:"unknown"` // indirect/unresolved branch+call count -} - -// Function is a discovered function and its structural metrics. -type Function struct { - Name string `json:"name"` - Entry int64 `json:"entry"` - End int64 `json:"end"` - Blocks []*Block `json:"blocks"` - Instructions int `json:"instructions"` - Complexity int `json:"complexity"` - Calls []int64 `json:"calls"` - CalledBy []int64 `json:"calledBy"` - Unknown int `json:"unknown"` -} - -// CallEdge is one direct call from a discovered function. -type CallEdge struct { - Caller int64 `json:"caller"` - CallSite int64 `json:"callSite"` - Callee int64 `json:"callee"` - External bool `json:"external"` -} - -// Seed is a known or suspected function entry supplied by executable metadata. -type Seed struct { - Name string `json:"name"` - Entry int64 `json:"entry"` -} - -// Program is a bounded recursive function-discovery result for one byte range. -type Program struct { - Entry int64 `json:"entry"` - Base int64 `json:"base"` - End int64 `json:"end"` - Arch string `json:"arch"` - Functions []*Function `json:"functions"` - Edges []CallEdge `json:"edges"` - ExternalCalls []int64 `json:"externalCalls"` - Instructions int `json:"instructions"` - Unknown int `json:"unknown"` - Truncated bool `json:"truncated"` -} - -type rawInstr struct { - addr int64 - length int - mnem string - class string - target int64 - hasTgt bool - data int64 - hasDat bool - m map[string]any -} - -// BuildCFG decodes from entry (within buf, which starts at address base) and -// returns the control-flow graph plus discovered references. -func BuildCFG(buf []byte, base int64, arch string, entry int64, maxInstr int) *CFG { - if maxInstr <= 0 { - maxInstr = 8192 - } - cfg := &CFG{Entry: entry, Arch: arch} - decoded := map[int64]*rawInstr{} - inRange := func(a int64) bool { return a >= base && a < base+int64(len(buf)) } - - var stack []int64 - push := func(a int64) { - if inRange(a) { - stack = append(stack, a) - } - } - push(entry) - - for len(stack) > 0 && len(decoded) < maxInstr { - addr := stack[len(stack)-1] - stack = stack[:len(stack)-1] - for inRange(addr) && len(decoded) < maxInstr { - if _, seen := decoded[addr]; seen { - break - } - ri := decodeAt(buf, addr, base, arch) - if ri == nil { - break - } - decoded[addr] = ri - next := addr + int64(ri.length) - switch ri.class { - case "ret": - addr = -1 - case "jmp": - if ri.hasTgt { - addr = ri.target - } else { - cfg.Unknown++ - addr = -1 - } - case "cond": - if ri.hasTgt { - push(ri.target) - } else { - cfg.Unknown++ - } - addr = next - case "call": - if !ri.hasTgt { - cfg.Unknown++ - } - addr = next - default: - addr = next - } - if addr < 0 { - break - } - } - } - - // Collect references and call targets. - callSet := map[int64]bool{} - for _, ri := range decoded { - if ri.hasTgt { - switch ri.class { - case "call": - cfg.Xrefs = append(cfg.Xrefs, Xref{From: ri.addr, To: ri.target, Kind: "call"}) - if !callSet[ri.target] { - callSet[ri.target] = true - cfg.Calls = append(cfg.Calls, ri.target) - } - case "jmp", "cond": - cfg.Xrefs = append(cfg.Xrefs, Xref{From: ri.addr, To: ri.target, Kind: "jump"}) - } - } - if ri.hasDat { - cfg.Xrefs = append(cfg.Xrefs, Xref{From: ri.addr, To: ri.data, Kind: "data"}) - } - } - sort.Slice(cfg.Calls, func(i, j int) bool { return cfg.Calls[i] < cfg.Calls[j] }) - sort.Slice(cfg.Xrefs, func(i, j int) bool { - if cfg.Xrefs[i].From != cfg.Xrefs[j].From { - return cfg.Xrefs[i].From < cfg.Xrefs[j].From - } - return cfg.Xrefs[i].To < cfg.Xrefs[j].To - }) - - cfg.Blocks = buildBlocks(decoded, inRange) - return cfg -} - -// DiscoverFunctions recursively follows direct calls from entry, building a -// bounded call graph over buf. Indirect calls are counted but cannot be -// discovered statically. maxInstr is a total budget across all functions. -func DiscoverFunctions(buf []byte, base int64, arch string, entry int64, maxFunctions, maxInstr int) *Program { - return DiscoverFunctionsFromSeeds(buf, base, arch, []Seed{{Entry: entry}}, maxFunctions, maxInstr) -} - -// DiscoverFunctionsFromSeeds analyzes named and unnamed roots plus every -// direct in-range call target reached from them. -func DiscoverFunctionsFromSeeds(buf []byte, base int64, arch string, seeds []Seed, maxFunctions, maxInstr int) *Program { - if maxFunctions <= 0 || maxFunctions > 4096 { - maxFunctions = 256 - } - if maxInstr <= 0 || maxInstr > 1_000_000 { - maxInstr = 65_536 - } - end := base + int64(len(buf)) - entry := base - if len(seeds) > 0 { - entry = seeds[0].Entry - } - program := &Program{Entry: entry, Base: base, End: end, Arch: arch} - inRange := func(addr int64) bool { return addr >= base && addr < end } - queue := make([]int64, 0, len(seeds)) - queued := map[int64]bool{} - names := map[int64]string{} - for _, seed := range seeds { - if !inRange(seed.Entry) { - continue - } - if !queued[seed.Entry] { - queued[seed.Entry] = true - queue = append(queue, seed.Entry) - } - if seed.Name != "" { - names[seed.Entry] = seed.Name - } - } - if len(queue) == 0 { - program.Truncated = true - return program - } - - discovered := map[int64]*Function{} - callerSets := map[int64]map[int64]bool{} - externalSet := map[int64]bool{} - - for len(queue) > 0 && len(discovered) < maxFunctions && program.Instructions < maxInstr { - fnEntry := queue[0] - queue = queue[1:] - if discovered[fnEntry] != nil { - continue - } - remaining := maxInstr - program.Instructions - perFunction := remaining - if perFunction > 8192 { - perFunction = 8192 - } - cfg := BuildCFG(buf, base, arch, fnEntry, perFunction) - fn := summarizeFunction(cfg, names[fnEntry]) - discovered[fnEntry] = fn - program.Functions = append(program.Functions, fn) - program.Instructions += fn.Instructions - program.Unknown += fn.Unknown - - for _, xref := range cfg.Xrefs { - if xref.Kind != "call" { - continue - } - external := !inRange(xref.To) - program.Edges = append(program.Edges, CallEdge{ - Caller: fnEntry, CallSite: xref.From, Callee: xref.To, External: external, - }) - if external { - externalSet[xref.To] = true - continue - } - if callerSets[xref.To] == nil { - callerSets[xref.To] = map[int64]bool{} - } - callerSets[xref.To][fnEntry] = true - if !queued[xref.To] { - queued[xref.To] = true - queue = append(queue, xref.To) - } - } - } - - if len(queue) > 0 || program.Instructions >= maxInstr { - program.Truncated = true - } - for _, fn := range program.Functions { - for caller := range callerSets[fn.Entry] { - fn.CalledBy = append(fn.CalledBy, caller) - } - sort.Slice(fn.CalledBy, func(i, j int) bool { return fn.CalledBy[i] < fn.CalledBy[j] }) - } - for target := range externalSet { - program.ExternalCalls = append(program.ExternalCalls, target) - } - sort.Slice(program.Functions, func(i, j int) bool { return program.Functions[i].Entry < program.Functions[j].Entry }) - sort.Slice(program.Edges, func(i, j int) bool { - if program.Edges[i].Caller != program.Edges[j].Caller { - return program.Edges[i].Caller < program.Edges[j].Caller - } - if program.Edges[i].CallSite != program.Edges[j].CallSite { - return program.Edges[i].CallSite < program.Edges[j].CallSite - } - return program.Edges[i].Callee < program.Edges[j].Callee - }) - sort.Slice(program.ExternalCalls, func(i, j int) bool { return program.ExternalCalls[i] < program.ExternalCalls[j] }) - return program -} - -func summarizeFunction(cfg *CFG, name string) *Function { - if name == "" { - name = unknownFunctionName(cfg.Entry) - } - fn := &Function{ - Name: name, Entry: cfg.Entry, Blocks: cfg.Blocks, - Calls: append([]int64(nil), cfg.Calls...), Unknown: cfg.Unknown, - } - edges := 0 - for _, block := range cfg.Blocks { - if block.End > fn.End { - fn.End = block.End - } - fn.Instructions += len(block.Instrs) - edges += len(block.Succs) - } - fn.Complexity = edges - len(cfg.Blocks) + 2 - if fn.Complexity < 1 { - fn.Complexity = 1 - } - return fn -} - -func unknownFunctionName(entry int64) string { - return "sub_" + strconv.FormatInt(entry, 16) -} - -func buildBlocks(decoded map[int64]*rawInstr, inRange func(int64) bool) []*Block { - if len(decoded) == 0 { - return nil - } - addrs := make([]int64, 0, len(decoded)) - for a := range decoded { - addrs = append(addrs, a) - } - sort.Slice(addrs, func(i, j int) bool { return addrs[i] < addrs[j] }) - - // Leaders: first instr, every branch target, and the instruction after any - // branch/call/ret. - leaders := map[int64]bool{addrs[0]: true} - for _, a := range addrs { - ri := decoded[a] - next := a + int64(ri.length) - switch ri.class { - case "ret": - if _, ok := decoded[next]; ok { - leaders[next] = true - } - case "jmp": - if ri.hasTgt { - if _, ok := decoded[ri.target]; ok { - leaders[ri.target] = true - } - } - if _, ok := decoded[next]; ok { - leaders[next] = true - } - case "cond", "call": - if ri.hasTgt { - if _, ok := decoded[ri.target]; ok { - leaders[ri.target] = true - } - } - if _, ok := decoded[next]; ok { - leaders[next] = true - } - } - } - - var blocks []*Block - var cur *Block - flush := func() { - if cur != nil { - blocks = append(blocks, cur) - cur = nil - } - } - for idx, a := range addrs { - ri := decoded[a] - if leaders[a] { - flush() - cur = &Block{Start: a} - } - if cur == nil { - cur = &Block{Start: a} - } - cur.Instrs = append(cur.Instrs, ri.m) - next := a + int64(ri.length) - cur.End = next - - // Decide whether this instruction terminates the block. - isLast := idx == len(addrs)-1 - nextIsLeader := false - if !isLast { - if leaders[addrs[idx+1]] { - nextIsLeader = true - } - } - terminator := ri.class == "ret" || ri.class == "jmp" || ri.class == "cond" - if terminator || nextIsLeader || isLast { - cur.Kind = blockKind(ri.class) - switch ri.class { - case "ret": - // no successors - case "jmp": - if ri.hasTgt && inRange(ri.target) { - cur.Succs = []int64{ri.target} - } - case "cond": - if ri.hasTgt && inRange(ri.target) { - cur.Succs = append(cur.Succs, ri.target) - } - if _, ok := decoded[next]; ok { - cur.Succs = append(cur.Succs, next) - } - default: - if _, ok := decoded[next]; ok { - cur.Succs = []int64{next} - } - } - flush() - } - } - flush() - return blocks -} - -func blockKind(class string) string { - switch class { - case "ret", "jmp", "cond", "call": - return class - default: - return "fallthrough" - } -} - -// decodeAt decodes a single instruction at address addr (buf starts at base). -func decodeAt(buf []byte, addr, base int64, arch string) *rawInstr { - off := int(addr - base) - if off < 0 || off >= len(buf) { - return nil - } - end := off + 16 // max x86 instruction length; fixed-width arches need <= 4 - if end > len(buf) { - end = len(buf) - } - res := Disassemble(buf[off:end], addr, arch, 1) - if len(res) == 0 { - return nil - } - m := res[0] - ri := &rawInstr{addr: addr, m: m} - ri.length = byteCount(m["bytes"].(string)) - if ri.length <= 0 { - ri.length = 1 - } - ri.mnem, _ = m["mnemonic"].(string) - ops, _ := m["operands"].(string) - ri.class = classify(ri.mnem) - if t, ok := directTarget(ops); ok { - ri.target, ri.hasTgt = t, true - } - if dataReferencing(ri.class) { - if d, ok := dataTarget(ops); ok { - ri.data, ri.hasDat = d, true - } - } - return ri -} - -func byteCount(s string) int { - if s == "" { - return 0 - } - return len(strings.Fields(s)) -} - -func classify(mnem string) string { - switch mnem { - case "ret", "retf", "iret": - return "ret" - case "jmp", "jmpf", "b", "br": - return "jmp" - case "call", "callf", "bl", "blr", "jal", "jalr": - return "call" - case "cbz", "cbnz", "tbz", "tbnz", "loop", "loope", "loopne", "jecxz", "jrcxz": - return "cond" - } - if strings.HasPrefix(mnem, "b.") { // AArch64 conditional branch - return "cond" - } - if strings.HasPrefix(mnem, "j") { // x86 Jcc (jmp already handled above) - return "cond" - } - if strings.HasPrefix(mnem, "beq") || strings.HasPrefix(mnem, "bne") { // MIPS - return "cond" - } - return "normal" -} - -func dataReferencing(class string) bool { - // RIP-relative resolved targets show up as "; 0x.." comments on any - // instruction; only mine them on non-control-flow ops as data xrefs. - return class == "normal" || class == "call" -} - -// directTarget extracts a branch/call target when the final operand is a bare -// absolute address (a direct, statically known target). Register/memory -// indirect operands return false. -func directTarget(operands string) (int64, bool) { - if i := strings.Index(operands, ";"); i >= 0 { - operands = operands[:i] - } - if c := strings.LastIndex(operands, ","); c >= 0 { - operands = operands[c+1:] - } - operands = strings.TrimSpace(operands) - return parseHexAddr(operands) -} - -// dataTarget extracts a resolved address from a "; 0x.." trailing comment -// (e.g. an x86 RIP-relative memory operand). -func dataTarget(operands string) (int64, bool) { - i := strings.Index(operands, ";") - if i < 0 { - return 0, false - } - return parseHexAddr(strings.TrimSpace(operands[i+1:])) -} - -func parseHexAddr(s string) (int64, bool) { - if !strings.HasPrefix(s, "0x") { - return 0, false - } - v, err := strconv.ParseUint(s[2:], 16, 64) - if err != nil { - return 0, false - } - return int64(v), true -} diff --git a/internal/leviathan/disasm/image.go b/internal/leviathan/disasm/image.go deleted file mode 100644 index 9e0f38f..0000000 --- a/internal/leviathan/disasm/image.go +++ /dev/null @@ -1,74 +0,0 @@ -package disasm - -import ( - "github.com/andydixon/link/internal/leviathan/document" - "github.com/andydixon/link/internal/leviathan/parsers" -) - -// ImageAnalysis is a metadata-seeded analysis across file-backed executable -// regions in an ELF or PE image. -type ImageAnalysis struct { - Info *parsers.ExecutableInfo `json:"info"` - Programs []*Program `json:"programs"` - Truncated bool `json:"truncated"` -} - -// DiscoverExecutable analyzes executable regions using header entries, -// symbols and exports as roots. Budgets apply across the whole image. -func DiscoverExecutable(d *document.Document, maxBytes int64, maxFunctions, maxInstr int) (*ImageAnalysis, error) { - info, err := parsers.InspectExecutable(d) - if err != nil { - return nil, err - } - if maxBytes <= 0 || maxBytes > 32<<20 { - maxBytes = 8 << 20 - } - if maxFunctions <= 0 || maxFunctions > 4096 { - maxFunctions = 512 - } - if maxInstr <= 0 || maxInstr > 1_000_000 { - maxInstr = 131_072 - } - analysis := &ImageAnalysis{Info: info} - remainingBytes := maxBytes - remainingFunctions := maxFunctions - remainingInstr := maxInstr - for _, region := range info.Regions { - if remainingBytes <= 0 || remainingFunctions <= 0 || remainingInstr <= 0 { - analysis.Truncated = true - break - } - length := region.Size - if length > 4<<20 { - length = 4 << 20 - analysis.Truncated = true - } - if length > remainingBytes { - length = remainingBytes - analysis.Truncated = true - } - if length <= 0 { - continue - } - buf, err := d.Range(region.Offset, length) - if err != nil { - return nil, err - } - seeds := make([]Seed, 0) - for _, seed := range info.Seeds { - if seed.Offset >= region.Offset && seed.Offset < region.Offset+length { - seeds = append(seeds, Seed{Name: seed.Name, Entry: seed.Offset}) - } - } - if len(seeds) == 0 { - seeds = append(seeds, Seed{Name: region.Name, Entry: region.Offset}) - } - program := DiscoverFunctionsFromSeeds(buf, region.Offset, info.Arch, seeds, remainingFunctions, remainingInstr) - analysis.Programs = append(analysis.Programs, program) - remainingBytes -= length - remainingFunctions -= len(program.Functions) - remainingInstr -= program.Instructions - analysis.Truncated = analysis.Truncated || program.Truncated - } - return analysis, nil -} diff --git a/internal/leviathan/disasm/mips.go b/internal/leviathan/disasm/mips.go deleted file mode 100644 index 6f1f6a9..0000000 --- a/internal/leviathan/disasm/mips.go +++ /dev/null @@ -1,144 +0,0 @@ -package disasm - -import ( - "encoding/binary" - "fmt" -) - -func diMIPS32(buf []byte, base int64, little bool, maxInstr int) []map[string]any { - out := []map[string]any{} - for i := 0; i+4 <= len(buf) && len(out) < maxInstr; i += 4 { - var w uint32 - if little { - w = binary.LittleEndian.Uint32(buf[i : i+4]) - } else { - w = binary.BigEndian.Uint32(buf[i : i+4]) - } - mn, ops, conf := decodeMIPS32(w, base+int64(i)) - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i : i+4]), - "mnemonic": mn, - "operands": ops, - "confident": conf, - }) - } - for i := len(buf) - len(buf)%4; i < len(buf) && len(out) < maxInstr; i++ { - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i : i+1]), - "mnemonic": "db", - "operands": hexCommaList(buf[i : i+1]), - "confident": false, - }) - } - return out -} - -func decodeMIPS32(w uint32, pc int64) (string, string, bool) { - if w == 0 { - return "nop", "", true - } - op := (w >> 26) & 0x3f - rs := (w >> 21) & 0x1f - rt := (w >> 16) & 0x1f - rd := (w >> 11) & 0x1f - shamt := (w >> 6) & 0x1f - funct := w & 0x3f - imm := uint16(w) - simm := int16(imm) - target26 := w & 0x03ffffff - - switch op { - case 0x00: - switch funct { - case 0x00: - return "sll", fmt.Sprintf("%s, %s, %d", mipsReg(rd), mipsReg(rt), shamt), true - case 0x02: - return "srl", fmt.Sprintf("%s, %s, %d", mipsReg(rd), mipsReg(rt), shamt), true - case 0x03: - return "sra", fmt.Sprintf("%s, %s, %d", mipsReg(rd), mipsReg(rt), shamt), true - case 0x08: - return "jr", mipsReg(rs), true - case 0x09: - return "jalr", fmt.Sprintf("%s, %s", mipsReg(rd), mipsReg(rs)), true - case 0x0c: - return "syscall", fmt.Sprintf("0x%x", (w>>6)&0xfffff), true - case 0x20: - return "add", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - case 0x21: - return "addu", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - case 0x22: - return "sub", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - case 0x23: - return "subu", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - case 0x24: - return "and", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - case 0x25: - return "or", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - case 0x26: - return "xor", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - case 0x27: - return "nor", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - case 0x2a: - return "slt", fmt.Sprintf("%s, %s, %s", mipsReg(rd), mipsReg(rs), mipsReg(rt)), true - } - case 0x02: - return "j", fmt.Sprintf("0x%x", mipsJumpTarget(pc, target26)), true - case 0x03: - return "jal", fmt.Sprintf("0x%x", mipsJumpTarget(pc, target26)), true - case 0x04: - return "beq", fmt.Sprintf("%s, %s, 0x%x", mipsReg(rs), mipsReg(rt), mipsBranchTarget(pc, simm)), true - case 0x05: - return "bne", fmt.Sprintf("%s, %s, 0x%x", mipsReg(rs), mipsReg(rt), mipsBranchTarget(pc, simm)), true - case 0x08: - return "addi", fmt.Sprintf("%s, %s, %d", mipsReg(rt), mipsReg(rs), simm), true - case 0x09: - return "addiu", fmt.Sprintf("%s, %s, %d", mipsReg(rt), mipsReg(rs), simm), true - case 0x0a: - return "slti", fmt.Sprintf("%s, %s, %d", mipsReg(rt), mipsReg(rs), simm), true - case 0x0c: - return "andi", fmt.Sprintf("%s, %s, 0x%x", mipsReg(rt), mipsReg(rs), imm), true - case 0x0d: - return "ori", fmt.Sprintf("%s, %s, 0x%x", mipsReg(rt), mipsReg(rs), imm), true - case 0x0e: - return "xori", fmt.Sprintf("%s, %s, 0x%x", mipsReg(rt), mipsReg(rs), imm), true - case 0x0f: - return "lui", fmt.Sprintf("%s, 0x%x", mipsReg(rt), imm), true - case 0x20: - return "lb", mipsMem(rt, rs, simm), true - case 0x21: - return "lh", mipsMem(rt, rs, simm), true - case 0x23: - return "lw", mipsMem(rt, rs, simm), true - case 0x28: - return "sb", mipsMem(rt, rs, simm), true - case 0x29: - return "sh", mipsMem(rt, rs, simm), true - case 0x2b: - return "sw", mipsMem(rt, rs, simm), true - } - return ".word", fmt.Sprintf("0x%08x", w), false -} - -func mipsJumpTarget(pc int64, target uint32) int64 { - upper := uint64(pc+4) & 0xfffffffff0000000 - return int64(upper | uint64(target<<2)) -} - -func mipsBranchTarget(pc int64, imm int16) int64 { - return pc + 4 + int64(imm)*4 -} - -func mipsMem(rt, rs uint32, off int16) string { - return fmt.Sprintf("%s, %d(%s)", mipsReg(rt), off, mipsReg(rs)) -} - -func mipsReg(r uint32) string { - return [...]string{ - "$zero", "$at", "$v0", "$v1", "$a0", "$a1", "$a2", "$a3", - "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7", - "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7", - "$t8", "$t9", "$k0", "$k1", "$gp", "$sp", "$fp", "$ra", - }[r&0x1f] -} diff --git a/internal/leviathan/disasm/thumb.go b/internal/leviathan/disasm/thumb.go deleted file mode 100644 index 5fee9e9..0000000 --- a/internal/leviathan/disasm/thumb.go +++ /dev/null @@ -1,117 +0,0 @@ -package disasm - -import ( - "encoding/binary" - "fmt" -) - -func diThumb16(buf []byte, base int64, maxInstr int) []map[string]any { - out := []map[string]any{} - for i := 0; i+2 <= len(buf) && len(out) < maxInstr; i += 2 { - h := binary.LittleEndian.Uint16(buf[i : i+2]) - mn, ops, conf := decodeThumb16(h, base+int64(i)) - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i : i+2]), - "mnemonic": mn, - "operands": ops, - "confident": conf, - }) - } - for i := len(buf) - len(buf)%2; i < len(buf) && len(out) < maxInstr; i++ { - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i : i+1]), - "mnemonic": "db", - "operands": hexCommaList(buf[i : i+1]), - "confident": false, - }) - } - return out -} - -func decodeThumb16(h uint16, pc int64) (string, string, bool) { - switch { - case h == 0xbf00: - return "nop", "", true - case h&0xff87 == 0x4700: - return "bx", thumbHighReg((h >> 3) & 0xf), true - case h&0xff87 == 0x4780: - return "blx", thumbHighReg((h >> 3) & 0xf), true - case h&0xf800 == 0xe000: - imm := signExtend64(uint64(h&0x07ff)<<1, 12) - return "b", fmt.Sprintf("0x%x", pc+4+imm), true - case h&0xf000 == 0xd000 && h&0x0f00 != 0x0f00: - cond := uint32((h >> 8) & 0xf) - imm := signExtend64(uint64(h&0x00ff)<<1, 9) - return "b" + armCondSuffix(cond), fmt.Sprintf("0x%x", pc+4+imm), true - case h&0xff00 == 0xdf00: - return "svc", fmt.Sprintf("#0x%x", h&0x00ff), true - case h&0xfe00 == 0xb400: - mask := uint32(h & 0x00ff) - if h&0x0100 != 0 { - mask |= 1 << 14 - } - return "push", armRegList(mask), true - case h&0xfe00 == 0xbc00: - mask := uint32(h & 0x00ff) - if h&0x0100 != 0 { - mask |= 1 << 15 - } - return "pop", armRegList(mask), true - case h&0xf800 == 0x2000: - rd := uint32((h >> 8) & 0x7) - return "movs", fmt.Sprintf("%s, #0x%x", armReg(rd), h&0x00ff), true - case h&0xf800 == 0x2800: - rn := uint32((h >> 8) & 0x7) - return "cmp", fmt.Sprintf("%s, #0x%x", armReg(rn), h&0x00ff), true - case h&0xf800 == 0x3000: - rd := uint32((h >> 8) & 0x7) - return "adds", fmt.Sprintf("%s, #0x%x", armReg(rd), h&0x00ff), true - case h&0xf800 == 0x3800: - rd := uint32((h >> 8) & 0x7) - return "subs", fmt.Sprintf("%s, #0x%x", armReg(rd), h&0x00ff), true - case h&0xfc00 == 0x4000: - op := (h >> 6) & 0xf - rs := uint32((h >> 3) & 0x7) - rd := uint32(h & 0x7) - names := [...]string{"ands", "eors", "lsls", "lsrs", "asrs", "adcs", "sbcs", "rors", "tst", "rsbs", "cmp", "cmn", "orrs", "muls", "bics", "mvns"} - mn := names[op] - if op == 8 || op == 10 || op == 11 { - return mn, fmt.Sprintf("%s, %s", armReg(rd), armReg(rs)), true - } - return mn, fmt.Sprintf("%s, %s", armReg(rd), armReg(rs)), true - case h&0xfe00 == 0x1800: - rm := uint32((h >> 6) & 0x7) - rn := uint32((h >> 3) & 0x7) - rd := uint32(h & 0x7) - mn := "adds" - if h&0x0200 != 0 { - mn = "subs" - } - return mn, fmt.Sprintf("%s, %s, %s", armReg(rd), armReg(rn), armReg(rm)), true - case h&0xe000 == 0x6000: - imm := uint32((h>>6)&0x1f) * 4 - rn := uint32((h >> 3) & 0x7) - rd := uint32(h & 0x7) - mn := "str" - if h&0x0800 != 0 { - mn = "ldr" - } - return mn, fmt.Sprintf("%s, [%s, #0x%x]", armReg(rd), armReg(rn), imm), true - } - return ".hword", fmt.Sprintf("0x%04x", h), false -} - -func thumbHighReg(r uint16) string { - if r == 13 { - return "sp" - } - if r == 14 { - return "lr" - } - if r == 15 { - return "pc" - } - return fmt.Sprintf("r%d", r) -} diff --git a/internal/leviathan/disasm/x86.go b/internal/leviathan/disasm/x86.go deleted file mode 100644 index 90fb21d..0000000 --- a/internal/leviathan/disasm/x86.go +++ /dev/null @@ -1,91 +0,0 @@ -// Package disasm provides a honest, limited heuristic disassembler. -// It does NOT aim to be a full x86/ARM decoder. It recognises a -// subset of common opcodes so the UI can show plausible disassembly -// for inspection, and is explicit in the output when instructions -// are unknown. -package disasm - -import "fmt" - -// Instr is one decoded instruction. -type Instr struct { - Offset int64 - Bytes []byte - Mnemonic string - Operands string - Confident bool -} - -// Disassemble returns a slice of Instr for buf starting at `base`. -// arch is one of "x86", "x86_64", "arm", "thumb", "aarch64", "mips". -// For unsupported arches we emit a byte stream with "db" entries. -func Disassemble(buf []byte, base int64, arch string, maxInstr int) []map[string]any { - if maxInstr <= 0 { - maxInstr = 4096 - } - switch arch { - case "x86", "x86_64": - return diX86(buf, base, arch == "x86_64", maxInstr) - case "aarch64", "arm64": - return diAArch64(buf, base, maxInstr) - case "arm", "arm32": - return diARM32(buf, base, maxInstr) - case "thumb", "thumb16", "t16": - return diThumb16(buf, base, maxInstr) - case "mips", "mipsbe", "mips32": - return diMIPS32(buf, base, false, maxInstr) - case "mipsle", "mipsel": - return diMIPS32(buf, base, true, maxInstr) - } - return diRaw(buf, base, arch, maxInstr) -} - -// diRaw emits `db` lines 16 bytes at a time for unsupported arches. -func diRaw(buf []byte, base int64, arch string, maxInstr int) []map[string]any { - out := []map[string]any{} - step := 16 - for i := 0; i < len(buf) && len(out) < maxInstr; i += step { - end := i + step - if end > len(buf) { - end = len(buf) - } - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i:end]), - "mnemonic": "db", - "operands": hexCommaList(buf[i:end]), - "confident": false, - "note": arch + " decode not implemented; raw bytes only", - }) - } - return out -} - -// diX86 decodes x86 / x86-64 instructions. The full decoder lives in -// x86_decode.go; this dispatcher only forwards to it. -func diX86(buf []byte, base int64, is64 bool, maxInstr int) []map[string]any { - return decodeX86(buf, base, is64, maxInstr) -} - -func hexStr(b []byte) string { - const h = "0123456789abcdef" - out := make([]byte, 0, len(b)*3) - for i, c := range b { - if i > 0 { - out = append(out, ' ') - } - out = append(out, h[c>>4], h[c&0x0f]) - } - return string(out) -} - -func hexCommaList(b []byte) string { - out := "" - for i, c := range b { - if i > 0 { - out += "," - } - out += fmt.Sprintf("0x%02x", c) - } - return out -} diff --git a/internal/leviathan/disasm/x86_decode.go b/internal/leviathan/disasm/x86_decode.go deleted file mode 100644 index 7fc23c6..0000000 --- a/internal/leviathan/disasm/x86_decode.go +++ /dev/null @@ -1,1172 +0,0 @@ -package disasm - -import "fmt" - -// x86_decode.go is a from-scratch x86 / x86-64 instruction decoder. Unlike -// the previous heuristic, it tracks legacy prefixes, the REX prefix, and -// fully decodes ModR/M + SIB + displacement + immediate operands so that -// instruction *lengths* are correct (which keeps a linear sweep in sync) and -// operands are rendered in Intel syntax. -// -// Coverage: the complete general-purpose integer ISA (ALU groups, MOV/LEA, -// shifts/rotates, the F6/F7/FE/FF groups, string ops, stack ops, all near -// branches), the two-byte 0F map (Jcc/SETcc/CMOVcc, MOVZX/MOVSX, IMUL, bit -// tests, CMPXCHG/XADD, BSWAP, SHLD/SHRD, system ops) and the common SSE/SSE2 -// moves and arithmetic. VEX/EVEX (AVX), x87 detail and a few rare encodings -// fall back to a one-byte `db` resync, exactly like objdump's "(bad)". - -type x86dec struct { - buf []byte - pos int - base int64 - is64 bool - - opSize int // 16/32/64 effective operand size - addrSize int // 16/32/64 effective address size - has66 bool - seg string - rep string // "rep" / "repne" / "" - lock bool - - rexW, rexR, rexX, rexB bool - hasRex bool - - trunc bool // ran past end of buffer mid-instruction - hasRip bool // a RIP-relative memory operand was decoded - ripDisp int64 // its displacement (resolved once length is known) -} - -func decodeX86(buf []byte, base int64, is64 bool, maxInstr int) []map[string]any { - out := []map[string]any{} - i := 0 - for i < len(buf) && len(out) < maxInstr { - d := &x86dec{buf: buf, pos: i, base: base, is64: is64} - mn, ops, conf := d.decode() - var length int - if d.trunc || mn == "" { - length = 1 - mn = "db" - ops = fmt.Sprintf("0x%02x", buf[i]) - conf = false - } else { - length = d.pos - i - if length <= 0 { - length = 1 - } - if i+length > len(buf) { - length = len(buf) - i - } - if d.hasRip { - abs := base + int64(i+length) + d.ripDisp - ops += fmt.Sprintf(" ; 0x%x", abs) - } - } - out = append(out, map[string]any{ - "offset": base + int64(i), - "bytes": hexStr(buf[i : i+length]), - "mnemonic": mn, - "operands": ops, - "confident": conf, - }) - i += length - } - return out -} - -// ---- byte readers (set trunc on overrun, never panic) ---- - -func (d *x86dec) u8() uint8 { - if d.pos >= len(d.buf) { - d.trunc = true - return 0 - } - b := d.buf[d.pos] - d.pos++ - return b -} - -func (d *x86dec) u16() uint16 { return uint16(d.u8()) | uint16(d.u8())<<8 } -func (d *x86dec) u32() uint32 { - return uint32(d.u8()) | uint32(d.u8())<<8 | uint32(d.u8())<<16 | uint32(d.u8())<<24 -} -func (d *x86dec) u64() uint64 { return uint64(d.u32()) | uint64(d.u32())<<32 } - -func (d *x86dec) peek() (uint8, bool) { - if d.pos >= len(d.buf) { - return 0, false - } - return d.buf[d.pos], true -} - -// ---- prefix + opcode dispatch ---- - -func (d *x86dec) decode() (string, string, bool) { - d.opSize = 32 - if d.is64 { - d.addrSize = 64 - } else { - d.addrSize = 32 - } -prefixes: - for { - b, ok := d.peek() - if !ok { - return "", "", false - } - switch b { - case 0x66: - d.has66 = true - d.opSize = 16 - d.pos++ - continue - case 0x67: - if d.is64 { - d.addrSize = 32 - } else { - d.addrSize = 16 - } - d.pos++ - continue - case 0xF0: - d.lock = true - d.pos++ - continue - case 0xF2: - d.rep = "repne" - d.pos++ - continue - case 0xF3: - d.rep = "rep" - d.pos++ - continue - case 0x2E: - d.seg = "cs" - d.pos++ - continue - case 0x36: - d.seg = "ss" - d.pos++ - continue - case 0x3E: - d.seg = "ds" - d.pos++ - continue - case 0x26: - d.seg = "es" - d.pos++ - continue - case 0x64: - d.seg = "fs" - d.pos++ - continue - case 0x65: - d.seg = "gs" - d.pos++ - continue - } - if d.is64 && b >= 0x40 && b <= 0x4f { - d.hasRex = true - d.rexW = b&0x08 != 0 - d.rexR = b&0x04 != 0 - d.rexX = b&0x02 != 0 - d.rexB = b&0x01 != 0 - d.pos++ - } - break prefixes - } - if d.rexW { - d.opSize = 64 - } - - op := d.u8() - if d.trunc { - return "", "", false - } - if op == 0x0f { - return d.decode0F() - } - return d.decode1(op) -} - -// ---- register naming ---- - -var reg8 = [...]string{"al", "cl", "dl", "bl", "ah", "ch", "dh", "bh"} -var reg8rex = [...]string{"al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil", - "r8b", "r9b", "r10b", "r11b", "r12b", "r13b", "r14b", "r15b"} -var reg16 = [...]string{"ax", "cx", "dx", "bx", "sp", "bp", "si", "di", - "r8w", "r9w", "r10w", "r11w", "r12w", "r13w", "r14w", "r15w"} -var reg32 = [...]string{"eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi", - "r8d", "r9d", "r10d", "r11d", "r12d", "r13d", "r14d", "r15d"} -var reg64 = [...]string{"rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", - "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"} - -func (d *x86dec) regName(size, n int) string { - switch size { - case 8: - if d.hasRex { - return reg8rex[n&15] - } - return reg8[n&7] - case 16: - return reg16[n&15] - case 64: - return reg64[n&15] - case 128: - return fmt.Sprintf("xmm%d", n&15) - default: - return reg32[n&15] - } -} - -func accReg(size int) string { - switch size { - case 8: - return "al" - case 16: - return "ax" - case 64: - return "rax" - default: - return "eax" - } -} - -func ptrSize(size int) string { - switch size { - case 8: - return "byte ptr" - case 16: - return "word ptr" - case 64: - return "qword ptr" - case 128: - return "xmmword ptr" - default: - return "dword ptr" - } -} - -// ---- ModR/M + SIB ---- - -// modrm reads the ModR/M byte (and any SIB/displacement) and returns the -// extended reg field plus the textual r/m operand sized to rmSize. -func (d *x86dec) modrm(rmSize int) (int, string) { - return d.modrmEx(rmSize, true) -} - -func (d *x86dec) modrmEx(rmSize int, ptr bool) (int, string) { - m := d.u8() - mod := int(m >> 6) - reg := int((m >> 3) & 7) - if d.rexR { - reg += 8 - } - rm := int(m & 7) - if mod == 3 { - r := rm - if d.rexB { - r += 8 - } - return reg, d.regName(rmSize, r) - } - if d.addrSize == 16 { - return reg, d.mem16(mod, rm, rmSize, ptr) - } - return reg, d.mem32(mod, rm, rmSize, ptr) -} - -func (d *x86dec) mem32(mod, rm, opsize int, ptr bool) string { - var base, index string - scale := 1 - dispSize := 0 - ripRel := false - - switch { - case rm == 4: - sib := d.u8() - scale = 1 << ((sib >> 6) & 3) - idx := int((sib >> 3) & 7) - baseRaw := int(sib & 7) - if !(idx == 4 && !d.rexX) { - ir := idx - if d.rexX { - ir += 8 - } - index = d.regName(d.addrSize, ir) - } - if baseRaw == 5 && mod == 0 { - dispSize = 32 - } else { - br := baseRaw - if d.rexB { - br += 8 - } - base = d.regName(d.addrSize, br) - } - case rm == 5 && mod == 0: - dispSize = 32 - if d.is64 { - ripRel = true - } - default: - br := rm - if d.rexB { - br += 8 - } - base = d.regName(d.addrSize, br) - } - - switch mod { - case 1: - if dispSize == 0 { - dispSize = 8 - } - case 2: - if dispSize == 0 { - dispSize = 32 - } - } - - var disp int64 - hasDisp := dispSize != 0 - switch dispSize { - case 8: - disp = int64(int8(d.u8())) - case 32: - disp = int64(int32(d.u32())) - } - return d.formatMem(opsize, base, index, scale, disp, hasDisp, ripRel, ptr) -} - -var amode16 = [...]string{"bx+si", "bx+di", "bp+si", "bp+di", "si", "di", "bp", "bx"} - -func (d *x86dec) mem16(mod, rm, opsize int, ptr bool) string { - base := amode16[rm] - dispSize := 0 - if rm == 6 && mod == 0 { - base = "" - dispSize = 16 - } - switch mod { - case 1: - dispSize = 8 - case 2: - dispSize = 16 - } - var disp int64 - hasDisp := dispSize != 0 - switch dispSize { - case 8: - disp = int64(int8(d.u8())) - case 16: - disp = int64(int16(d.u16())) - } - return d.formatMem(opsize, base, "", 1, disp, hasDisp, false, ptr) -} - -func (d *x86dec) formatMem(opsize int, base, index string, scale int, disp int64, hasDisp, ripRel, ptr bool) string { - var inner string - if ripRel { - d.hasRip = true - d.ripDisp = disp - inner = "rip" + signHex(disp) - } else { - first := true - if base != "" { - inner += base - first = false - } - if index != "" { - if !first { - inner += "+" - } - inner += index - if scale != 1 { - inner += fmt.Sprintf("*%d", scale) - } - first = false - } - if hasDisp { - if first { - inner += fmt.Sprintf("0x%x", uint64(disp)&addrMask(d.addrSize)) - } else { - inner += signHex(disp) - } - } else if first { - inner += "0x0" - } - } - seg := "" - if d.seg != "" { - seg = d.seg + ":" - } - if ptr { - return ptrSize(opsize) + " " + seg + "[" + inner + "]" - } - return seg + "[" + inner + "]" -} - -func addrMask(size int) uint64 { - switch size { - case 16: - return 0xffff - case 32: - return 0xffffffff - default: - return ^uint64(0) - } -} - -func signHex(v int64) string { - if v < 0 { - return fmt.Sprintf("-0x%x", -v) - } - return fmt.Sprintf("+0x%x", v) -} - -// ---- immediates as text ---- - -func (d *x86dec) immZ() string { - if d.opSize == 16 { - return fmt.Sprintf("0x%x", d.u16()) - } - return fmt.Sprintf("0x%x", d.u32()) -} - -func (d *x86dec) immB() string { return fmt.Sprintf("0x%x", d.u8()) } -func (d *x86dec) immBs() string { return fmt.Sprintf("0x%x", uint64(int64(int8(d.u8())))) } - -// ---- condition codes ---- - -var ccName = [...]string{"o", "no", "b", "ae", "e", "ne", "be", "a", - "s", "ns", "p", "np", "l", "ge", "le", "g"} - -func (d *x86dec) rel8() string { - rel := int64(int8(d.u8())) - return fmt.Sprintf("0x%x", d.base+int64(d.pos)+rel) -} - -func (d *x86dec) rel32() string { - rel := int64(int32(d.u32())) - return fmt.Sprintf("0x%x", d.base+int64(d.pos)+rel) -} - -// stackSize is the operand size for implicit-stack ops (push/pop/call/jmp), -// which default to 64-bit in long mode. -func (d *x86dec) stackSize() int { - if d.is64 { - if d.has66 { - return 16 - } - return 64 - } - return d.opSize -} - -var aluName = [...]string{"add", "or", "adc", "sbb", "and", "sub", "xor", "cmp"} -var shiftName = [...]string{"rol", "ror", "rcl", "rcr", "shl", "shr", "shl", "sar"} - -// ---- one-byte opcode map ---- - -func (d *x86dec) decode1(op uint8) (string, string, bool) { - // ALU group: 0x00-0x3F in the standard 8-wide pattern. - if op <= 0x3f && (op&7) <= 5 { - name := aluName[(op>>3)&7] - switch op & 7 { - case 0: - reg, rm := d.modrm(8) - return name, rm + ", " + d.regName(8, reg), true - case 1: - reg, rm := d.modrm(d.opSize) - return name, rm + ", " + d.regName(d.opSize, reg), true - case 2: - reg, rm := d.modrm(8) - return name, d.regName(8, reg) + ", " + rm, true - case 3: - reg, rm := d.modrm(d.opSize) - return name, d.regName(d.opSize, reg) + ", " + rm, true - case 4: - return name, "al, " + d.immB(), true - case 5: - return name, accReg(d.opSize) + ", " + d.immZ(), true - } - } - - switch { - case op == 0x0f: - return d.decode0F() - - // INC/DEC reg (32-bit only; REX in 64-bit is handled as a prefix). - case op >= 0x40 && op <= 0x4f && !d.is64: - name := "inc" - if op >= 0x48 { - name = "dec" - } - return name, d.regName(d.opSize, int(op&7)), true - - case op >= 0x50 && op <= 0x57: - r := int(op - 0x50) - if d.rexB { - r += 8 - } - return "push", d.regName(d.stackSize(), r), true - case op >= 0x58 && op <= 0x5f: - r := int(op - 0x58) - if d.rexB { - r += 8 - } - return "pop", d.regName(d.stackSize(), r), true - - case op == 0x63: // MOVSXD (64) / ARPL (32) - if d.is64 { - reg, rm := d.modrm(32) - return "movsxd", d.regName(64, reg) + ", " + rm, true - } - reg, rm := d.modrm(16) - return "arpl", rm + ", " + d.regName(16, reg), true - - case op == 0x68: - return "push", d.immZ(), true - case op == 0x6a: - return "push", d.immBs(), true - case op == 0x69: - reg, rm := d.modrm(d.opSize) - return "imul", d.regName(d.opSize, reg) + ", " + rm + ", " + d.immZ(), true - case op == 0x6b: - reg, rm := d.modrm(d.opSize) - return "imul", d.regName(d.opSize, reg) + ", " + rm + ", " + d.immBs(), true - - case op >= 0x70 && op <= 0x7f: - return "j" + ccName[op-0x70], d.rel8(), true - - case op == 0x80: // GRP1 Eb, ib - reg, rm := d.modrm(8) - return aluName[reg&7], rm + ", " + d.immB(), true - case op == 0x81: // GRP1 Ev, iz - reg, rm := d.modrm(d.opSize) - return aluName[reg&7], rm + ", " + d.immZ(), true - case op == 0x82: // alias of 0x80 - reg, rm := d.modrm(8) - return aluName[reg&7], rm + ", " + d.immB(), true - case op == 0x83: // GRP1 Ev, ib (sign-extended) - reg, rm := d.modrm(d.opSize) - return aluName[reg&7], rm + ", " + d.immBs(), true - - case op == 0x84: - reg, rm := d.modrm(8) - return "test", rm + ", " + d.regName(8, reg), true - case op == 0x85: - reg, rm := d.modrm(d.opSize) - return "test", rm + ", " + d.regName(d.opSize, reg), true - case op == 0x86: - reg, rm := d.modrm(8) - return "xchg", rm + ", " + d.regName(8, reg), true - case op == 0x87: - reg, rm := d.modrm(d.opSize) - return "xchg", rm + ", " + d.regName(d.opSize, reg), true - - case op == 0x88: - reg, rm := d.modrm(8) - return "mov", rm + ", " + d.regName(8, reg), true - case op == 0x89: - reg, rm := d.modrm(d.opSize) - return "mov", rm + ", " + d.regName(d.opSize, reg), true - case op == 0x8a: - reg, rm := d.modrm(8) - return "mov", d.regName(8, reg) + ", " + rm, true - case op == 0x8b: - reg, rm := d.modrm(d.opSize) - return "mov", d.regName(d.opSize, reg) + ", " + rm, true - case op == 0x8c: // mov Ew, Sreg - reg, rm := d.modrm(16) - return "mov", rm + ", " + segReg(reg), true - case op == 0x8d: // lea - reg, rm := d.modrmEx(d.opSize, false) - return "lea", d.regName(d.opSize, reg) + ", " + rm, true - case op == 0x8e: // mov Sreg, Ew - reg, rm := d.modrm(16) - return "mov", segReg(reg) + ", " + rm, true - case op == 0x8f: // GRP1A pop Ev - _, rm := d.modrm(d.stackSize()) - return "pop", rm, true - - case op == 0x90: - if d.rep == "rep" { - return "pause", "", true - } - if !d.rexB { - return "nop", "", true - } - fallthrough - case op >= 0x91 && op <= 0x97: - r := int(op - 0x90) - if d.rexB { - r += 8 - } - return "xchg", accReg(d.opSize) + ", " + d.regName(d.opSize, r), true - case op == 0x98: - return map[int]string{16: "cbw", 32: "cwde", 64: "cdqe"}[d.opSize], "", true - case op == 0x99: - return map[int]string{16: "cwd", 32: "cdq", 64: "cqo"}[d.opSize], "", true - case op == 0x9b: - return "fwait", "", true - case op == 0x9c: - return "pushf", "", true - case op == 0x9d: - return "popf", "", true - case op == 0x9e: - return "sahf", "", true - case op == 0x9f: - return "lahf", "", true - - case op == 0xa0: - return "mov", "al, " + d.moffs(), true - case op == 0xa1: - return "mov", accReg(d.opSize) + ", " + d.moffs(), true - case op == 0xa2: - return "mov", d.moffs() + ", al", true - case op == 0xa3: - return "mov", d.moffs() + ", " + accReg(d.opSize), true - case op == 0xa4: - return d.repPfx() + "movsb", "", true - case op == 0xa5: - return d.repPfx() + "movs" + sizeSuffix(d.opSize), "", true - case op == 0xa6: - return d.repPfx() + "cmpsb", "", true - case op == 0xa7: - return d.repPfx() + "cmps" + sizeSuffix(d.opSize), "", true - case op == 0xa8: - return "test", "al, " + d.immB(), true - case op == 0xa9: - return "test", accReg(d.opSize) + ", " + d.immZ(), true - case op == 0xaa: - return d.repPfx() + "stosb", "", true - case op == 0xab: - return d.repPfx() + "stos" + sizeSuffix(d.opSize), "", true - case op == 0xac: - return d.repPfx() + "lodsb", "", true - case op == 0xad: - return d.repPfx() + "lods" + sizeSuffix(d.opSize), "", true - case op == 0xae: - return d.repPfx() + "scasb", "", true - case op == 0xaf: - return d.repPfx() + "scas" + sizeSuffix(d.opSize), "", true - - case op >= 0xb0 && op <= 0xb7: - r := int(op - 0xb0) - if d.rexB { - r += 8 - } - return "mov", d.regName(8, r) + ", " + d.immB(), true - case op >= 0xb8 && op <= 0xbf: - r := int(op - 0xb8) - if d.rexB { - r += 8 - } - if d.opSize == 64 { - return "movabs", d.regName(64, r) + fmt.Sprintf(", 0x%x", d.u64()), true - } - return "mov", d.regName(d.opSize, r) + ", " + d.immZ(), true - - case op == 0xc0: // GRP2 Eb, ib - reg, rm := d.modrm(8) - return shiftName[reg&7], rm + ", " + d.immB(), true - case op == 0xc1: // GRP2 Ev, ib - reg, rm := d.modrm(d.opSize) - return shiftName[reg&7], rm + ", " + d.immB(), true - case op == 0xc2: - return "ret", fmt.Sprintf("0x%x", d.u16()), true - case op == 0xc3: - return "ret", "", true - case op == 0xc6: // GRP11 mov Eb, ib - _, rm := d.modrm(8) - return "mov", rm + ", " + d.immB(), true - case op == 0xc7: // GRP11 mov Ev, iz - _, rm := d.modrm(d.opSize) - return "mov", rm + ", " + d.immZ(), true - case op == 0xc8: - iw := d.u16() - ib := d.u8() - return "enter", fmt.Sprintf("0x%x, 0x%x", iw, ib), true - case op == 0xc9: - return "leave", "", true - case op == 0xca: - return "retf", fmt.Sprintf("0x%x", d.u16()), true - case op == 0xcb: - return "retf", "", true - case op == 0xcc: - return "int3", "", true - case op == 0xcd: - return "int", d.immB(), true - case op == 0xce: - return "into", "", true - case op == 0xcf: - return "iret", "", true - - case op == 0xd0: // GRP2 Eb, 1 - reg, rm := d.modrm(8) - return shiftName[reg&7], rm + ", 1", true - case op == 0xd1: // GRP2 Ev, 1 - reg, rm := d.modrm(d.opSize) - return shiftName[reg&7], rm + ", 1", true - case op == 0xd2: // GRP2 Eb, cl - reg, rm := d.modrm(8) - return shiftName[reg&7], rm + ", cl", true - case op == 0xd3: // GRP2 Ev, cl - reg, rm := d.modrm(d.opSize) - return shiftName[reg&7], rm + ", cl", true - case op == 0xd7: - return "xlat", "", true - case op >= 0xd8 && op <= 0xdf: // x87 — consume ModR/M for length, name generic - _, rm := d.modrmEx(32, true) - return "fpu", rm, false - - case op == 0xe0: - return "loopne", d.rel8(), true - case op == 0xe1: - return "loope", d.rel8(), true - case op == 0xe2: - return "loop", d.rel8(), true - case op == 0xe3: - if d.addrSize == 32 { - return "jecxz", d.rel8(), true - } - return "jrcxz", d.rel8(), true - case op == 0xe4: - return "in", "al, " + d.immB(), true - case op == 0xe5: - return "in", accReg(d.opSize) + ", " + d.immB(), true - case op == 0xe6: - return "out", d.immB() + ", al", true - case op == 0xe7: - return "out", d.immB() + ", " + accReg(d.opSize), true - case op == 0xe8: - return "call", d.rel32(), true - case op == 0xe9: - return "jmp", d.rel32(), true - case op == 0xeb: - return "jmp", d.rel8(), true - case op == 0xec: - return "in", "al, dx", true - case op == 0xed: - return "in", accReg(d.opSize) + ", dx", true - case op == 0xee: - return "out", "dx, al", true - case op == 0xef: - return "out", "dx, " + accReg(d.opSize), true - - case op == 0xf1: - return "int1", "", true - case op == 0xf4: - return "hlt", "", true - case op == 0xf5: - return "cmc", "", true - case op == 0xf6: - return d.grp3(8) - case op == 0xf7: - return d.grp3(d.opSize) - case op == 0xf8: - return "clc", "", true - case op == 0xf9: - return "stc", "", true - case op == 0xfa: - return "cli", "", true - case op == 0xfb: - return "sti", "", true - case op == 0xfc: - return "cld", "", true - case op == 0xfd: - return "std", "", true - case op == 0xfe: // GRP4 - reg, rm := d.modrm(8) - if reg&7 == 0 { - return "inc", rm, true - } - return "dec", rm, true - case op == 0xff: // GRP5 - return d.grp5() - } - return "", "", false -} - -func (d *x86dec) repPfx() string { - if d.rep == "rep" { - return "rep " - } - if d.rep == "repne" { - return "repne " - } - return "" -} - -func sizeSuffix(size int) string { - switch size { - case 16: - return "w" - case 64: - return "q" - default: - return "d" - } -} - -func segReg(n int) string { - names := [...]string{"es", "cs", "ss", "ds", "fs", "gs", "?", "?"} - return names[n&7] -} - -func (d *x86dec) moffs() string { - var off uint64 - switch d.addrSize { - case 16: - off = uint64(d.u16()) - case 32: - off = uint64(d.u32()) - default: - off = d.u64() - } - seg := "" - if d.seg != "" { - seg = d.seg + ":" - } - return fmt.Sprintf("%s[0x%x]", seg, off) -} - -func (d *x86dec) grp3(size int) (string, string, bool) { - reg, rm := d.modrm(size) - switch reg & 7 { - case 0, 1: - if size == 8 { - return "test", rm + ", " + d.immB(), true - } - return "test", rm + ", " + d.immZ(), true - case 2: - return "not", rm, true - case 3: - return "neg", rm, true - case 4: - return "mul", rm, true - case 5: - return "imul", rm, true - case 6: - return "div", rm, true - default: - return "idiv", rm, true - } -} - -func (d *x86dec) grp5() (string, string, bool) { - // Peek reg field without committing size, then decode appropriately. - m, ok := d.peek() - if !ok { - return "", "", false - } - reg := int((m >> 3) & 7) - switch reg { - case 0: - _, rm := d.modrm(d.opSize) - return "inc", rm, true - case 1: - _, rm := d.modrm(d.opSize) - return "dec", rm, true - case 2: - _, rm := d.modrm(d.stackSize()) - return "call", rm, true - case 3: - _, rm := d.modrm(d.opSize) - return "callf", rm, true - case 4: - _, rm := d.modrm(d.stackSize()) - return "jmp", rm, true - case 5: - _, rm := d.modrm(d.opSize) - return "jmpf", rm, true - case 6: - _, rm := d.modrm(d.stackSize()) - return "push", rm, true - default: - _, rm := d.modrm(d.opSize) - return "(bad)", rm, false - } -} - -// ---- two-byte 0F map ---- - -func (d *x86dec) decode0F() (string, string, bool) { - op := d.u8() - if d.trunc { - return "", "", false - } - switch { - case op == 0x05: - return "syscall", "", true - case op == 0x06: - return "clts", "", true - case op == 0x07: - return "sysret", "", true - case op == 0x08: - return "invd", "", true - case op == 0x09: - return "wbinvd", "", true - case op == 0x0b: - return "ud2", "", true - case op == 0x1f: // multi-byte NOP - _, rm := d.modrm(d.opSize) - return "nop", rm, true - case op == 0x1e && d.rep == "rep": // ENDBR32/ENDBR64 (f3 0f 1e fa/fb) - m := d.u8() - if m == 0xfa { - return "endbr64", "", true - } - if m == 0xfb { - return "endbr32", "", true - } - return "nop", "", true - case op >= 0x18 && op <= 0x1e: // hints / prefetch - _, rm := d.modrmEx(8, true) - return "nop", rm, true - case op == 0x31: - return "rdtsc", "", true - case op == 0x34: - return "sysenter", "", true - case op == 0x35: - return "sysexit", "", true - case op == 0xa2: - return "cpuid", "", true - case op == 0x77: - return "emms", "", true - case op == 0xa0: - return "push", "fs", true - case op == 0xa1: - return "pop", "fs", true - case op == 0xa8: - return "push", "gs", true - case op == 0xa9: - return "pop", "gs", true - - case op >= 0x40 && op <= 0x4f: // CMOVcc - reg, rm := d.modrm(d.opSize) - return "cmov" + ccName[op-0x40], d.regName(d.opSize, reg) + ", " + rm, true - case op >= 0x80 && op <= 0x8f: // Jcc rel32 - return "j" + ccName[op-0x80], d.rel32(), true - case op >= 0x90 && op <= 0x9f: // SETcc Eb - _, rm := d.modrm(8) - return "set" + ccName[op-0x90], rm, true - - case op == 0xa3: - reg, rm := d.modrm(d.opSize) - return "bt", rm + ", " + d.regName(d.opSize, reg), true - case op == 0xab: - reg, rm := d.modrm(d.opSize) - return "bts", rm + ", " + d.regName(d.opSize, reg), true - case op == 0xb3: - reg, rm := d.modrm(d.opSize) - return "btr", rm + ", " + d.regName(d.opSize, reg), true - case op == 0xbb: - reg, rm := d.modrm(d.opSize) - return "btc", rm + ", " + d.regName(d.opSize, reg), true - case op == 0xba: // GRP8 Ev, ib - reg, rm := d.modrm(d.opSize) - names := map[int]string{4: "bt", 5: "bts", 6: "btr", 7: "btc"} - if n, okn := names[reg&7]; okn { - return n, rm + ", " + d.immB(), true - } - return "(bad)", rm, false - - case op == 0xa4: - reg, rm := d.modrm(d.opSize) - return "shld", rm + ", " + d.regName(d.opSize, reg) + ", " + d.immB(), true - case op == 0xa5: - reg, rm := d.modrm(d.opSize) - return "shld", rm + ", " + d.regName(d.opSize, reg) + ", cl", true - case op == 0xac: - reg, rm := d.modrm(d.opSize) - return "shrd", rm + ", " + d.regName(d.opSize, reg) + ", " + d.immB(), true - case op == 0xad: - reg, rm := d.modrm(d.opSize) - return "shrd", rm + ", " + d.regName(d.opSize, reg) + ", cl", true - case op == 0xaf: - reg, rm := d.modrm(d.opSize) - return "imul", d.regName(d.opSize, reg) + ", " + rm, true - - case op == 0xb0: - reg, rm := d.modrm(8) - return "cmpxchg", rm + ", " + d.regName(8, reg), true - case op == 0xb1: - reg, rm := d.modrm(d.opSize) - return "cmpxchg", rm + ", " + d.regName(d.opSize, reg), true - case op == 0xb6: - reg, rm := d.modrm(8) - return "movzx", d.regName(d.opSize, reg) + ", " + rm, true - case op == 0xb7: - reg, rm := d.modrm(16) - return "movzx", d.regName(d.opSize, reg) + ", " + rm, true - case op == 0xbe: - reg, rm := d.modrm(8) - return "movsx", d.regName(d.opSize, reg) + ", " + rm, true - case op == 0xbf: - reg, rm := d.modrm(16) - return "movsx", d.regName(d.opSize, reg) + ", " + rm, true - case op == 0xbc: - reg, rm := d.modrm(d.opSize) - name := "bsf" - if d.rep == "rep" { - name = "tzcnt" - } - return name, d.regName(d.opSize, reg) + ", " + rm, true - case op == 0xbd: - reg, rm := d.modrm(d.opSize) - name := "bsr" - if d.rep == "rep" { - name = "lzcnt" - } - return name, d.regName(d.opSize, reg) + ", " + rm, true - case op == 0xc0: - reg, rm := d.modrm(8) - return "xadd", rm + ", " + d.regName(8, reg), true - case op == 0xc1: - reg, rm := d.modrm(d.opSize) - return "xadd", rm + ", " + d.regName(d.opSize, reg), true - case op >= 0xc8 && op <= 0xcf: - r := int(op - 0xc8) - if d.rexB { - r += 8 - } - return "bswap", d.regName(d.opSize, r), true - } - // Common SSE / SSE2 (XMM) subset. - if mn, ops, ok := d.sse0F(op); ok { - return mn, ops, true - } - return "", "", false -} - -// sse0F decodes the common SSE/SSE2 instructions. Mnemonics depend on the -// mandatory prefix (none / 0x66 / 0xF3 / 0xF2). -func (d *x86dec) sse0F(op uint8) (string, string, bool) { - pfx := "none" - switch { - case d.has66: - pfx = "66" - case d.rep == "rep": - pfx = "f3" - case d.rep == "repne": - pfx = "f2" - } - - xmmRM := func(name string, store bool) (string, string, bool) { - reg, rm := d.modrm(128) - r := d.regName(128, reg) - if store { - return name, rm + ", " + r, true - } - return name, r + ", " + rm, true - } - - switch op { - case 0x10: - return xmmRM(map[string]string{"none": "movups", "66": "movupd", "f3": "movss", "f2": "movsd"}[pfx], false) - case 0x11: - return xmmRM(map[string]string{"none": "movups", "66": "movupd", "f3": "movss", "f2": "movsd"}[pfx], true) - case 0x28: - return xmmRM(map[string]string{"none": "movaps", "66": "movapd"}[pfx2(pfx)], false) - case 0x29: - return xmmRM(map[string]string{"none": "movaps", "66": "movapd"}[pfx2(pfx)], true) - case 0x2e: - return xmmRM(map[string]string{"none": "ucomiss", "66": "ucomisd"}[pfx2(pfx)], false) - case 0x2f: - return xmmRM(map[string]string{"none": "comiss", "66": "comisd"}[pfx2(pfx)], false) - case 0x51: - return xmmRM(sseScalar("sqrt", pfx), false) - case 0x54: - return xmmRM(map[string]string{"none": "andps", "66": "andpd"}[pfx2(pfx)], false) - case 0x57: - return xmmRM(map[string]string{"none": "xorps", "66": "xorpd"}[pfx2(pfx)], false) - case 0x58: - return xmmRM(sseScalar("add", pfx), false) - case 0x59: - return xmmRM(sseScalar("mul", pfx), false) - case 0x5c: - return xmmRM(sseScalar("sub", pfx), false) - case 0x5e: - return xmmRM(sseScalar("div", pfx), false) - case 0x5d: - return xmmRM(sseScalar("min", pfx), false) - case 0x5f: - return xmmRM(sseScalar("max", pfx), false) - case 0x6e: // movd/movq xmm, r/m - sz := 32 - name := "movd" - if d.opSize == 64 { - sz, name = 64, "movq" - } - reg, rm := d.modrm(sz) - return name, d.regName(128, reg) + ", " + rm, true - case 0x7e: - if pfx == "f3" { // movq xmm, xmm/m64 - return xmmRM("movq", false) - } - sz := 32 - name := "movd" - if d.opSize == 64 { - sz, name = 64, "movq" - } - reg, rm := d.modrm(sz) - return name, rm + ", " + d.regName(128, reg), true - case 0x6f: - return xmmRM(map[string]string{"66": "movdqa", "f3": "movdqu"}[pfx], false) - case 0x7f: - return xmmRM(map[string]string{"66": "movdqa", "f3": "movdqu"}[pfx], true) - case 0xd6: - return xmmRM("movq", true) - case 0xef: - return xmmRM("pxor", false) - case 0x76: - return xmmRM("pcmpeqd", false) - case 0x74: - return xmmRM("pcmpeqb", false) - case 0xfc: - return xmmRM("paddb", false) - case 0xfe: - return xmmRM("paddd", false) - case 0x12: - return xmmRM(map[string]string{"none": "movlps", "66": "movlpd"}[pfx2(pfx)], false) - case 0x13: - return xmmRM(map[string]string{"none": "movlps", "66": "movlpd"}[pfx2(pfx)], true) - case 0x16: - return xmmRM(map[string]string{"none": "movhps", "66": "movhpd"}[pfx2(pfx)], false) - case 0x17: - return xmmRM(map[string]string{"none": "movhps", "66": "movhpd"}[pfx2(pfx)], true) - case 0x14: - return xmmRM(map[string]string{"none": "unpcklps", "66": "unpcklpd"}[pfx2(pfx)], false) - case 0x2a: - reg, rm := d.modrm(d.opSize) - return sseScalar("cvtsi2", pfx), d.regName(128, reg) + ", " + rm, true - case 0x2c, 0x2d: - reg, rm := d.modrm(128) - return "cvt", d.regName(d.opSize, reg) + ", " + rm, true - } - return "", "", false -} - -// pfx2 collapses f3/f2 to "none" for instructions that only vary between the -// no-prefix and 0x66 forms. -func pfx2(p string) string { - if p == "66" { - return "66" - } - return "none" -} - -func sseScalar(base, pfx string) string { - switch pfx { - case "f3": - return base + "ss" - case "f2": - return base + "sd" - case "66": - return base + "pd" - default: - return base + "ps" - } -} diff --git a/internal/leviathan/document/document.go b/internal/leviathan/document/document.go deleted file mode 100644 index d43797d..0000000 --- a/internal/leviathan/document/document.go +++ /dev/null @@ -1,439 +0,0 @@ -// Package document implements the chunked binary document model. -// -// The document holds three conceptual layers per slot: -// -// source — immutable original bytes, loaded once. -// patch — sparse overlay of modified bytes keyed by offset. -// working — logical view = source with patch applied on read. -// -// Reads happen through windowed Range() calls so neither the editor -// nor analysis services need to materialise the whole file. -package document - -import ( - "errors" - "fmt" - "io" - "os" - "path/filepath" - "sync" -) - -// ChunkSize is the granularity at which source bytes are exposed to -// analysis services. It is independent of the view row width. -const ChunkSize = 64 * 1024 - -// Document represents one loaded artefact. A Session may hold many. -type Document struct { - mu sync.RWMutex - name string - source []byte // original immutable content - path string // optional immutable file-backed source - size int64 - patch map[int64]byte - history []EditOp - redo []EditOp - cursor int -} - -// EditOp records a single byte-level mutation for undo/redo. -type EditOp struct { - Offset int64 - Old byte - New byte - Kind string // "set", "fill", "xor" -} - -// New creates a Document from raw source bytes. The slice is not -// copied; callers must not mutate it after handoff. -func New(name string, src []byte) *Document { - return &Document{ - name: name, - source: src, - size: int64(len(src)), - patch: make(map[int64]byte), - } -} - -// Open creates a Document backed by an immutable file on disk. Source -// bytes are read on demand through Range rather than loaded eagerly. -func Open(path string) (*Document, error) { - st, err := os.Stat(path) - if err != nil { - return nil, err - } - if st.IsDir() { - return nil, errors.New("document path is a directory") - } - return &Document{ - name: filepath.Base(path), - path: path, - size: st.Size(), - patch: make(map[int64]byte), - }, nil -} - -// Name returns the document's display name. -func (d *Document) Name() string { - d.mu.RLock() - defer d.mu.RUnlock() - return d.name -} - -// Size returns the logical document length in bytes. -func (d *Document) Size() int64 { - d.mu.RLock() - defer d.mu.RUnlock() - return d.size -} - -// At returns the logical byte at offset (patch takes precedence). -func (d *Document) At(offset int64) (byte, error) { - d.mu.RLock() - defer d.mu.RUnlock() - if offset < 0 || offset >= d.size { - return 0, errors.New("offset out of range") - } - if b, ok := d.patch[offset]; ok { - return b, nil - } - return d.sourceByteLocked(offset) -} - -// Range copies [start, start+length) into dst. It applies the patch -// overlay. Callers get a fresh slice they are free to retain. -func (d *Document) Range(start, length int64) ([]byte, error) { - d.mu.RLock() - defer d.mu.RUnlock() - n := d.size - if start < 0 || start > n { - return nil, errors.New("range start out of bounds") - } - if length < 0 { - return nil, errors.New("negative range") - } - if length > n-start { - length = n - start - } - out, err := d.sourceRangeLocked(start, length) - if err != nil { - return nil, err - } - if len(d.patch) > 0 { - end := start + length - for off, b := range d.patch { - if off >= start && off < end { - out[off-start] = b - } - } - } - return out, nil -} - -// IsDirty reports whether any byte has been patched. -func (d *Document) IsDirty() bool { - d.mu.RLock() - defer d.mu.RUnlock() - return len(d.patch) > 0 -} - -// Set patches a single byte. Returns the previous value. -func (d *Document) Set(offset int64, b byte) (byte, error) { - d.mu.Lock() - defer d.mu.Unlock() - if offset < 0 || offset >= d.size { - return 0, errors.New("offset out of range") - } - orig, err := d.sourceByteLocked(offset) - if err != nil { - return 0, err - } - var old byte - if v, ok := d.patch[offset]; ok { - old = v - } else { - old = orig - } - if b == orig { - delete(d.patch, offset) - } else { - d.patch[offset] = b - } - d.history = append(d.history, EditOp{Offset: offset, Old: old, New: b, Kind: "set"}) - d.redo = nil - return old, nil -} - -// Fill patches [start,start+length) with b. -func (d *Document) Fill(start, length int64, b byte) error { - d.mu.Lock() - defer d.mu.Unlock() - if err := d.validateRangeLocked("fill", start, length); err != nil { - return err - } - orig, err := d.sourceRangeLocked(start, length) - if err != nil { - return err - } - for i := int64(0); i < length; i++ { - off := start + i - origB := orig[i] - var old byte - if v, ok := d.patch[off]; ok { - old = v - } else { - old = origB - } - if b == origB { - delete(d.patch, off) - } else { - d.patch[off] = b - } - d.history = append(d.history, EditOp{Offset: off, Old: old, New: b, Kind: "fill"}) - } - d.redo = nil - return nil -} - -// XOR patches [start,start+length) with a repeating key. -func (d *Document) XOR(start, length int64, key []byte) error { - if len(key) == 0 { - return errors.New("empty key") - } - d.mu.Lock() - defer d.mu.Unlock() - if err := d.validateRangeLocked("xor", start, length); err != nil { - return err - } - orig, err := d.sourceRangeLocked(start, length) - if err != nil { - return err - } - for i := int64(0); i < length; i++ { - off := start + i - origB := orig[i] - var cur byte - if v, ok := d.patch[off]; ok { - cur = v - } else { - cur = origB - } - newB := cur ^ key[i%int64(len(key))] - if newB == origB { - delete(d.patch, off) - } else { - d.patch[off] = newB - } - d.history = append(d.history, EditOp{Offset: off, Old: cur, New: newB, Kind: "xor"}) - } - d.redo = nil - return nil -} - -// Copy copies [src,src+length) onto [dst,dst+length) as a patch. -func (d *Document) Copy(src, length, dst int64) error { - d.mu.Lock() - defer d.mu.Unlock() - if err := d.validateRangeLocked("copy source", src, length); err != nil { - return err - } - if err := d.validateRangeLocked("copy destination", dst, length); err != nil { - return err - } - buf, err := d.workingRangeLocked(src, length) - if err != nil { - return err - } - dstOrig, err := d.sourceRangeLocked(dst, length) - if err != nil { - return err - } - for i := int64(0); i < length; i++ { - off := dst + i - origB := dstOrig[i] - var cur byte - if v, ok := d.patch[off]; ok { - cur = v - } else { - cur = origB - } - b := buf[i] - if b == origB { - delete(d.patch, off) - } else { - d.patch[off] = b - } - d.history = append(d.history, EditOp{Offset: off, Old: cur, New: b, Kind: "fill"}) - } - d.redo = nil - return nil -} - -// Undo reverts the last edit. Returns false if there is no history. -func (d *Document) Undo() bool { - d.mu.Lock() - defer d.mu.Unlock() - if len(d.history) == 0 { - return false - } - op := d.history[len(d.history)-1] - d.history = d.history[:len(d.history)-1] - orig, err := d.sourceByteLocked(op.Offset) - if err != nil { - return false - } - if op.Old == orig { - delete(d.patch, op.Offset) - } else { - d.patch[op.Offset] = op.Old - } - d.redo = append(d.redo, op) - return true -} - -// Redo reapplies the last undone edit. Returns false if there is no redo. -func (d *Document) Redo() bool { - d.mu.Lock() - defer d.mu.Unlock() - if len(d.redo) == 0 { - return false - } - op := d.redo[len(d.redo)-1] - d.redo = d.redo[:len(d.redo)-1] - orig, err := d.sourceByteLocked(op.Offset) - if err != nil { - return false - } - if op.New == orig { - delete(d.patch, op.Offset) - } else { - d.patch[op.Offset] = op.New - } - d.history = append(d.history, op) - return true -} - -// Revert drops all patches and history. -func (d *Document) Revert() { - d.mu.Lock() - defer d.mu.Unlock() - d.patch = make(map[int64]byte) - d.history = nil - d.redo = nil -} - -// Patches returns a snapshot of the patch overlay. -func (d *Document) Patches() map[int64]byte { - d.mu.RLock() - defer d.mu.RUnlock() - out := make(map[int64]byte, len(d.patch)) - for k, v := range d.patch { - out[k] = v - } - return out -} - -// HistoryLen returns the number of recorded edits. -func (d *Document) HistoryLen() int { - d.mu.RLock() - defer d.mu.RUnlock() - return len(d.history) -} - -// RedoLen returns the number of redoable edits. -func (d *Document) RedoLen() int { - d.mu.RLock() - defer d.mu.RUnlock() - return len(d.redo) -} - -// ForEachChunk yields chunks covering [start,start+length) with the -// working-copy bytes visible. It is suitable for analysis passes that -// want bounded memory use. -func (d *Document) ForEachChunk(start, length int64, fn func(off int64, chunk []byte) error) error { - d.mu.RLock() - n := d.size - d.mu.RUnlock() - if start < 0 { - start = 0 - } - end := start + length - if length < 0 || end > n { - end = n - } - off := start - for off < end { - chunkEnd := off + ChunkSize - if chunkEnd > end { - chunkEnd = end - } - buf, err := d.Range(off, chunkEnd-off) - if err != nil { - return err - } - if err := fn(off, buf); err != nil { - return err - } - off = chunkEnd - } - return nil -} - -func (d *Document) sourceByteLocked(offset int64) (byte, error) { - buf, err := d.sourceRangeLocked(offset, 1) - if err != nil { - return 0, err - } - if len(buf) == 0 { - return 0, errors.New("offset out of range") - } - return buf[0], nil -} - -func (d *Document) workingRangeLocked(start, length int64) ([]byte, error) { - buf, err := d.sourceRangeLocked(start, length) - if err != nil { - return nil, err - } - for off, b := range d.patch { - if off >= start && off < start+length { - buf[off-start] = b - } - } - return buf, nil -} - -func (d *Document) sourceRangeLocked(start, length int64) ([]byte, error) { - if err := d.validateRangeLocked("range", start, length); err != nil { - return nil, err - } - if length == 0 { - return []byte{}, nil - } - if d.path == "" { - out := make([]byte, length) - copy(out, d.source[start:start+length]) - return out, nil - } - f, err := os.Open(d.path) - if err != nil { - return nil, err - } - defer f.Close() - out := make([]byte, length) - _, err = f.ReadAt(out, start) - if errors.Is(err, io.EOF) { - err = nil - } - if err != nil { - return nil, err - } - return out, nil -} - -func (d *Document) validateRangeLocked(label string, start, length int64) error { - if start < 0 || start > d.size || length < 0 || length > d.size-start { - return fmt.Errorf("%s range invalid", label) - } - return nil -} diff --git a/internal/leviathan/document/session.go b/internal/leviathan/document/session.go deleted file mode 100644 index c0c0a86..0000000 --- a/internal/leviathan/document/session.go +++ /dev/null @@ -1,62 +0,0 @@ -package document - -import ( - "errors" - "sync" -) - -// Slot identifies one of the documents in a session. -type Slot string - -const ( - SlotOriginal Slot = "original" - SlotCompare Slot = "compare" -) - -// Session tracks loaded documents and addresses them by slot. -// The original slot is the active working document; compare is -// used by the diff tooling. -type Session struct { - mu sync.RWMutex - documents map[Slot]*Document -} - -// NewSession returns an empty session. -func NewSession() *Session { - return &Session{documents: make(map[Slot]*Document)} -} - -// Load replaces the document in the given slot. -func (s *Session) Load(slot Slot, name string, src []byte) *Document { - s.mu.Lock() - defer s.mu.Unlock() - d := New(name, src) - s.documents[slot] = d - return d -} - -// Get returns the document for a slot, or an error if not loaded. -func (s *Session) Get(slot Slot) (*Document, error) { - s.mu.RLock() - defer s.mu.RUnlock() - d, ok := s.documents[slot] - if !ok { - return nil, errors.New("slot not loaded: " + string(slot)) - } - return d, nil -} - -// Has reports whether a slot has a document loaded. -func (s *Session) Has(slot Slot) bool { - s.mu.RLock() - defer s.mu.RUnlock() - _, ok := s.documents[slot] - return ok -} - -// Drop removes a slot. -func (s *Session) Drop(slot Slot) { - s.mu.Lock() - defer s.mu.Unlock() - delete(s.documents, slot) -} diff --git a/internal/leviathan/parsers/asn1.go b/internal/leviathan/parsers/asn1.go deleted file mode 100644 index 5baf9aa..0000000 --- a/internal/leviathan/parsers/asn1.go +++ /dev/null @@ -1,321 +0,0 @@ -package parsers - -import ( - "encoding/asn1" - "encoding/pem" - "errors" - "fmt" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// ParseASN1 produces a field tree for the DER TLV structure starting -// at `at`, spanning at most `length` bytes. Nested SEQUENCE / SET -// structures are decoded recursively; primitive content is rendered -// as best-effort. -func ParseASN1(d *document.Document, at, length int64) (*Field, error) { - size := d.Size() - if length < 0 || at+length > size { - length = size - at - } - if length <= 0 { - return nil, errors.New("asn1: empty range") - } - buf, err := d.Range(at, length) - if err != nil { - return nil, err - } - // If the buffer looks like PEM, decode first. - if pemBlock, _ := pem.Decode(buf); pemBlock != nil { - root := &Field{Name: "PEM " + pemBlock.Type, Type: "container", Offset: at, Length: length} - inner, err := decodeASN1(pemBlock.Bytes, at) - if err == nil { - root.Children = append(root.Children, inner) - } else { - root.Note = "pem decoded but asn1 failed: " + err.Error() - } - return root, nil - } - return decodeASN1(buf, at) -} - -// decodeASN1 walks buf recursively, treating it as one or more DER TLVs. -func decodeASN1(buf []byte, base int64) (*Field, error) { - root := &Field{Name: "ASN.1", Type: "container", Offset: base, Length: int64(len(buf))} - off := 0 - for off < len(buf) { - raw, consumed, err := readTLV(buf[off:]) - if err != nil { - if off == 0 { - return nil, err - } - break - } - node := asn1Node(raw, base+int64(off)) - root.Children = append(root.Children, node) - off += consumed - } - if len(root.Children) == 0 { - return nil, errors.New("asn1: no TLV decoded") - } - if len(root.Children) == 1 { - return root.Children[0], nil - } - return root, nil -} - -// tlv is one DER TLV we've split out of the byte stream. -type tlv struct { - Class int // 0=universal, 1=application, 2=context, 3=private - Constructed bool - Tag int - Content []byte // the V of the TLV - HeaderLen int // length of T+L bytes -} - -func readTLV(buf []byte) (tlv, int, error) { - if len(buf) < 2 { - return tlv{}, 0, errors.New("short TLV") - } - b0 := buf[0] - t := tlv{ - Class: int(b0 >> 6), - Constructed: b0&0x20 != 0, - } - tagVal := int(b0 & 0x1f) - i := 1 - if tagVal == 0x1f { - // multi-byte tag - tagVal = 0 - for i < len(buf) { - c := buf[i] - i++ - tagVal = (tagVal << 7) | int(c&0x7f) - if c&0x80 == 0 { - break - } - } - } - t.Tag = tagVal - if i >= len(buf) { - return tlv{}, 0, errors.New("truncated length") - } - ln := int(buf[i]) - i++ - if ln >= 0x80 { - n := ln & 0x7f - if n == 0 || n > 8 || i+n > len(buf) { - return tlv{}, 0, errors.New("bad length") - } - ln = 0 - for k := 0; k < n; k++ { - ln = (ln << 8) | int(buf[i+k]) - } - i += n - } - if i+ln > len(buf) { - return tlv{}, 0, errors.New("length exceeds buffer") - } - t.HeaderLen = i - t.Content = buf[i : i+ln] - return t, i + ln, nil -} - -func asn1Node(t tlv, off int64) *Field { - name, kind := asn1TagName(t) - total := int64(t.HeaderLen + len(t.Content)) - node := &Field{Name: name, Type: kind, Offset: off, Length: total} - if t.Class == 2 { - node.Name = fmt.Sprintf("[%d]", t.Tag) - } - if t.Constructed { - // Recurse into children. - inner := t.Content - childOff := off + int64(t.HeaderLen) - i := 0 - for i < len(inner) { - child, consumed, err := readTLV(inner[i:]) - if err != nil { - break - } - node.Children = append(node.Children, asn1Node(child, childOff+int64(i))) - i += consumed - } - } else { - node.Value = renderPrimitive(t) - } - return node -} - -func asn1TagName(t tlv) (string, string) { - if t.Class != 0 { - prefix := []string{"", "APP ", "CTX ", "PRIV "}[t.Class] - return fmt.Sprintf("%s%d", prefix, t.Tag), "tag" - } - names := map[int]string{ - 1: "BOOLEAN", - 2: "INTEGER", - 3: "BIT STRING", - 4: "OCTET STRING", - 5: "NULL", - 6: "OBJECT IDENTIFIER", - 10: "ENUMERATED", - 12: "UTF8String", - 16: "SEQUENCE", - 17: "SET", - 19: "PrintableString", - 20: "T61String", - 22: "IA5String", - 23: "UTCTime", - 24: "GeneralizedTime", - } - if n, ok := names[t.Tag]; ok { - return n, strings.ToLower(n) - } - return fmt.Sprintf("tag %d", t.Tag), "tag" -} - -func renderPrimitive(t tlv) string { - switch t.Tag { - case 1: - if len(t.Content) == 1 { - if t.Content[0] == 0 { - return "false" - } - return "true" - } - case 2: - return fmt.Sprintf("%s (%d bytes)", bigIntString(t.Content), len(t.Content)) - case 3: - if len(t.Content) == 0 { - return "empty" - } - return fmt.Sprintf("unused=%d bits=%x", t.Content[0], t.Content[1:]) - case 4: - if isPrintable(t.Content) { - return fmt.Sprintf("%q (%d bytes)", string(t.Content), len(t.Content)) - } - return fmt.Sprintf("%x (%d bytes)", t.Content, len(t.Content)) - case 5: - return "null" - case 6: - oid, err := parseOID(t.Content) - if err != nil { - return err.Error() - } - return oid + oidNote(oid) - case 12, 19, 20, 22: - return fmt.Sprintf("%q", string(t.Content)) - case 23, 24: - return fmt.Sprintf("%q (time)", string(t.Content)) - } - if isPrintable(t.Content) { - return fmt.Sprintf("%q", string(t.Content)) - } - if len(t.Content) > 32 { - return fmt.Sprintf("%x… (%d bytes)", t.Content[:32], len(t.Content)) - } - return fmt.Sprintf("%x", t.Content) -} - -func bigIntString(b []byte) string { - if len(b) == 0 { - return "0" - } - if len(b) <= 8 { - var v int64 - negative := b[0]&0x80 != 0 - for _, c := range b { - v = (v << 8) | int64(c) - } - if negative { - // sign-extend - v -= 1 << uint(8*len(b)) - } - return fmt.Sprintf("%d", v) - } - // large — hex is more useful than a decimal approximation. - return fmt.Sprintf("0x%x", b) -} - -func parseOID(b []byte) (string, error) { - // Use Go's asn1 parser for correctness. - var oid asn1.ObjectIdentifier - rest, err := asn1.Unmarshal(append([]byte{0x06, byte(len(b))}, b...), &oid) - if err != nil || len(rest) != 0 { - // Fallback manual decode. - return manualOID(b), nil - } - return oid.String(), nil -} - -func manualOID(b []byte) string { - if len(b) == 0 { - return "" - } - parts := []string{} - first := int(b[0]) - parts = append(parts, fmt.Sprintf("%d.%d", first/40, first%40)) - v := 0 - for _, c := range b[1:] { - v = (v << 7) | int(c&0x7f) - if c&0x80 == 0 { - parts = append(parts, fmt.Sprintf("%d", v)) - v = 0 - } - } - return strings.Join(parts, ".") -} - -// Known-OID table for human-readable hints in cert dumps. -var oidNames = map[string]string{ - "1.2.840.113549.1.1.1": " rsaEncryption", - "1.2.840.113549.1.1.11": " sha256WithRSAEncryption", - "1.2.840.113549.1.1.5": " sha1WithRSAEncryption", - "1.2.840.113549.1.1.13": " sha512WithRSAEncryption", - "1.2.840.10045.2.1": " ecPublicKey", - "1.2.840.10045.4.3.2": " ecdsaWithSHA256", - "1.3.101.112": " Ed25519", - "2.5.4.3": " commonName", - "2.5.4.6": " countryName", - "2.5.4.7": " localityName", - "2.5.4.8": " stateOrProvinceName", - "2.5.4.10": " organizationName", - "2.5.4.11": " organizationalUnitName", - "2.5.4.5": " serialNumber", - "1.2.840.113549.1.9.1": " emailAddress", - "2.5.29.14": " subjectKeyIdentifier", - "2.5.29.15": " keyUsage", - "2.5.29.17": " subjectAltName", - "2.5.29.19": " basicConstraints", - "2.5.29.31": " cRLDistributionPoints", - "2.5.29.32": " certificatePolicies", - "2.5.29.35": " authorityKeyIdentifier", - "2.5.29.37": " extKeyUsage", - "1.3.6.1.5.5.7.3.1": " serverAuth", - "1.3.6.1.5.5.7.3.2": " clientAuth", - "1.3.6.1.5.5.7.1.1": " authorityInfoAccess", -} - -func oidNote(oid string) string { - if n, ok := oidNames[oid]; ok { - return n - } - return "" -} - -func isPrintable(b []byte) bool { - if len(b) == 0 { - return false - } - for _, c := range b { - if c == 0x09 || c == 0x0a || c == 0x0d { - continue - } - if c < 0x20 || c > 0x7e { - return false - } - } - return true -} diff --git a/internal/leviathan/parsers/cert.go b/internal/leviathan/parsers/cert.go deleted file mode 100644 index 492eafa..0000000 --- a/internal/leviathan/parsers/cert.go +++ /dev/null @@ -1,196 +0,0 @@ -package parsers - -import ( - "crypto/sha256" - "crypto/x509" - "encoding/pem" - "errors" - "fmt" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// ParseCerts scans the whole document for PEM-encoded certificates -// and for DER-encoded X.509 certificates appearing at arbitrary -// offsets (heuristic: SEQUENCE 0x30 0x82 , then tbsCertificate -// SEQUENCE, then version INTEGER). Returns a list of decoded certs. -func ParseCerts(d *document.Document) (*Field, error) { - size := d.Size() - root := &Field{Name: "Certificates", Type: "container", Offset: 0, Length: size} - - // Read whole doc — bounded by caller's judgment. For huge firmware - // blobs this might be expensive but certs are small; most real - // cases are 32*1024*1024 { - return nil, errors.New("certs scan skipped: file > 32 MiB") - } - all, err := d.Range(0, size) - if err != nil { - return nil, err - } - - // PEM pass. - rest := all - pemCount := 0 - var pemOff int64 = 0 - for { - block, next := pem.Decode(rest) - if block == nil { - break - } - // Approximate offset: the header "-----BEGIN" will be somewhere in `rest`. - idx := indexOfHeader(rest) - if idx < 0 { - break - } - start := pemOff + int64(idx) - end := pemOff + int64(len(rest)-len(next)) - pemCount++ - child := &Field{ - Name: fmt.Sprintf("PEM %s #%d", block.Type, pemCount), - Type: "pem", - Offset: start, - Length: end - start, - } - if block.Type == "CERTIFICATE" { - if cert, err := x509.ParseCertificate(block.Bytes); err == nil { - populateCert(child, cert) - } else { - child.Note = "x509 parse failed: " + err.Error() - } - } - root.Children = append(root.Children, child) - pemOff += int64(len(rest) - len(next)) - rest = next - } - - // DER pass — scan for SEQUENCE 0x30 0x82 xx yy (definite long-form, 2 length bytes). - // - // Each candidate runs a full x509.ParseCertificate over a blob up to 64 KiB. - // Without guards a buffer dense with "30 82" byte pairs (random/firmware - // data, or a hostile file) would attempt that parse at nearly every offset - // — O(n) expensive parses, enough to freeze the UI. Two guards keep it - // bounded: a cheap structural pre-filter (a DER certificate body begins - // with the tbsCertificate SEQUENCE, 0x30) rejects almost all false - // candidates without parsing, and a hard cap on parse attempts bounds even - // adversarial input that defeats the pre-filter. - const maxDERAttempts = 1 << 16 - derCount := 0 - derAttempts := 0 - for i := 0; i+4 < len(all); i++ { - if all[i] != 0x30 || all[i+1] != 0x82 { - continue - } - if all[i+4] != 0x30 { - continue - } - totalLen := int(all[i+2])<<8 | int(all[i+3]) - if totalLen < 100 || i+4+totalLen > len(all) { - continue - } - if derAttempts++; derAttempts > maxDERAttempts { - break - } - blob := all[i : i+4+totalLen] - cert, err := x509.ParseCertificate(blob) - if err != nil { - continue - } - derCount++ - child := &Field{ - Name: fmt.Sprintf("DER cert #%d", derCount), - Type: "der", - Offset: int64(i), - Length: int64(4 + totalLen), - } - populateCert(child, cert) - root.Children = append(root.Children, child) - i += 4 + totalLen - 1 - } - - if len(root.Children) == 0 { - return nil, errors.New("no certificates found") - } - root.Value = fmt.Sprintf("PEM=%d DER=%d", pemCount, derCount) - return root, nil -} - -func populateCert(f *Field, c *x509.Certificate) { - f.Value = fmt.Sprintf("subject=%q issuer=%q serial=%x", - c.Subject.String(), c.Issuer.String(), c.SerialNumber) - add := func(name, value string) { - f.Children = append(f.Children, &Field{Name: name, Type: "field", Offset: f.Offset, Length: 0, Value: value}) - } - add("subject", c.Subject.String()) - add("issuer", c.Issuer.String()) - add("serial", fmt.Sprintf("%x", c.SerialNumber)) - add("valid_from", c.NotBefore.UTC().Format("2006-01-02 15:04:05 UTC")) - add("valid_until", c.NotAfter.UTC().Format("2006-01-02 15:04:05 UTC")) - add("pubkey_alg", c.PublicKeyAlgorithm.String()) - add("sig_alg", c.SignatureAlgorithm.String()) - if len(c.DNSNames) > 0 { - add("dns_names", strings.Join(c.DNSNames, ", ")) - } - if len(c.IPAddresses) > 0 { - ips := make([]string, 0, len(c.IPAddresses)) - for _, ip := range c.IPAddresses { - ips = append(ips, ip.String()) - } - add("ip_addresses", strings.Join(ips, ", ")) - } - if len(c.EmailAddresses) > 0 { - add("emails", strings.Join(c.EmailAddresses, ", ")) - } - if c.IsCA { - add("ca", "true") - } - if c.KeyUsage != 0 { - add("key_usage", fmt.Sprintf("0x%x", int(c.KeyUsage))) - } - if len(c.ExtKeyUsage) > 0 { - parts := make([]string, 0, len(c.ExtKeyUsage)) - for _, u := range c.ExtKeyUsage { - parts = append(parts, extKeyUsageName(u)) - } - add("ext_key_usage", strings.Join(parts, ", ")) - } - // SHA-256 fingerprint of DER - add("sha256_fingerprint", sha256Hex(c.Raw)) -} - -func extKeyUsageName(u x509.ExtKeyUsage) string { - switch u { - case x509.ExtKeyUsageAny: - return "any" - case x509.ExtKeyUsageServerAuth: - return "serverAuth" - case x509.ExtKeyUsageClientAuth: - return "clientAuth" - case x509.ExtKeyUsageCodeSigning: - return "codeSigning" - case x509.ExtKeyUsageEmailProtection: - return "emailProtection" - case x509.ExtKeyUsageTimeStamping: - return "timeStamping" - case x509.ExtKeyUsageOCSPSigning: - return "OCSPSigning" - } - return fmt.Sprintf("%d", int(u)) -} - -func sha256Hex(b []byte) string { - h := sha256.Sum256(b) - return fmt.Sprintf("%x", h[:]) -} - -// indexOfHeader returns the byte offset of the next PEM BEGIN line. -func indexOfHeader(b []byte) int { - const tag = "-----BEGIN" - for i := 0; i+len(tag) <= len(b); i++ { - if string(b[i:i+len(tag)]) == tag { - return i - } - } - return -1 -} diff --git a/internal/leviathan/parsers/executable.go b/internal/leviathan/parsers/executable.go deleted file mode 100644 index 246bd02..0000000 --- a/internal/leviathan/parsers/executable.go +++ /dev/null @@ -1,412 +0,0 @@ -package parsers - -import ( - "encoding/binary" - "errors" - "fmt" - "sort" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// ExecutableRegion is a file-backed executable code range. -type ExecutableRegion struct { - Name string `json:"name"` - Offset int64 `json:"offset"` - Size int64 `json:"size"` - Address uint64 `json:"address"` -} - -// FunctionSeed is a named function entry mapped to a file offset. -type FunctionSeed struct { - Name string `json:"name"` - Offset int64 `json:"offset"` - Address uint64 `json:"address"` - Size uint64 `json:"size"` - Source string `json:"source"` -} - -// ExecutableInfo contains disassembly-oriented metadata for an ELF or PE. -type ExecutableInfo struct { - Format string `json:"format"` - Arch string `json:"arch"` - Entry int64 `json:"entry"` - Regions []ExecutableRegion `json:"regions"` - Seeds []FunctionSeed `json:"seeds"` -} - -// InspectExecutable extracts executable ranges, entry point and named function -// seeds without scraping the display-oriented parser tree. -func InspectExecutable(d *document.Document) (*ExecutableInfo, error) { - head, err := d.Range(0, min64(64, d.Size())) - if err != nil { - return nil, err - } - if len(head) >= 4 && string(head[:4]) == "\x7fELF" { - return inspectELFExecutable(d, head) - } - if len(head) >= 2 && string(head[:2]) == "MZ" { - return inspectPEExecutable(d) - } - return nil, errors.New("not a supported ELF or PE executable") -} - -func inspectELFExecutable(d *document.Document, hdr []byte) (*ExecutableInfo, error) { - if len(hdr) < 52 || (hdr[4] != 1 && hdr[4] != 2) || (hdr[5] != 1 && hdr[5] != 2) { - return nil, errors.New("invalid ELF header") - } - class, data := hdr[4], hdr[5] - bo := elfByteOrder(data) - var machine uint16 - var entry, phoff, shoff uint64 - var phentsize, phnum, shentsize, shnum uint16 - if class == 2 { - if len(hdr) < 64 { - return nil, errors.New("ELF64 header too short") - } - machine = bo.Uint16(hdr[18:20]) - entry = bo.Uint64(hdr[24:32]) - phoff = bo.Uint64(hdr[32:40]) - shoff = bo.Uint64(hdr[40:48]) - phentsize, phnum = bo.Uint16(hdr[54:56]), bo.Uint16(hdr[56:58]) - shentsize, shnum = bo.Uint16(hdr[58:60]), bo.Uint16(hdr[60:62]) - } else { - machine = bo.Uint16(hdr[18:20]) - entry = uint64(bo.Uint32(hdr[24:28])) - phoff = uint64(bo.Uint32(hdr[28:32])) - shoff = uint64(bo.Uint32(hdr[32:36])) - phentsize, phnum = bo.Uint16(hdr[42:44]), bo.Uint16(hdr[44:46]) - shentsize, shnum = bo.Uint16(hdr[46:48]), bo.Uint16(hdr[48:50]) - } - sections, _ := executableELFSections(d, bo, class, shoff, shentsize, shnum) - info := &ExecutableInfo{Format: "elf", Arch: elfDisasmArch(machine, data), Entry: -1} - if info.Arch == "" { - return nil, fmt.Errorf("unsupported ELF machine 0x%x", machine) - } - for i, section := range sections { - if section.flags&0x4 != 0 && validFileRange(d, section.fileOff, section.secSize) { - info.Regions = append(info.Regions, ExecutableRegion{ - Name: fmt.Sprintf("section[%d]", i), Offset: int64(section.fileOff), - Size: int64(section.secSize), Address: section.addr, - }) - } - } - if len(info.Regions) == 0 { - info.Regions = executableELFSegments(d, bo, class, phoff, phentsize, phnum) - } - if off, ok := elfAddressToOffset(sections, entry); ok { - info.Entry = off - info.Seeds = append(info.Seeds, FunctionSeed{Name: "entry", Offset: off, Address: entry, Source: "header"}) - } else if off, ok := regionAddressToOffset(info.Regions, entry); ok { - info.Entry = off - info.Seeds = append(info.Seeds, FunctionSeed{Name: "entry", Offset: off, Address: entry, Source: "header"}) - } - for _, symtab := range sections { - if symtab.typ == 2 || symtab.typ == 11 { - info.Seeds = append(info.Seeds, executableELFFunctions(d, bo, class, sections, symtab)...) - } - } - finishExecutableInfo(info) - return info, nil -} - -func executableELFSegments(d *document.Document, bo binary.ByteOrder, class byte, off uint64, entrySize, count uint16) []ExecutableRegion { - if off == 0 || entrySize == 0 || count == 0 || count > 8192 { - return nil - } - regions := make([]ExecutableRegion, 0) - for i := uint16(0); i < count; i++ { - buf, err := d.Range(int64(off)+int64(i)*int64(entrySize), int64(entrySize)) - if err != nil { - break - } - var typ, flags uint32 - var fileOff, address, fileSize uint64 - if class == 2 { - if len(buf) < 56 { - break - } - typ, flags = bo.Uint32(buf[0:4]), bo.Uint32(buf[4:8]) - fileOff, address, fileSize = bo.Uint64(buf[8:16]), bo.Uint64(buf[16:24]), bo.Uint64(buf[32:40]) - } else { - if len(buf) < 32 { - break - } - typ, flags = bo.Uint32(buf[0:4]), bo.Uint32(buf[24:28]) - fileOff, address, fileSize = uint64(bo.Uint32(buf[4:8])), uint64(bo.Uint32(buf[8:12])), uint64(bo.Uint32(buf[16:20])) - } - if typ == 1 && flags&0x1 != 0 && validFileRange(d, fileOff, fileSize) { - regions = append(regions, ExecutableRegion{ - Name: fmt.Sprintf("PT_LOAD[%d]", i), Offset: int64(fileOff), Size: int64(fileSize), Address: address, - }) - } - } - return regions -} - -func executableELFSections(d *document.Document, bo binary.ByteOrder, class byte, off uint64, entrySize, count uint16) ([]elfSectionInfo, error) { - if off == 0 || entrySize == 0 || count == 0 || count > 8192 { - return nil, errors.New("ELF has no usable section table") - } - sections := make([]elfSectionInfo, 0, count) - for i := uint16(0); i < count; i++ { - buf, err := d.Range(int64(off)+int64(i)*int64(entrySize), int64(entrySize)) - if err != nil { - return nil, err - } - var section elfSectionInfo - if class == 2 { - if len(buf) < 64 { - return nil, errors.New("truncated ELF64 section") - } - section.typ, section.flags = bo.Uint32(buf[4:8]), bo.Uint64(buf[8:16]) - section.addr, section.fileOff, section.secSize = bo.Uint64(buf[16:24]), bo.Uint64(buf[24:32]), bo.Uint64(buf[32:40]) - section.link, section.entrySize = bo.Uint32(buf[40:44]), bo.Uint64(buf[56:64]) - } else { - if len(buf) < 40 { - return nil, errors.New("truncated ELF32 section") - } - section.typ, section.flags = bo.Uint32(buf[4:8]), uint64(bo.Uint32(buf[8:12])) - section.addr = uint64(bo.Uint32(buf[12:16])) - section.fileOff, section.secSize = uint64(bo.Uint32(buf[16:20])), uint64(bo.Uint32(buf[20:24])) - section.link, section.entrySize = bo.Uint32(buf[24:28]), uint64(bo.Uint32(buf[36:40])) - } - sections = append(sections, section) - } - return sections, nil -} - -func executableELFFunctions(d *document.Document, bo binary.ByteOrder, class byte, sections []elfSectionInfo, symtab elfSectionInfo) []FunctionSeed { - entrySize := symtab.entrySize - if entrySize == 0 { - entrySize = 16 - if class == 2 { - entrySize = 24 - } - } - if !validFileRange(d, symtab.fileOff, symtab.secSize) || int(symtab.link) >= len(sections) { - return nil - } - strsec := sections[symtab.link] - if !validFileRange(d, strsec.fileOff, strsec.secSize) || strsec.secSize > 8<<20 { - return nil - } - strtab, err := d.Range(int64(strsec.fileOff), int64(strsec.secSize)) - if err != nil { - return nil - } - count := int(symtab.secSize / entrySize) - if count > 16384 { - count = 16384 - } - out := make([]FunctionSeed, 0) - for i := 0; i < count; i++ { - buf, err := d.Range(int64(symtab.fileOff)+int64(i)*int64(entrySize), int64(entrySize)) - if err != nil || len(buf) < int(entrySize) { - break - } - var nameOff uint32 - var info byte - var sectionIndex uint16 - var value, size uint64 - if class == 2 { - nameOff, info, sectionIndex = bo.Uint32(buf[0:4]), buf[4], bo.Uint16(buf[6:8]) - value, size = bo.Uint64(buf[8:16]), bo.Uint64(buf[16:24]) - } else { - nameOff, value, size = bo.Uint32(buf[0:4]), uint64(bo.Uint32(buf[4:8])), uint64(bo.Uint32(buf[8:12])) - info, sectionIndex = buf[12], bo.Uint16(buf[14:16]) - } - if info&0xf != 2 || sectionIndex == 0 || value == 0 { - continue - } - name := elfStringAt(strtab, nameOff) - offset, ok := elfAddressToOffset(sections, value) - if name == "" || !ok { - continue - } - source := "symtab" - if symtab.typ == 11 { - source = "dynsym" - } - out = append(out, FunctionSeed{Name: name, Offset: offset, Address: value, Size: size, Source: source}) - } - return out -} - -func inspectPEExecutable(d *document.Document) (*ExecutableInfo, error) { - sections, dirs, is64, err := peLayout(d) - if err != nil { - return nil, err - } - hdr, _ := d.Range(0, 64) - lfanew := int64(binary.LittleEndian.Uint32(hdr[0x3c:0x40])) - peHdr, _ := d.Range(lfanew, 24) - machine := binary.LittleEndian.Uint16(peHdr[4:6]) - optSize := int64(binary.LittleEndian.Uint16(peHdr[20:22])) - opt, err := d.Range(lfanew+24, optSize) - if err != nil || len(opt) < 20 { - return nil, errors.New("PE optional header too short") - } - entryRVA := binary.LittleEndian.Uint32(opt[16:20]) - info := &ExecutableInfo{Format: "pe", Arch: peDisasmArch(machine, is64), Entry: -1} - if info.Arch == "" { - return nil, fmt.Errorf("unsupported PE machine 0x%x", machine) - } - for _, section := range sections { - if section.Characteristics&0x20000000 != 0 && validFileRange(d, uint64(section.RawOffset), uint64(section.RawSize)) { - info.Regions = append(info.Regions, ExecutableRegion{ - Name: section.Name, Offset: int64(section.RawOffset), Size: int64(section.RawSize), Address: uint64(section.VirtualAddress), - }) - } - } - if off, ok := peRVAToOffset(sections, entryRVA); ok { - info.Entry = int64(off) - info.Seeds = append(info.Seeds, FunctionSeed{Name: "entry", Offset: int64(off), Address: uint64(entryRVA), Source: "header"}) - } - if len(dirs) > 0 && dirs[0].RVA != 0 { - info.Seeds = append(info.Seeds, executablePEExports(d, sections, dirs[0])...) - } - finishExecutableInfo(info) - return info, nil -} - -func executablePEExports(d *document.Document, sections []peSectionInfo, dir peDataDirectory) []FunctionSeed { - off, ok := peRVAToOffset(sections, dir.RVA) - if !ok { - return nil - } - buf, err := d.Range(int64(off), 40) - if err != nil || len(buf) < 40 { - return nil - } - functionCount, nameCount := binary.LittleEndian.Uint32(buf[20:24]), binary.LittleEndian.Uint32(buf[24:28]) - functionsOff, funcsOK := peRVAToOffset(sections, binary.LittleEndian.Uint32(buf[28:32])) - namesOff, namesOK := peRVAToOffset(sections, binary.LittleEndian.Uint32(buf[32:36])) - ordinalsOff, ordOK := peRVAToOffset(sections, binary.LittleEndian.Uint32(buf[36:40])) - if !funcsOK || !namesOK || !ordOK { - return nil - } - if nameCount > 16384 { - nameCount = 16384 - } - out := make([]FunctionSeed, 0, nameCount) - for i := uint32(0); i < nameCount; i++ { - namePtr, nameErr := d.Range(int64(namesOff)+int64(i)*4, 4) - ordBuf, ordErr := d.Range(int64(ordinalsOff)+int64(i)*2, 2) - if nameErr != nil || ordErr != nil || len(namePtr) < 4 || len(ordBuf) < 2 { - break - } - index := uint32(binary.LittleEndian.Uint16(ordBuf)) - if index >= functionCount { - continue - } - funcBuf, err := d.Range(int64(functionsOff)+int64(index)*4, 4) - if err != nil || len(funcBuf) < 4 { - break - } - funcRVA := binary.LittleEndian.Uint32(funcBuf) - if funcRVA >= dir.RVA && funcRVA < dir.RVA+dir.Size { - continue - } - nameOff, nameOK := peRVAToOffset(sections, binary.LittleEndian.Uint32(namePtr)) - funcOff, funcOK := peRVAToOffset(sections, funcRVA) - if !nameOK || !funcOK { - continue - } - name := readCString(d, int64(nameOff), 512) - if name != "" { - out = append(out, FunctionSeed{Name: name, Offset: int64(funcOff), Address: uint64(funcRVA), Source: "export"}) - } - } - return out -} - -func validFileRange(d *document.Document, offset, size uint64) bool { - return size > 0 && offset <= uint64(d.Size()) && size <= uint64(d.Size())-offset -} - -func elfAddressToOffset(sections []elfSectionInfo, address uint64) (int64, bool) { - for _, section := range sections { - if section.secSize > 0 && address >= section.addr && address < section.addr+section.secSize { - return int64(section.fileOff + address - section.addr), true - } - } - return 0, false -} - -func regionAddressToOffset(regions []ExecutableRegion, address uint64) (int64, bool) { - for _, region := range regions { - if address >= region.Address && address < region.Address+uint64(region.Size) { - return region.Offset + int64(address-region.Address), true - } - } - return 0, false -} - -func elfDisasmArch(machine uint16, data byte) string { - switch machine { - case 3: - return "x86" - case 8: - if data == 1 { - return "mipsle" - } - return "mips" - case 40: - return "arm32" - case 62: - return "x86_64" - case 183: - return "aarch64" - } - return "" -} - -func peDisasmArch(machine uint16, _ bool) string { - switch machine { - case 0x14c: - return "x86" - case 0x8664: - return "x86_64" - case 0x1c0, 0x1c4: - return "arm32" - case 0xaa64: - return "aarch64" - case 0x166, 0x266, 0x366, 0x466: - return "mipsle" - } - return "" -} - -func finishExecutableInfo(info *ExecutableInfo) { - sort.Slice(info.Regions, func(i, j int) bool { return info.Regions[i].Offset < info.Regions[j].Offset }) - sort.Slice(info.Seeds, func(i, j int) bool { - if info.Seeds[i].Offset != info.Seeds[j].Offset { - return info.Seeds[i].Offset < info.Seeds[j].Offset - } - return info.Seeds[i].Name < info.Seeds[j].Name - }) - unique := info.Seeds[:0] - for _, seed := range info.Seeds { - if seed.Offset < 0 || !offsetInRegions(info.Regions, seed.Offset) { - continue - } - if len(unique) > 0 && unique[len(unique)-1].Offset == seed.Offset { - if unique[len(unique)-1].Name == "entry" && seed.Name != "entry" { - unique[len(unique)-1] = seed - } - continue - } - unique = append(unique, seed) - } - info.Seeds = unique -} - -func offsetInRegions(regions []ExecutableRegion, offset int64) bool { - for _, region := range regions { - if offset >= region.Offset && offset < region.Offset+region.Size { - return true - } - } - return false -} diff --git a/internal/leviathan/parsers/formats.go b/internal/leviathan/parsers/formats.go deleted file mode 100644 index 7154f06..0000000 --- a/internal/leviathan/parsers/formats.go +++ /dev/null @@ -1,2182 +0,0 @@ -// Package parsers provides lightweight structure parsers for common -// binary formats used in Leviathan's structures / templates panel. -// Each parser returns a tree of Field values anchored at absolute -// offsets into the document. -package parsers - -import ( - "encoding/binary" - "errors" - "fmt" - "strings" - "unicode/utf16" - "unicode/utf8" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// Field is one node of the parsed structure. -type Field struct { - Name string - Type string - Offset int64 - Length int64 - Value string - Note string - Children []*Field -} - -// ToMap serialises a Field tree to JS-friendly maps. -func (f *Field) ToMap() map[string]any { - m := map[string]any{ - "name": f.Name, - "type": f.Type, - "offset": f.Offset, - "length": f.Length, - "value": f.Value, - } - if f.Note != "" { - m["note"] = f.Note - } - if len(f.Children) > 0 { - kids := make([]map[string]any, len(f.Children)) - for i, c := range f.Children { - kids[i] = c.ToMap() - } - m["children"] = kids - } - return m -} - -// DetectAndParse picks the first matching parser for the document. -// It returns the parser name used and the tree. -func DetectAndParse(d *document.Document) (string, *Field, error) { - // Read enough to cover the tar ustar magic at offset 257. - head, err := d.Range(0, min64(265, d.Size())) - if err != nil { - return "", nil, err - } - switch { - case len(head) >= 8 && string(head[:8]) == "\x89PNG\r\n\x1a\n": - f, err := ParsePNG(d) - return "png", f, err - case len(head) >= 3 && head[0] == 0xff && head[1] == 0xd8 && head[2] == 0xff: - f, err := ParseJPEG(d) - return "jpeg", f, err - case len(head) >= 6 && (string(head[:6]) == "GIF87a" || string(head[:6]) == "GIF89a"): - f, err := ParseGIF(d) - return "gif", f, err - case len(head) >= 2 && string(head[:2]) == "BM": - f, err := ParseBMP(d) - return "bmp", f, err - case len(head) >= 4 && string(head[:4]) == "PK\x03\x04": - f, err := ParseZIP(d) - return "zip", f, err - case len(head) >= 4 && string(head[:4]) == "\x7fELF": - f, err := ParseELF(d) - return "elf", f, err - case len(head) >= 2 && string(head[:2]) == "MZ": - f, err := ParsePE(d) - return "pe", f, err - case len(head) >= 4 && string(head[:4]) == "RIFF": - f, err := ParseRIFF(d) - return "riff", f, err - case len(head) >= 16 && string(head[:16]) == "SQLite format 3\x00": - f, err := ParseSQLite(d) - return "sqlite", f, err - case len(head) >= 8 && string(head[:8]) == string(cfbMagic): - f, err := ParseCFB(d) - return "cfb", f, err - case len(head) >= 3 && head[0] == 0x1f && head[1] == 0x8b && head[2] == 8: - f, err := ParseGZIP(d) - return "gzip", f, err - case len(head) >= 4 && string(head[:4]) == "\x0a\x0d\x0d\x0a": - f, err := ParsePCAPNG(d) - return "pcapng", f, err - case len(head) >= 4 && isPCAPMagic(head): - f, err := ParsePCAP(d) - return "pcap", f, err - case len(head) >= 4 && (string(head[:4]) == "II\x2a\x00" || string(head[:4]) == "MM\x00\x2a"): - f, err := ParseTIFF(d) - return "tiff", f, err - case len(head) >= 265 && (string(head[257:262]) == "ustar"): - f, err := ParseTAR(d) - return "tar", f, err - } - // Mach-O (including fat/universal) — detection needs more than 8 bytes. - if IsMachO(head, d.Size()) { - f, err := ParseMachO(d) - return "macho", f, err - } - return "", nil, errors.New("no parser matched") -} - -func isPCAPMagic(head []byte) bool { - m := binary.LittleEndian.Uint32(head[:4]) - return m == 0xa1b2c3d4 || m == 0xd4c3b2a1 || m == 0xa1b23c4d || m == 0x4d3cb2a1 -} - -// ParsePNG walks chunks sequentially and returns a Field tree. -func ParsePNG(d *document.Document) (*Field, error) { - root := &Field{Name: "PNG", Type: "container", Offset: 0} - hdr, err := d.Range(0, 8) - if err != nil { - return nil, err - } - root.Children = append(root.Children, &Field{ - Name: "signature", Type: "bytes", Offset: 0, Length: 8, - Value: fmt.Sprintf("% x", hdr), - }) - off := int64(8) - size := d.Size() - for off+8 <= size { - hdr, err := d.Range(off, 8) - if err != nil { - return root, err - } - ln := int64(binary.BigEndian.Uint32(hdr[0:4])) - typ := string(hdr[4:8]) - chunk := &Field{ - Name: typ + "@" + fmt.Sprintf("0x%x", off), - Type: "chunk", - Offset: off, - Length: 12 + ln, - Value: fmt.Sprintf("length=%d", ln), - } - chunk.Children = []*Field{ - {Name: "length", Type: "u32be", Offset: off, Length: 4, Value: fmt.Sprintf("%d", ln)}, - {Name: "type", Type: "ascii", Offset: off + 4, Length: 4, Value: typ}, - {Name: "data", Type: "bytes", Offset: off + 8, Length: ln, Value: fmt.Sprintf("%d bytes", ln)}, - {Name: "crc", Type: "u32be", Offset: off + 8 + ln, Length: 4, Value: "crc32"}, - } - root.Children = append(root.Children, chunk) - off += 12 + ln - if typ == "IEND" { - break - } - } - root.Length = off - return root, nil -} - -// ParseJPEG walks marker segments. -func ParseJPEG(d *document.Document) (*Field, error) { - root := &Field{Name: "JPEG", Type: "container", Offset: 0} - off := int64(0) - size := d.Size() - for off < size { - b, err := d.Range(off, 2) - if err != nil || len(b) < 2 { - break - } - if b[0] != 0xff { - return root, errors.New("lost sync") - } - marker := b[1] - mark := &Field{ - Name: jpegMarkerName(marker), - Type: "marker", - Offset: off, - Value: fmt.Sprintf("0x%02x", marker), - } - off += 2 - if marker == 0xd8 || marker == 0xd9 || marker == 0x00 || (marker >= 0xd0 && marker <= 0xd7) { - mark.Length = 2 - root.Children = append(root.Children, mark) - if marker == 0xd9 { - break - } - continue - } - lb, err := d.Range(off, 2) - if err != nil || len(lb) < 2 { - break - } - ln := int64(binary.BigEndian.Uint16(lb)) - mark.Length = 2 + ln - mark.Children = []*Field{ - {Name: "length", Type: "u16be", Offset: off, Length: 2, Value: fmt.Sprintf("%d", ln)}, - {Name: "data", Type: "bytes", Offset: off + 2, Length: ln - 2, Value: fmt.Sprintf("%d bytes", ln-2)}, - } - root.Children = append(root.Children, mark) - off += ln - if marker == 0xda { // SOS - // scan until next marker - for off < size { - bb, err := d.Range(off, 2) - if err != nil || len(bb) < 2 { - break - } - if bb[0] == 0xff && bb[1] != 0x00 && !(bb[1] >= 0xd0 && bb[1] <= 0xd7) { - break - } - off++ - } - } - } - root.Length = off - return root, nil -} - -func jpegMarkerName(m byte) string { - switch m { - case 0xd8: - return "SOI" - case 0xd9: - return "EOI" - case 0xda: - return "SOS" - case 0xdb: - return "DQT" - case 0xc4: - return "DHT" - case 0xe0: - return "APP0" - case 0xe1: - return "APP1" - case 0xfe: - return "COM" - } - if m >= 0xc0 && m <= 0xcf { - return fmt.Sprintf("SOF%d", int(m-0xc0)) - } - if m >= 0xe0 && m <= 0xef { - return fmt.Sprintf("APP%d", int(m-0xe0)) - } - return fmt.Sprintf("MRK%02x", m) -} - -// ParseGIF walks the logical screen descriptor, global colour table, -// extension blocks, image descriptors and trailer. -func ParseGIF(d *document.Document) (*Field, error) { - size := d.Size() - if size < 13 { - return nil, errors.New("gif too short") - } - hdr, err := d.Range(0, 13) - if err != nil { - return nil, err - } - version := string(hdr[0:6]) - if version != "GIF87a" && version != "GIF89a" { - return nil, errors.New("bad gif signature") - } - width := binary.LittleEndian.Uint16(hdr[6:8]) - height := binary.LittleEndian.Uint16(hdr[8:10]) - packed := hdr[10] - gctFlag := packed&0x80 != 0 - colorRes := ((packed >> 4) & 0x07) + 1 - sortFlag := packed&0x08 != 0 - gctEntries := 0 - if gctFlag { - gctEntries = 1 << ((packed & 0x07) + 1) - } - root := &Field{ - Name: "GIF", - Type: "container", - Offset: 0, - Value: fmt.Sprintf("%s %dx%d", version, width, height), - Children: []*Field{ - {Name: "signature", Type: "ascii", Offset: 0, Length: 6, Value: version}, - {Name: "width", Type: "u16le", Offset: 6, Length: 2, Value: fmt.Sprintf("%d", width)}, - {Name: "height", Type: "u16le", Offset: 8, Length: 2, Value: fmt.Sprintf("%d", height)}, - {Name: "packed", Type: "u8", Offset: 10, Length: 1, Value: fmt.Sprintf("gct=%v colorRes=%d sort=%v entries=%d", gctFlag, colorRes, sortFlag, gctEntries)}, - {Name: "backgroundColorIndex", Type: "u8", Offset: 11, Length: 1, Value: fmt.Sprintf("%d", hdr[11])}, - {Name: "pixelAspectRatio", Type: "u8", Offset: 12, Length: 1, Value: fmt.Sprintf("%d", hdr[12])}, - }, - } - off := int64(13) - if gctEntries > 0 { - ln := int64(gctEntries * 3) - if off+ln > size { - return root, errors.New("gif global colour table exceeds file") - } - root.Children = append(root.Children, &Field{Name: "globalColorTable", Type: "palette", Offset: off, Length: ln, Value: fmt.Sprintf("%d RGB entries", gctEntries)}) - off += ln - } - for off < size { - tag, err := d.Range(off, 1) - if err != nil || len(tag) < 1 { - break - } - switch tag[0] { - case 0x3b: - root.Children = append(root.Children, &Field{Name: "trailer", Type: "byte", Offset: off, Length: 1, Value: "0x3b"}) - off++ - root.Length = off - return root, nil - case 0x21: - block, next, err := parseGIFExtension(d, off, size) - root.Children = append(root.Children, block) - if err != nil { - root.Length = next - return root, err - } - off = next - case 0x2c: - block, next, err := parseGIFImage(d, off, size) - root.Children = append(root.Children, block) - if err != nil { - root.Length = next - return root, err - } - off = next - default: - root.Children = append(root.Children, &Field{Name: "unknown", Type: "byte", Offset: off, Length: 1, Value: fmt.Sprintf("0x%02x", tag[0])}) - root.Length = off + 1 - return root, fmt.Errorf("unknown gif block 0x%02x at 0x%x", tag[0], off) - } - } - root.Length = off - return root, nil -} - -func parseGIFExtension(d *document.Document, off, size int64) (*Field, int64, error) { - hdr, err := d.Range(off, 2) - if err != nil || len(hdr) < 2 { - return &Field{Name: "extension", Type: "block", Offset: off}, off, errors.New("short gif extension") - } - label := hdr[1] - name := gifExtensionName(label) - next, subBlocks, err := skipGIFSubBlocks(d, off+2, size) - f := &Field{ - Name: name, - Type: "extension", - Offset: off, - Length: next - off, - Value: fmt.Sprintf("label=0x%02x subBlocks=%d", label, subBlocks), - Children: []*Field{ - {Name: "introducer", Type: "u8", Offset: off, Length: 1, Value: "0x21"}, - {Name: "label", Type: "u8", Offset: off + 1, Length: 1, Value: fmt.Sprintf("0x%02x", label)}, - }, - } - return f, next, err -} - -func parseGIFImage(d *document.Document, off, size int64) (*Field, int64, error) { - hdr, err := d.Range(off, 10) - if err != nil || len(hdr) < 10 { - return &Field{Name: "image", Type: "image", Offset: off}, off, errors.New("short gif image descriptor") - } - left := binary.LittleEndian.Uint16(hdr[1:3]) - top := binary.LittleEndian.Uint16(hdr[3:5]) - width := binary.LittleEndian.Uint16(hdr[5:7]) - height := binary.LittleEndian.Uint16(hdr[7:9]) - packed := hdr[9] - lctEntries := 0 - if packed&0x80 != 0 { - lctEntries = 1 << ((packed & 0x07) + 1) - } - next := off + 10 + int64(lctEntries*3) - if next >= size { - return nil, next, errors.New("gif image data missing") - } - lzw, err := d.Range(next, 1) - if err != nil || len(lzw) < 1 { - return nil, next, errors.New("gif lzw minimum code size missing") - } - dataStart := next + 1 - next, subBlocks, err := skipGIFSubBlocks(d, dataStart, size) - f := &Field{ - Name: fmt.Sprintf("image@0x%x", off), - Type: "image", - Offset: off, - Length: next - off, - Value: fmt.Sprintf("%dx%d at %d,%d subBlocks=%d", width, height, left, top, subBlocks), - Children: []*Field{ - {Name: "left", Type: "u16le", Offset: off + 1, Length: 2, Value: fmt.Sprintf("%d", left)}, - {Name: "top", Type: "u16le", Offset: off + 3, Length: 2, Value: fmt.Sprintf("%d", top)}, - {Name: "width", Type: "u16le", Offset: off + 5, Length: 2, Value: fmt.Sprintf("%d", width)}, - {Name: "height", Type: "u16le", Offset: off + 7, Length: 2, Value: fmt.Sprintf("%d", height)}, - {Name: "packed", Type: "u8", Offset: off + 9, Length: 1, Value: fmt.Sprintf("lctEntries=%d interlace=%v", lctEntries, packed&0x40 != 0)}, - {Name: "lzwMinimumCodeSize", Type: "u8", Offset: dataStart - 1, Length: 1, Value: fmt.Sprintf("%d", lzw[0])}, - {Name: "imageData", Type: "subblocks", Offset: dataStart, Length: next - dataStart, Value: fmt.Sprintf("%d sub-blocks", subBlocks)}, - }, - } - if lctEntries > 0 { - f.Children = append(f.Children, &Field{Name: "localColorTable", Type: "palette", Offset: off + 10, Length: int64(lctEntries * 3), Value: fmt.Sprintf("%d RGB entries", lctEntries)}) - } - return f, next, err -} - -func skipGIFSubBlocks(d *document.Document, off, size int64) (int64, int, error) { - count := 0 - for off < size { - b, err := d.Range(off, 1) - if err != nil || len(b) < 1 { - return off, count, errors.New("short gif sub-block") - } - off++ - ln := int64(b[0]) - if ln == 0 { - return off, count, nil - } - if off+ln > size { - return off, count, errors.New("gif sub-block exceeds file") - } - off += ln - count++ - } - return off, count, errors.New("unterminated gif sub-block chain") -} - -func gifExtensionName(label byte) string { - switch label { - case 0xf9: - return "graphicControlExtension" - case 0xfe: - return "commentExtension" - case 0xff: - return "applicationExtension" - case 0x01: - return "plainTextExtension" - } - return fmt.Sprintf("extension%02x", label) -} - -// ParseBMP reads the BMP file header and DIB header metadata. -func ParseBMP(d *document.Document) (*Field, error) { - size := d.Size() - if size < 26 { - return nil, errors.New("bmp too short") - } - hdr, err := d.Range(0, min64(size, 138)) - if err != nil { - return nil, err - } - if len(hdr) < 26 || string(hdr[0:2]) != "BM" { - return nil, errors.New("bad bmp signature") - } - fileSize := binary.LittleEndian.Uint32(hdr[2:6]) - pixelOff := binary.LittleEndian.Uint32(hdr[10:14]) - dibSize := binary.LittleEndian.Uint32(hdr[14:18]) - root := &Field{ - Name: "BMP", - Type: "container", - Offset: 0, - Length: int64(fileSize), - Value: fmt.Sprintf("fileSize=%d pixelOffset=0x%x dib=%s", fileSize, pixelOff, bmpDIBName(dibSize)), - Children: []*Field{ - {Name: "signature", Type: "ascii", Offset: 0, Length: 2, Value: "BM"}, - {Name: "fileSize", Type: "u32le", Offset: 2, Length: 4, Value: fmt.Sprintf("%d", fileSize)}, - {Name: "pixelDataOffset", Type: "u32le", Offset: 10, Length: 4, Value: fmt.Sprintf("0x%x", pixelOff)}, - {Name: "dibHeaderSize", Type: "u32le", Offset: 14, Length: 4, Value: fmt.Sprintf("%d (%s)", dibSize, bmpDIBName(dibSize))}, - }, - } - if dibSize == 12 { - if len(hdr) < 26 { - return root, nil - } - width := binary.LittleEndian.Uint16(hdr[18:20]) - height := binary.LittleEndian.Uint16(hdr[20:22]) - planes := binary.LittleEndian.Uint16(hdr[22:24]) - bpp := binary.LittleEndian.Uint16(hdr[24:26]) - root.Children = append(root.Children, - &Field{Name: "width", Type: "u16le", Offset: 18, Length: 2, Value: fmt.Sprintf("%d", width)}, - &Field{Name: "height", Type: "u16le", Offset: 20, Length: 2, Value: fmt.Sprintf("%d", height)}, - &Field{Name: "planes", Type: "u16le", Offset: 22, Length: 2, Value: fmt.Sprintf("%d", planes)}, - &Field{Name: "bitsPerPixel", Type: "u16le", Offset: 24, Length: 2, Value: fmt.Sprintf("%d", bpp)}, - ) - } else if dibSize >= 40 && len(hdr) >= 54 { - width := int32(binary.LittleEndian.Uint32(hdr[18:22])) - height := int32(binary.LittleEndian.Uint32(hdr[22:26])) - planes := binary.LittleEndian.Uint16(hdr[26:28]) - bpp := binary.LittleEndian.Uint16(hdr[28:30]) - compression := binary.LittleEndian.Uint32(hdr[30:34]) - imageSize := binary.LittleEndian.Uint32(hdr[34:38]) - xppm := int32(binary.LittleEndian.Uint32(hdr[38:42])) - yppm := int32(binary.LittleEndian.Uint32(hdr[42:46])) - coloursUsed := binary.LittleEndian.Uint32(hdr[46:50]) - root.Children = append(root.Children, - &Field{Name: "width", Type: "i32le", Offset: 18, Length: 4, Value: fmt.Sprintf("%d", width)}, - &Field{Name: "height", Type: "i32le", Offset: 22, Length: 4, Value: fmt.Sprintf("%d", height)}, - &Field{Name: "planes", Type: "u16le", Offset: 26, Length: 2, Value: fmt.Sprintf("%d", planes)}, - &Field{Name: "bitsPerPixel", Type: "u16le", Offset: 28, Length: 2, Value: fmt.Sprintf("%d", bpp)}, - &Field{Name: "compression", Type: "u32le", Offset: 30, Length: 4, Value: fmt.Sprintf("%d (%s)", compression, bmpCompressionName(compression))}, - &Field{Name: "imageSize", Type: "u32le", Offset: 34, Length: 4, Value: fmt.Sprintf("%d", imageSize)}, - &Field{Name: "xPixelsPerMeter", Type: "i32le", Offset: 38, Length: 4, Value: fmt.Sprintf("%d", xppm)}, - &Field{Name: "yPixelsPerMeter", Type: "i32le", Offset: 42, Length: 4, Value: fmt.Sprintf("%d", yppm)}, - &Field{Name: "coloursUsed", Type: "u32le", Offset: 46, Length: 4, Value: fmt.Sprintf("%d", coloursUsed)}, - ) - } - if int64(pixelOff) < size { - root.Children = append(root.Children, &Field{Name: "pixelData", Type: "bytes", Offset: int64(pixelOff), Length: size - int64(pixelOff), Value: fmt.Sprintf("%d bytes", size-int64(pixelOff))}) - } - return root, nil -} - -func bmpDIBName(size uint32) string { - switch size { - case 12: - return "BITMAPCOREHEADER" - case 40: - return "BITMAPINFOHEADER" - case 52: - return "BITMAPV2INFOHEADER" - case 56: - return "BITMAPV3INFOHEADER" - case 108: - return "BITMAPV4HEADER" - case 124: - return "BITMAPV5HEADER" - } - return "unknown" -} - -func bmpCompressionName(v uint32) string { - switch v { - case 0: - return "BI_RGB" - case 1: - return "BI_RLE8" - case 2: - return "BI_RLE4" - case 3: - return "BI_BITFIELDS" - case 4: - return "BI_JPEG" - case 5: - return "BI_PNG" - } - return "unknown" -} - -// ParseZIP walks local file headers by seeking forward. -func ParseZIP(d *document.Document) (*Field, error) { - root := &Field{Name: "ZIP", Type: "container", Offset: 0, Length: d.Size()} - size := d.Size() - - // 1) Local file headers, walked from the front. - off := int64(0) - entries := 0 - for off+30 <= size && entries < 5000 { - sig, err := d.Range(off, 4) - if err != nil || string(sig) != "PK\x03\x04" { - break // reached the central directory / EOCD region - } - hdr, err := d.Range(off, 30) - if err != nil { - break - } - flags := binary.LittleEndian.Uint16(hdr[6:8]) - method := binary.LittleEndian.Uint16(hdr[8:10]) - dosTime := binary.LittleEndian.Uint16(hdr[10:12]) - dosDate := binary.LittleEndian.Uint16(hdr[12:14]) - crc := binary.LittleEndian.Uint32(hdr[14:18]) - compSize := int64(binary.LittleEndian.Uint32(hdr[18:22])) - uncompSize := int64(binary.LittleEndian.Uint32(hdr[22:26])) - nameLen := int64(binary.LittleEndian.Uint16(hdr[26:28])) - extraLen := int64(binary.LittleEndian.Uint16(hdr[28:30])) - nameB, _ := d.Range(off+30, nameLen) - e := &Field{ - Name: string(nameB), - Type: "entry", - Offset: off, - Length: 30 + nameLen + extraLen + compSize, - Value: fmt.Sprintf("%s comp=%d uncomp=%d", zipMethod(method), compSize, uncompSize), - Children: []*Field{ - {Name: "signature", Type: "bytes", Offset: off, Length: 4, Value: "PK\\x03\\x04"}, - {Name: "version_needed", Type: "u16le", Offset: off + 4, Length: 2, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint16(hdr[4:6]))}, - {Name: "flags", Type: "u16le", Offset: off + 6, Length: 2, Value: zipFlags(flags)}, - {Name: "method", Type: "u16le", Offset: off + 8, Length: 2, Value: zipMethod(method)}, - {Name: "mod_time", Type: "dostime", Offset: off + 10, Length: 4, Value: zipDOSDateTime(dosDate, dosTime)}, - {Name: "crc32", Type: "u32le", Offset: off + 14, Length: 4, Value: fmt.Sprintf("0x%08x", crc)}, - {Name: "compressed_size", Type: "u32le", Offset: off + 18, Length: 4, Value: fmt.Sprintf("%d", compSize)}, - {Name: "uncompressed_size", Type: "u32le", Offset: off + 22, Length: 4, Value: fmt.Sprintf("%d", uncompSize)}, - {Name: "name", Type: "ascii", Offset: off + 30, Length: nameLen, Value: string(nameB)}, - {Name: "data", Type: "bytes", Offset: off + 30 + nameLen + extraLen, Length: compSize, Value: fmt.Sprintf("%d bytes", compSize)}, - }, - } - root.Children = append(root.Children, e) - off += 30 + nameLen + extraLen + compSize - entries++ - } - - // 2) Central directory + end-of-central-directory, located from the back. - if cd := zipCentralDirectory(d); cd != nil { - root.Children = append(root.Children, cd...) - } - return root, nil -} - -// zipCentralDirectory finds the EOCD record, decodes it, and walks the central -// directory it points at. Returns the EOCD field plus a "central_directory" -// container, or nil if no EOCD is present. -func zipCentralDirectory(d *document.Document) []*Field { - size := d.Size() - // EOCD is at most 22 + 65535 bytes from the end; scan a bounded tail. - scan := int64(22 + 65535) - if scan > size { - scan = size - } - tail, err := d.Range(size-scan, scan) - if err != nil { - return nil - } - eocdRel := int64(-1) - for i := len(tail) - 22; i >= 0; i-- { - if tail[i] == 'P' && tail[i+1] == 'K' && tail[i+2] == 0x05 && tail[i+3] == 0x06 { - eocdRel = int64(i) - break - } - } - if eocdRel < 0 { - return nil - } - eocdOff := size - scan + eocdRel - e, _ := d.Range(eocdOff, 22) - if len(e) < 22 { - return nil - } - total := binary.LittleEndian.Uint16(e[10:12]) - cdSize := int64(binary.LittleEndian.Uint32(e[12:16])) - cdOff := int64(binary.LittleEndian.Uint32(e[16:20])) - commentLen := int64(binary.LittleEndian.Uint16(e[20:22])) - eocd := &Field{ - Name: "end_of_central_directory", Type: "eocd", Offset: eocdOff, Length: 22 + commentLen, - Value: fmt.Sprintf("%d entries, cd@0x%x size=%d", total, cdOff, cdSize), - Children: []*Field{ - {Name: "total_entries", Type: "u16le", Offset: eocdOff + 10, Length: 2, Value: fmt.Sprintf("%d", total)}, - {Name: "cd_size", Type: "u32le", Offset: eocdOff + 12, Length: 4, Value: fmt.Sprintf("%d", cdSize)}, - {Name: "cd_offset", Type: "u32le", Offset: eocdOff + 16, Length: 4, Value: fmt.Sprintf("0x%x", cdOff)}, - {Name: "comment_length", Type: "u16le", Offset: eocdOff + 20, Length: 2, Value: fmt.Sprintf("%d", commentLen)}, - }, - } - out := []*Field{} - - // ZIP64: a 0xffffffff offset or 0xffff count signals a ZIP64 EOCD locator. - if cdOff == 0xffffffff || total == 0xffff { - if eocdOff-20 >= 0 { - loc, _ := d.Range(eocdOff-20, 20) - if len(loc) == 20 && string(loc[0:4]) == "PK\x06\x07" { - z64Off := int64(binary.LittleEndian.Uint64(loc[8:16])) - eocd.Children = append(eocd.Children, &Field{Name: "zip64_locator", Type: "bytes", Offset: eocdOff - 20, Length: 20, Value: fmt.Sprintf("zip64 EOCD @0x%x", z64Off)}) - if z64, _ := d.Range(z64Off, 56); len(z64) == 56 && string(z64[0:4]) == "PK\x06\x06" { - cdOff = int64(binary.LittleEndian.Uint64(z64[48:56])) - cdSize = int64(binary.LittleEndian.Uint64(z64[40:48])) - out = append(out, &Field{Name: "zip64_eocd", Type: "zip64", Offset: z64Off, Length: 56, Value: fmt.Sprintf("entries=%d cd@0x%x", binary.LittleEndian.Uint64(z64[32:40]), cdOff)}) - } - } - } - } - - // Walk central-directory file headers. - if cdOff >= 0 && cdOff < size { - cd := &Field{Name: "central_directory", Type: "directory", Offset: cdOff, Length: cdSize} - off := cdOff - end := size - if cdSize > 0 && cdOff+cdSize <= size { - end = cdOff + cdSize - } - for n := 0; off+46 <= end && n < 5000; n++ { - h, err := d.Range(off, 46) - if err != nil || string(h[0:4]) != "PK\x01\x02" { - break - } - method := binary.LittleEndian.Uint16(h[10:12]) - crc := binary.LittleEndian.Uint32(h[16:20]) - compSize := int64(binary.LittleEndian.Uint32(h[20:24])) - nameLen := int64(binary.LittleEndian.Uint16(h[28:30])) - extraLen := int64(binary.LittleEndian.Uint16(h[30:32])) - commLen := int64(binary.LittleEndian.Uint16(h[32:34])) - lho := binary.LittleEndian.Uint32(h[42:46]) - nameB, _ := d.Range(off+46, nameLen) - cd.Children = append(cd.Children, &Field{ - Name: string(nameB), Type: "cd_entry", Offset: off, Length: 46 + nameLen + extraLen + commLen, - Value: fmt.Sprintf("%s crc=0x%08x comp=%d lfh@0x%x", zipMethod(method), crc, compSize, lho), - }) - off += 46 + nameLen + extraLen + commLen - } - out = append(out, cd) - } - out = append(out, eocd) - return out -} - -func zipMethod(m uint16) string { - names := map[uint16]string{0: "store", 1: "shrink", 6: "implode", 8: "deflate", 9: "deflate64", 12: "bzip2", 14: "lzma", 95: "xz", 96: "jpeg", 97: "wavpack", 98: "ppmd", 99: "aes"} - if n, ok := names[m]; ok { - return fmt.Sprintf("%d (%s)", m, n) - } - return fmt.Sprintf("%d", m) -} - -func zipFlags(f uint16) string { - var on []string - if f&0x0001 != 0 { - on = append(on, "encrypted") - } - if f&0x0008 != 0 { - on = append(on, "data-descriptor") - } - if f&0x0800 != 0 { - on = append(on, "utf8") - } - if len(on) == 0 { - return fmt.Sprintf("0x%04x", f) - } - return fmt.Sprintf("0x%04x (%s)", f, strings.Join(on, "|")) -} - -// zipDOSDateTime decodes the MS-DOS packed date/time used by ZIP entries. -func zipDOSDateTime(date, t uint16) string { - if date == 0 && t == 0 { - return "0" - } - year := 1980 + int(date>>9) - month := int(date>>5) & 0x0f - day := int(date) & 0x1f - hour := int(t >> 11) - minute := int(t>>5) & 0x3f - second := (int(t) & 0x1f) * 2 - return fmt.Sprintf("%04d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second) -} - -// ParseELF returns header, program header, and section header fields. -func ParseELF(d *document.Document) (*Field, error) { - hdr, err := d.Range(0, 64) - if err != nil || len(hdr) < 52 { - return nil, errors.New("elf too short") - } - class := hdr[4] - data := hdr[5] - kind := "32-bit" - if class == 2 { - kind = "64-bit" - } - endian := "little-endian" - if data == 2 { - endian = "big-endian" - } - root := &Field{ - Name: "ELF", - Type: "container", - Offset: 0, - Length: d.Size(), - Value: kind + " " + endian, - Children: []*Field{ - {Name: "ei_mag", Type: "bytes", Offset: 0, Length: 4, Value: "\\x7fELF"}, - {Name: "ei_class", Type: "u8", Offset: 4, Length: 1, Value: kind}, - {Name: "ei_data", Type: "u8", Offset: 5, Length: 1, Value: endian}, - {Name: "ei_version", Type: "u8", Offset: 6, Length: 1, Value: fmt.Sprintf("%d", hdr[6])}, - {Name: "ei_osabi", Type: "u8", Offset: 7, Length: 1, Value: fmt.Sprintf("%d", hdr[7])}, - }, - } - var e_type, e_machine uint16 - var e_entry uint64 - var e_phoff, e_shoff uint64 - var e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx uint16 - bo := elfByteOrder(data) - if class == 2 { - e_type = bo.Uint16(hdr[16:18]) - e_machine = bo.Uint16(hdr[18:20]) - e_entry = bo.Uint64(hdr[24:32]) - e_phoff = bo.Uint64(hdr[32:40]) - e_shoff = bo.Uint64(hdr[40:48]) - e_phentsize = bo.Uint16(hdr[54:56]) - e_phnum = bo.Uint16(hdr[56:58]) - e_shentsize = bo.Uint16(hdr[58:60]) - e_shnum = bo.Uint16(hdr[60:62]) - e_shstrndx = bo.Uint16(hdr[62:64]) - } else { - e_type = bo.Uint16(hdr[16:18]) - e_machine = bo.Uint16(hdr[18:20]) - e_entry = uint64(bo.Uint32(hdr[24:28])) - e_phoff = uint64(bo.Uint32(hdr[28:32])) - e_shoff = uint64(bo.Uint32(hdr[32:36])) - e_phentsize = bo.Uint16(hdr[42:44]) - e_phnum = bo.Uint16(hdr[44:46]) - e_shentsize = bo.Uint16(hdr[46:48]) - e_shnum = bo.Uint16(hdr[48:50]) - e_shstrndx = bo.Uint16(hdr[50:52]) - } - root.Children = append(root.Children, - &Field{Name: "e_type", Value: fmt.Sprintf("%d (%s)", e_type, elfType(e_type))}, - &Field{Name: "e_machine", Value: fmt.Sprintf("%d (%s)", e_machine, elfMachine(e_machine))}, - &Field{Name: "e_entry", Value: fmt.Sprintf("0x%x", e_entry)}, - &Field{Name: "e_phoff", Value: fmt.Sprintf("0x%x", e_phoff)}, - &Field{Name: "e_shoff", Value: fmt.Sprintf("0x%x", e_shoff)}, - &Field{Name: "e_phentsize", Value: fmt.Sprintf("%d", e_phentsize)}, - &Field{Name: "e_phnum", Value: fmt.Sprintf("%d", e_phnum)}, - &Field{Name: "e_shentsize", Value: fmt.Sprintf("%d", e_shentsize)}, - &Field{Name: "e_shnum", Value: fmt.Sprintf("%d", e_shnum)}, - &Field{Name: "e_shstrndx", Value: fmt.Sprintf("%d", e_shstrndx)}, - ) - root.Children = append(root.Children, elfProgramHeaders(d, bo, class, e_phoff, e_phentsize, e_phnum)...) - root.Children = append(root.Children, elfSectionHeaders(d, bo, class, e_shoff, e_shentsize, e_shnum, e_shstrndx)...) - return root, nil -} - -func elfByteOrder(data byte) binary.ByteOrder { - if data == 2 { - return binary.BigEndian - } - return binary.LittleEndian -} - -func elfProgramHeaders(d *document.Document, bo binary.ByteOrder, class byte, off uint64, entsize, count uint16) []*Field { - if off == 0 || entsize == 0 || count == 0 { - return nil - } - size := d.Size() - out := make([]*Field, 0, count) - for i := uint16(0); i < count; i++ { - at := int64(off) + int64(i)*int64(entsize) - if at < 0 || at+int64(entsize) > size { - break - } - buf, err := d.Range(at, int64(entsize)) - if err != nil { - break - } - var typ, flags uint32 - var pOffset, vaddr, filesz, memsz, align uint64 - if class == 2 { - if len(buf) < 56 { - break - } - typ = bo.Uint32(buf[0:4]) - flags = bo.Uint32(buf[4:8]) - pOffset = bo.Uint64(buf[8:16]) - vaddr = bo.Uint64(buf[16:24]) - filesz = bo.Uint64(buf[32:40]) - memsz = bo.Uint64(buf[40:48]) - align = bo.Uint64(buf[48:56]) - } else { - if len(buf) < 32 { - break - } - typ = bo.Uint32(buf[0:4]) - pOffset = uint64(bo.Uint32(buf[4:8])) - vaddr = uint64(bo.Uint32(buf[8:12])) - filesz = uint64(bo.Uint32(buf[16:20])) - memsz = uint64(bo.Uint32(buf[20:24])) - flags = bo.Uint32(buf[24:28]) - align = uint64(bo.Uint32(buf[28:32])) - } - out = append(out, &Field{ - Name: fmt.Sprintf("program[%d]", i), - Type: "program_header", - Offset: at, - Length: int64(entsize), - Value: fmt.Sprintf("type=%s offset=0x%x vaddr=0x%x filesz=%d memsz=%d flags=%s align=0x%x", elfProgramType(typ), pOffset, vaddr, filesz, memsz, elfProgramFlags(flags), align), - }) - } - return out -} - -type elfSectionInfo struct { - nameOff uint32 - typ uint32 - flags, addr uint64 - fileOff, secSize uint64 - link, info uint32 - align, entrySize uint64 - at int64 - length int64 -} - -func elfSectionHeaders(d *document.Document, bo binary.ByteOrder, class byte, off uint64, entsize, count, strIndex uint16) []*Field { - if off == 0 || entsize == 0 || count == 0 { - return nil - } - size := d.Size() - sections := make([]elfSectionInfo, 0, count) - for i := uint16(0); i < count; i++ { - at := int64(off) + int64(i)*int64(entsize) - if at < 0 || at+int64(entsize) > size { - break - } - buf, err := d.Range(at, int64(entsize)) - if err != nil { - break - } - var s elfSectionInfo - s.at = at - s.length = int64(entsize) - if class == 2 { - if len(buf) < 64 { - break - } - s.nameOff = bo.Uint32(buf[0:4]) - s.typ = bo.Uint32(buf[4:8]) - s.flags = bo.Uint64(buf[8:16]) - s.addr = bo.Uint64(buf[16:24]) - s.fileOff = bo.Uint64(buf[24:32]) - s.secSize = bo.Uint64(buf[32:40]) - s.link = bo.Uint32(buf[40:44]) - s.info = bo.Uint32(buf[44:48]) - s.align = bo.Uint64(buf[48:56]) - s.entrySize = bo.Uint64(buf[56:64]) - } else { - if len(buf) < 40 { - break - } - s.nameOff = bo.Uint32(buf[0:4]) - s.typ = bo.Uint32(buf[4:8]) - s.flags = uint64(bo.Uint32(buf[8:12])) - s.addr = uint64(bo.Uint32(buf[12:16])) - s.fileOff = uint64(bo.Uint32(buf[16:20])) - s.secSize = uint64(bo.Uint32(buf[20:24])) - s.link = bo.Uint32(buf[24:28]) - s.info = bo.Uint32(buf[28:32]) - s.align = uint64(bo.Uint32(buf[32:36])) - s.entrySize = uint64(bo.Uint32(buf[36:40])) - } - sections = append(sections, s) - } - var names []byte - if int(strIndex) < len(sections) { - str := sections[strIndex] - if str.fileOff <= uint64(size) && str.fileOff+str.secSize <= uint64(size) && str.secSize <= 1<<20 { - names, _ = d.Range(int64(str.fileOff), int64(str.secSize)) - } - } - sectionNames := make([]string, len(sections)) - for i, s := range sections { - name := elfStringAt(names, s.nameOff) - if name == "" { - name = fmt.Sprintf("section[%d]", i) - } - sectionNames[i] = name - } - out := make([]*Field, 0, len(sections)) - for i, s := range sections { - name := sectionNames[i] - children := []*Field{} - if s.typ == 6 { - children = elfDynamicEntries(d, bo, class, sections, s) - } else if s.typ == 2 || s.typ == 11 { - children = elfSymbolEntries(d, bo, class, sections, s) - } - out = append(out, &Field{ - Name: name, - Type: "section_header", - Offset: s.at, - Length: s.length, - Value: fmt.Sprintf("type=%s offset=0x%x size=%d addr=0x%x flags=%s link=%d info=%d align=0x%x entsize=%d", elfSectionType(s.typ), s.fileOff, s.secSize, s.addr, elfSectionFlags(s.flags), s.link, s.info, s.align, s.entrySize), - Children: children, - }) - } - return out -} - -func elfSymbolEntries(d *document.Document, bo binary.ByteOrder, class byte, sections []elfSectionInfo, symtab elfSectionInfo) []*Field { - entrySize := symtab.entrySize - if entrySize == 0 { - if class == 2 { - entrySize = 24 - } else { - entrySize = 16 - } - } - if entrySize == 0 || symtab.secSize == 0 || symtab.fileOff+symtab.secSize > uint64(d.Size()) { - return nil - } - var strtab []byte - if int(symtab.link) < len(sections) { - linked := sections[symtab.link] - if linked.fileOff+linked.secSize <= uint64(d.Size()) && linked.secSize <= 8<<20 { - strtab, _ = d.Range(int64(linked.fileOff), int64(linked.secSize)) - } - } - count := int(symtab.secSize / entrySize) - if count > 4096 { - count = 4096 - } - out := []*Field{} - for i := 0; i < count; i++ { - at := int64(symtab.fileOff + uint64(i)*entrySize) - buf, err := d.Range(at, int64(entrySize)) - if err != nil || len(buf) < int(entrySize) { - break - } - var nameOff uint32 - var info, other byte - var shndx uint16 - var value, size uint64 - if class == 2 { - nameOff = bo.Uint32(buf[0:4]) - info = buf[4] - other = buf[5] - shndx = bo.Uint16(buf[6:8]) - value = bo.Uint64(buf[8:16]) - size = bo.Uint64(buf[16:24]) - } else { - nameOff = bo.Uint32(buf[0:4]) - value = uint64(bo.Uint32(buf[4:8])) - size = uint64(bo.Uint32(buf[8:12])) - info = buf[12] - other = buf[13] - shndx = bo.Uint16(buf[14:16]) - } - name := elfStringAt(strtab, nameOff) - if name == "" { - name = fmt.Sprintf("", i) - } - out = append(out, &Field{ - Name: fmt.Sprintf("symbol[%d] %s", i, name), - Type: "symbol", - Offset: at, - Length: int64(entrySize), - Value: fmt.Sprintf("name=%s bind=%s type=%s vis=%s shndx=%s value=0x%x size=%d", - name, elfSymbolBind(info>>4), elfSymbolType(info&0xf), elfSymbolVisibility(other&0x3), elfSymbolSection(shndx), value, size), - }) - } - return out -} - -func elfDynamicEntries(d *document.Document, bo binary.ByteOrder, class byte, sections []elfSectionInfo, dyn elfSectionInfo) []*Field { - entrySize := dyn.entrySize - if entrySize == 0 { - if class == 2 { - entrySize = 16 - } else { - entrySize = 8 - } - } - if entrySize == 0 || dyn.secSize == 0 || dyn.fileOff+dyn.secSize > uint64(d.Size()) { - return nil - } - var strtab []byte - if int(dyn.link) < len(sections) { - linked := sections[dyn.link] - if linked.fileOff+linked.secSize <= uint64(d.Size()) && linked.secSize <= 8<<20 { - strtab, _ = d.Range(int64(linked.fileOff), int64(linked.secSize)) - } - } - out := []*Field{} - count := int(dyn.secSize / entrySize) - if count > 4096 { - count = 4096 - } - for i := 0; i < count; i++ { - at := int64(dyn.fileOff + uint64(i)*entrySize) - buf, err := d.Range(at, int64(entrySize)) - if err != nil || len(buf) < int(entrySize) { - break - } - var tag int64 - var val uint64 - if class == 2 { - tag = int64(bo.Uint64(buf[0:8])) - val = bo.Uint64(buf[8:16]) - } else { - tag = int64(int32(bo.Uint32(buf[0:4]))) - val = uint64(bo.Uint32(buf[4:8])) - } - value := fmt.Sprintf("%s=0x%x", elfDynamicTag(tag), val) - if tag == 1 && len(strtab) > 0 { - if s := elfStringAt(strtab, uint32(val)); s != "" { - value = "NEEDED=" + s - } - } - out = append(out, &Field{ - Name: fmt.Sprintf("dynamic[%d]", i), - Type: "dynamic_entry", - Offset: at, - Length: int64(entrySize), - Value: value, - }) - if tag == 0 { - break - } - } - return out -} - -func elfSymbolBind(bind byte) string { - switch bind { - case 0: - return "LOCAL" - case 1: - return "GLOBAL" - case 2: - return "WEAK" - } - return fmt.Sprintf("0x%x", bind) -} - -func elfSymbolType(typ byte) string { - switch typ { - case 0: - return "NOTYPE" - case 1: - return "OBJECT" - case 2: - return "FUNC" - case 3: - return "SECTION" - case 4: - return "FILE" - case 5: - return "COMMON" - case 6: - return "TLS" - } - return fmt.Sprintf("0x%x", typ) -} - -func elfSymbolVisibility(vis byte) string { - switch vis { - case 0: - return "DEFAULT" - case 1: - return "INTERNAL" - case 2: - return "HIDDEN" - case 3: - return "PROTECTED" - } - return fmt.Sprintf("0x%x", vis) -} - -func elfSymbolSection(idx uint16) string { - switch idx { - case 0: - return "UND" - case 0xfff1: - return "ABS" - case 0xfff2: - return "COMMON" - } - return fmt.Sprintf("%d", idx) -} - -func elfDynamicTag(tag int64) string { - switch tag { - case 0: - return "NULL" - case 1: - return "NEEDED" - case 2: - return "PLTRELSZ" - case 3: - return "PLTGOT" - case 4: - return "HASH" - case 5: - return "STRTAB" - case 6: - return "SYMTAB" - case 7: - return "RELA" - case 8: - return "RELASZ" - case 9: - return "RELAENT" - case 10: - return "STRSZ" - case 11: - return "SYMENT" - case 12: - return "INIT" - case 13: - return "FINI" - case 14: - return "SONAME" - case 15: - return "RPATH" - case 16: - return "SYMBOLIC" - case 17: - return "REL" - case 18: - return "RELSZ" - case 19: - return "RELENT" - case 20: - return "PLTREL" - case 21: - return "DEBUG" - case 22: - return "TEXTREL" - case 23: - return "JMPREL" - case 29: - return "RUNPATH" - } - return fmt.Sprintf("0x%x", tag) -} - -func elfStringAt(buf []byte, off uint32) string { - if int(off) >= len(buf) { - return "" - } - end := int(off) - for end < len(buf) && buf[end] != 0 { - end++ - } - return string(buf[off:end]) -} - -func elfProgramType(t uint32) string { - switch t { - case 0: - return "NULL" - case 1: - return "LOAD" - case 2: - return "DYNAMIC" - case 3: - return "INTERP" - case 4: - return "NOTE" - case 5: - return "SHLIB" - case 6: - return "PHDR" - case 7: - return "TLS" - } - return fmt.Sprintf("0x%x", t) -} - -func elfProgramFlags(flags uint32) string { - var out string - if flags&4 != 0 { - out += "R" - } - if flags&2 != 0 { - out += "W" - } - if flags&1 != 0 { - out += "X" - } - if out == "" { - return "-" - } - return out -} - -func elfSectionType(t uint32) string { - switch t { - case 0: - return "NULL" - case 1: - return "PROGBITS" - case 2: - return "SYMTAB" - case 3: - return "STRTAB" - case 4: - return "RELA" - case 5: - return "HASH" - case 6: - return "DYNAMIC" - case 7: - return "NOTE" - case 8: - return "NOBITS" - case 9: - return "REL" - case 11: - return "DYNSYM" - case 14: - return "INIT_ARRAY" - case 15: - return "FINI_ARRAY" - case 17: - return "GROUP" - case 18: - return "SYMTAB_SHNDX" - } - return fmt.Sprintf("0x%x", t) -} - -func elfSectionFlags(flags uint64) string { - var out string - if flags&0x1 != 0 { - out += "W" - } - if flags&0x2 != 0 { - out += "A" - } - if flags&0x4 != 0 { - out += "X" - } - if flags&0x10 != 0 { - out += "M" - } - if flags&0x20 != 0 { - out += "S" - } - if flags&0x40 != 0 { - out += "I" - } - if flags&0x400 != 0 { - out += "T" - } - if out == "" { - return "-" - } - return out -} - -func elfType(t uint16) string { - switch t { - case 1: - return "REL" - case 2: - return "EXEC" - case 3: - return "DYN" - case 4: - return "CORE" - } - return "UNKNOWN" -} - -func elfMachine(m uint16) string { - switch m { - case 0x03: - return "x86" - case 0x28: - return "ARM" - case 0x3e: - return "x86_64" - case 0xb7: - return "AArch64" - case 0x08: - return "MIPS" - case 0xf3: - return "RISC-V" - } - return "other" -} - -// ParsePE walks the DOS header, PE header, and section headers. -func ParsePE(d *document.Document) (*Field, error) { - hdr, err := d.Range(0, 64) - if err != nil || len(hdr) < 64 { - return nil, errors.New("too short") - } - lfanew := int64(binary.LittleEndian.Uint32(hdr[0x3c:0x40])) - root := &Field{Name: "PE", Type: "container", Offset: 0} - root.Children = append(root.Children, - &Field{Name: "e_magic", Offset: 0, Length: 2, Value: "MZ"}, - &Field{Name: "e_lfanew", Offset: 0x3c, Length: 4, Value: fmt.Sprintf("0x%x", lfanew)}, - ) - peHdr, err := d.Range(lfanew, 24) - if err != nil || len(peHdr) < 24 || string(peHdr[:4]) != "PE\x00\x00" { - return root, errors.New("no PE signature") - } - machine := binary.LittleEndian.Uint16(peHdr[4:6]) - numSections := binary.LittleEndian.Uint16(peHdr[6:8]) - timestamp := binary.LittleEndian.Uint32(peHdr[8:12]) - sizeOptHdr := binary.LittleEndian.Uint16(peHdr[20:22]) - characteristics := binary.LittleEndian.Uint16(peHdr[22:24]) - root.Children = append(root.Children, - &Field{Name: "Signature", Offset: lfanew, Length: 4, Value: "PE\\x00\\x00"}, - &Field{Name: "Machine", Offset: lfanew + 4, Length: 2, Value: fmt.Sprintf("0x%04x (%s)", machine, peMachine(machine))}, - &Field{Name: "NumberOfSections", Offset: lfanew + 6, Length: 2, Value: fmt.Sprintf("%d", numSections)}, - &Field{Name: "TimeDateStamp", Offset: lfanew + 8, Length: 4, Value: fmt.Sprintf("%d", timestamp)}, - &Field{Name: "SizeOfOptionalHeader", Offset: lfanew + 20, Length: 2, Value: fmt.Sprintf("%d", sizeOptHdr)}, - &Field{Name: "Characteristics", Offset: lfanew + 22, Length: 2, Value: fmt.Sprintf("0x%04x", characteristics)}, - ) - sections := make([]peSectionInfo, 0, numSections) - optOff := lfanew + 24 - var dataDirs []peDataDirectory - peIs64 := false - if sizeOptHdr > 0 { - opt, err := d.Range(optOff, int64(sizeOptHdr)) - if err == nil && len(opt) >= 2 { - optional, dirs := parsePEOptionalHeader(opt, optOff) - root.Children = append(root.Children, optional) - dataDirs = dirs - peIs64 = binary.LittleEndian.Uint16(opt[0:2]) == 0x20b - } - } - sectOff := lfanew + 24 + int64(sizeOptHdr) - for i := 0; i < int(numSections); i++ { - sh, err := d.Range(sectOff, 40) - if err != nil || len(sh) < 40 { - break - } - name := string(trimZero(sh[0:8])) - vsize := binary.LittleEndian.Uint32(sh[8:12]) - vaddr := binary.LittleEndian.Uint32(sh[12:16]) - rsize := binary.LittleEndian.Uint32(sh[16:20]) - raddr := binary.LittleEndian.Uint32(sh[20:24]) - flags := binary.LittleEndian.Uint32(sh[36:40]) - sec := &Field{ - Name: name, Type: "section", Offset: sectOff, Length: 40, - Value: fmt.Sprintf("vaddr=0x%x vsize=%d raw=0x%x rsize=%d flags=0x%x", vaddr, vsize, raddr, rsize, flags), - } - root.Children = append(root.Children, sec) - sections = append(sections, peSectionInfo{Name: name, VirtualAddress: vaddr, VirtualSize: vsize, RawOffset: raddr, RawSize: rsize, Characteristics: flags}) - sectOff += 40 - } - if len(dataDirs) > 0 && dataDirs[0].RVA != 0 && dataDirs[0].Size != 0 { - root.Children = append(root.Children, parsePEExports(d, sections, dataDirs[0])...) - } - if len(dataDirs) > 1 && dataDirs[1].RVA != 0 && dataDirs[1].Size != 0 { - root.Children = append(root.Children, parsePEImports(d, sections, dataDirs[1], peIs64)...) - } - if len(dataDirs) > 2 && dataDirs[2].RVA != 0 && dataDirs[2].Size != 0 { - root.Children = append(root.Children, parsePEResources(d, sections, dataDirs[2])...) - } - return root, nil -} - -type peDataDirectory struct { - Name string - RVA uint32 - Size uint32 -} - -type peSectionInfo struct { - Name string - VirtualAddress uint32 - VirtualSize uint32 - RawOffset uint32 - RawSize uint32 - Characteristics uint32 -} - -func parsePEOptionalHeader(opt []byte, off int64) (*Field, []peDataDirectory) { - magic := binary.LittleEndian.Uint16(opt[0:2]) - is64 := magic == 0x20b - name := "PE32" - if is64 { - name = "PE32+" - } - field := &Field{Name: "optional_header", Type: "optional_header", Offset: off, Length: int64(len(opt)), Value: name} - if len(opt) >= 28 { - field.Children = append(field.Children, - &Field{Name: "Magic", Type: "u16le", Offset: off, Length: 2, Value: fmt.Sprintf("0x%04x (%s)", magic, name)}, - &Field{Name: "AddressOfEntryPoint", Type: "u32le", Offset: off + 16, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[16:20]))}, - &Field{Name: "BaseOfCode", Type: "u32le", Offset: off + 20, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[20:24]))}, - ) - } - if is64 && len(opt) >= 112 { - field.Children = append(field.Children, - &Field{Name: "ImageBase", Type: "u64le", Offset: off + 24, Length: 8, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint64(opt[24:32]))}, - &Field{Name: "SectionAlignment", Type: "u32le", Offset: off + 32, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[32:36]))}, - &Field{Name: "FileAlignment", Type: "u32le", Offset: off + 36, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[36:40]))}, - &Field{Name: "SizeOfImage", Type: "u32le", Offset: off + 56, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[56:60]))}, - &Field{Name: "Subsystem", Type: "u16le", Offset: off + 68, Length: 2, Value: peSubsystem(binary.LittleEndian.Uint16(opt[68:70]))}, - ) - return field, peDataDirectories(opt, off, 112) - } - if !is64 && len(opt) >= 96 { - field.Children = append(field.Children, - &Field{Name: "ImageBase", Type: "u32le", Offset: off + 28, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[28:32]))}, - &Field{Name: "SectionAlignment", Type: "u32le", Offset: off + 32, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[32:36]))}, - &Field{Name: "FileAlignment", Type: "u32le", Offset: off + 36, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[36:40]))}, - &Field{Name: "SizeOfImage", Type: "u32le", Offset: off + 56, Length: 4, Value: fmt.Sprintf("0x%x", binary.LittleEndian.Uint32(opt[56:60]))}, - &Field{Name: "Subsystem", Type: "u16le", Offset: off + 68, Length: 2, Value: peSubsystem(binary.LittleEndian.Uint16(opt[68:70]))}, - ) - return field, peDataDirectories(opt, off, 96) - } - return field, nil -} - -func peDataDirectories(opt []byte, optOff int64, dirOff int) []peDataDirectory { - if len(opt) < dirOff { - return nil - } - names := []string{"export", "import", "resource", "exception", "certificate", "base_reloc", "debug", "architecture", "global_ptr", "tls", "load_config", "bound_import", "iat", "delay_import", "clr_runtime", "reserved"} - count := (len(opt) - dirOff) / 8 - if count > len(names) { - count = len(names) - } - out := make([]peDataDirectory, 0, count) - for i := 0; i < count; i++ { - at := dirOff + i*8 - rva := binary.LittleEndian.Uint32(opt[at : at+4]) - size := binary.LittleEndian.Uint32(opt[at+4 : at+8]) - out = append(out, peDataDirectory{Name: names[i], RVA: rva, Size: size}) - } - return out -} - -func parsePEImports(d *document.Document, sections []peSectionInfo, dir peDataDirectory, is64 bool) []*Field { - off, ok := peRVAToOffset(sections, dir.RVA) - if !ok { - return nil - } - out := []*Field{} - for i := 0; i < 256; i++ { - at := int64(off) + int64(i*20) - buf, err := d.Range(at, 20) - if err != nil || len(buf) < 20 { - break - } - oft := binary.LittleEndian.Uint32(buf[0:4]) - timestamp := binary.LittleEndian.Uint32(buf[4:8]) - forwarder := binary.LittleEndian.Uint32(buf[8:12]) - nameRVA := binary.LittleEndian.Uint32(buf[12:16]) - firstThunk := binary.LittleEndian.Uint32(buf[16:20]) - if oft == 0 && timestamp == 0 && forwarder == 0 && nameRVA == 0 && firstThunk == 0 { - break - } - name := "" - if nameOff, ok := peRVAToOffset(sections, nameRVA); ok { - name = readCString(d, int64(nameOff), 256) - } - if name == "" { - name = fmt.Sprintf("rva_0x%x", nameRVA) - } - out = append(out, &Field{ - Name: fmt.Sprintf("import[%d]", i), - Type: "import_descriptor", - Offset: at, - Length: 20, - Value: fmt.Sprintf("dll=%s oft=0x%x first_thunk=0x%x timestamp=0x%x", name, oft, firstThunk, timestamp), - Children: parsePEImportThunks(d, sections, oft, firstThunk, is64), - }) - } - return out -} - -func parsePEExports(d *document.Document, sections []peSectionInfo, dir peDataDirectory) []*Field { - off, ok := peRVAToOffset(sections, dir.RVA) - if !ok { - return nil - } - buf, err := d.Range(int64(off), 40) - if err != nil || len(buf) < 40 { - return nil - } - nameRVA := binary.LittleEndian.Uint32(buf[12:16]) - ordinalBase := binary.LittleEndian.Uint32(buf[16:20]) - numberOfFunctions := binary.LittleEndian.Uint32(buf[20:24]) - numberOfNames := binary.LittleEndian.Uint32(buf[24:28]) - functionsRVA := binary.LittleEndian.Uint32(buf[28:32]) - namesRVA := binary.LittleEndian.Uint32(buf[32:36]) - ordinalsRVA := binary.LittleEndian.Uint32(buf[36:40]) - module := "" - if nameOff, ok := peRVAToOffset(sections, nameRVA); ok { - module = readCString(d, int64(nameOff), 512) - } - if module == "" { - module = fmt.Sprintf("rva_0x%x", nameRVA) - } - root := &Field{ - Name: "exports", - Type: "export_directory", - Offset: int64(off), - Length: 40, - Value: fmt.Sprintf("module=%s ordinal_base=%d functions=%d names=%d", module, ordinalBase, numberOfFunctions, numberOfNames), - } - if numberOfNames > 4096 { - numberOfNames = 4096 - } - namesOff, namesOK := peRVAToOffset(sections, namesRVA) - ordinalsOff, ordOK := peRVAToOffset(sections, ordinalsRVA) - functionsOff, funcsOK := peRVAToOffset(sections, functionsRVA) - if namesOK && ordOK && funcsOK { - for i := uint32(0); i < numberOfNames; i++ { - namePtrAt := int64(namesOff) + int64(i)*4 - namePtr, err := d.Range(namePtrAt, 4) - if err != nil || len(namePtr) < 4 { - break - } - symNameRVA := binary.LittleEndian.Uint32(namePtr) - symName := "" - if symNameOff, ok := peRVAToOffset(sections, symNameRVA); ok { - symName = readCString(d, int64(symNameOff), 512) - } - if symName == "" { - symName = fmt.Sprintf("rva_0x%x", symNameRVA) - } - ordAt := int64(ordinalsOff) + int64(i)*2 - ordBuf, err := d.Range(ordAt, 2) - if err != nil || len(ordBuf) < 2 { - break - } - index := uint32(binary.LittleEndian.Uint16(ordBuf)) - if index >= numberOfFunctions { - continue - } - funcAt := int64(functionsOff) + int64(index)*4 - funcBuf, err := d.Range(funcAt, 4) - if err != nil || len(funcBuf) < 4 { - break - } - funcRVA := binary.LittleEndian.Uint32(funcBuf) - value := fmt.Sprintf("name=%s ordinal=%d rva=0x%x", symName, ordinalBase+index, funcRVA) - if funcOff, ok := peRVAToOffset(sections, funcRVA); ok { - value += fmt.Sprintf(" file=0x%x", funcOff) - } - root.Children = append(root.Children, &Field{ - Name: fmt.Sprintf("export[%d] %s", i, symName), - Type: "export", - Offset: namePtrAt, - Length: 4, - Value: value, - }) - } - } - return []*Field{root} -} - -func parsePEResources(d *document.Document, sections []peSectionInfo, dir peDataDirectory) []*Field { - baseOff, ok := peRVAToOffset(sections, dir.RVA) - if !ok { - return nil - } - root := &Field{ - Name: "resources", - Type: "resource_directory", - Offset: int64(baseOff), - Length: int64(dir.Size), - Value: fmt.Sprintf("rva=0x%x size=%d", dir.RVA, dir.Size), - } - seen := map[uint32]bool{} - root.Children = parsePEResourceDirectory(d, sections, dir.RVA, baseOff, 0, 0, seen, "") - return []*Field{root} -} - -func parsePEResourceDirectory(d *document.Document, sections []peSectionInfo, baseRVA, baseOff, rel uint32, depth int, seen map[uint32]bool, path string) []*Field { - if depth > 8 || seen[rel] { - return nil - } - seen[rel] = true - defer delete(seen, rel) - - at := int64(baseOff + rel) - buf, err := d.Range(at, 16) - if err != nil || len(buf) < 16 { - return nil - } - named := binary.LittleEndian.Uint16(buf[12:14]) - ids := binary.LittleEndian.Uint16(buf[14:16]) - count := int(named) + int(ids) - if count > 1024 { - count = 1024 - } - header := &Field{ - Name: fmt.Sprintf("directory depth=%d", depth), - Type: "resource_directory_header", - Offset: at, - Length: 16, - Value: fmt.Sprintf("named=%d ids=%d timestamp=0x%x version=%d.%d", named, ids, binary.LittleEndian.Uint32(buf[4:8]), binary.LittleEndian.Uint16(buf[10:12]), binary.LittleEndian.Uint16(buf[8:10])), - } - out := []*Field{header} - for i := 0; i < count; i++ { - entryAt := at + 16 + int64(i)*8 - ent, err := d.Range(entryAt, 8) - if err != nil || len(ent) < 8 { - break - } - nameRaw := binary.LittleEndian.Uint32(ent[0:4]) - targetRaw := binary.LittleEndian.Uint32(ent[4:8]) - name := peResourceName(d, baseOff, nameRaw, depth) - targetRel := targetRaw & 0x7fffffff - nextPath := path - if nextPath == "" { - nextPath = name - } else { - nextPath += "/" + name - } - entry := &Field{ - Name: fmt.Sprintf("entry[%d] %s", i, name), - Type: "resource_entry", - Offset: entryAt, - Length: 8, - Value: fmt.Sprintf("name=0x%x target=0x%x path=%s", nameRaw, targetRaw, nextPath), - } - if targetRaw&0x80000000 != 0 { - entry.Children = parsePEResourceDirectory(d, sections, baseRVA, baseOff, targetRel, depth+1, seen, nextPath) - } else { - entry.Children = parsePEResourceDataEntry(d, sections, baseOff, targetRel, nextPath) - } - out = append(out, entry) - } - return out -} - -func parsePEResourceDataEntry(d *document.Document, sections []peSectionInfo, baseOff, rel uint32, path string) []*Field { - at := int64(baseOff + rel) - buf, err := d.Range(at, 16) - if err != nil || len(buf) < 16 { - return nil - } - dataRVA := binary.LittleEndian.Uint32(buf[0:4]) - size := binary.LittleEndian.Uint32(buf[4:8]) - codePage := binary.LittleEndian.Uint32(buf[8:12]) - dataOff, mapped := peRVAToOffset(sections, dataRVA) - value := fmt.Sprintf("data_rva=0x%x size=%d codepage=%d", dataRVA, size, codePage) - if mapped { - value += fmt.Sprintf(" file=0x%x", dataOff) - } - entry := &Field{Name: "data", Type: "resource_data", Offset: at, Length: 16, Value: value} - if mapped && size > 0 { - entry.Children = append(entry.Children, peResourcePayloadFields(d, int64(dataOff), size, path, codePage)...) - } - return []*Field{entry} -} - -func peResourcePayloadFields(d *document.Document, off int64, size uint32, path string, codePage uint32) []*Field { - if size > 16<<20 { - return []*Field{{Name: "payload", Type: "resource_payload", Offset: off, Length: int64(size), Value: "too large to decode"}} - } - buf, err := d.Range(off, int64(size)) - if err != nil { - return nil - } - out := []*Field{{Name: "payload", Type: "resource_payload", Offset: off, Length: int64(size), Value: fmt.Sprintf("%d bytes", size)}} - parts := strings.Split(path, "/") - typ := "" - if len(parts) > 0 { - typ = parts[0] - } - switch { - case strings.HasPrefix(typ, "RT_VERSION"): - out = append(out, decodePEVersionResource(buf, off)...) - case strings.HasPrefix(typ, "RT_STRING"): - out = append(out, decodePEStringTable(buf, off)...) - case strings.HasPrefix(typ, "RT_MANIFEST"): - out = append(out, &Field{Name: "manifest", Type: "manifest", Offset: off, Length: int64(size), Value: peResourceTextPreview(buf, codePage)}) - default: - if txt := peResourceTextPreview(buf, codePage); txt != "" { - out = append(out, &Field{Name: "text_preview", Type: "text", Offset: off, Length: int64(size), Value: txt}) - } - } - return out -} - -func peResourceName(d *document.Document, baseOff, raw uint32, depth int) string { - if raw&0x80000000 != 0 { - rel := raw & 0x7fffffff - buf, err := d.Range(int64(baseOff+rel), 2) - if err != nil || len(buf) < 2 { - return fmt.Sprintf("name@0x%x", rel) - } - n := int(binary.LittleEndian.Uint16(buf)) - if n <= 0 || n > 512 { - return fmt.Sprintf("name@0x%x", rel) - } - rawName, err := d.Range(int64(baseOff+rel+2), int64(n*2)) - if err != nil || len(rawName) < n*2 { - return fmt.Sprintf("name@0x%x", rel) - } - return decodeUTF16LE(rawName) - } - id := raw & 0xffff - if depth == 0 { - return peResourceTypeName(id) - } - if depth == 2 { - return peLanguageName(id) - } - return fmt.Sprintf("#%d", id) -} - -func peResourceTypeName(id uint32) string { - names := map[uint32]string{ - 1: "RT_CURSOR", 2: "RT_BITMAP", 3: "RT_ICON", 4: "RT_MENU", 5: "RT_DIALOG", 6: "RT_STRING", - 7: "RT_FONTDIR", 8: "RT_FONT", 9: "RT_ACCELERATOR", 10: "RT_RCDATA", 11: "RT_MESSAGETABLE", - 12: "RT_GROUP_CURSOR", 14: "RT_GROUP_ICON", 16: "RT_VERSION", 17: "RT_DLGINCLUDE", - 19: "RT_PLUGPLAY", 20: "RT_VXD", 21: "RT_ANICURSOR", 22: "RT_ANIICON", 23: "RT_HTML", 24: "RT_MANIFEST", - } - if name, ok := names[id]; ok { - return fmt.Sprintf("%s(%d)", name, id) - } - return fmt.Sprintf("#%d", id) -} - -func peLanguageName(id uint32) string { - switch id { - case 0: - return "LANG_NEUTRAL" - case 0x0409: - return "en-US" - case 0x0809: - return "en-GB" - } - return fmt.Sprintf("lang-0x%04x", id) -} - -func decodePEStringTable(buf []byte, off int64) []*Field { - out := []*Field{} - pos := 0 - for i := 0; i < 16 && pos+2 <= len(buf); i++ { - n := int(binary.LittleEndian.Uint16(buf[pos : pos+2])) - pos += 2 - if pos+n*2 > len(buf) { - break - } - if n > 0 { - out = append(out, &Field{Name: fmt.Sprintf("string[%d]", i), Type: "resource_string", Offset: off + int64(pos), Length: int64(n * 2), Value: decodeUTF16LE(buf[pos : pos+n*2])}) - } - pos += n * 2 - } - return out -} - -func decodePEVersionResource(buf []byte, off int64) []*Field { - if len(buf) < 6 { - return nil - } - key, keyEnd := readPEVersionKey(buf, 6) - out := []*Field{{Name: "version_info", Type: "version_info", Offset: off, Length: int64(len(buf)), Value: fmt.Sprintf("key=%s length=%d valueLength=%d type=%d", key, binary.LittleEndian.Uint16(buf[0:2]), binary.LittleEndian.Uint16(buf[2:4]), binary.LittleEndian.Uint16(buf[4:6]))}} - valueOff := align4(keyEnd) - if key == "VS_VERSION_INFO" && valueOff+52 <= len(buf) { - fixed := buf[valueOff : valueOff+52] - if binary.LittleEndian.Uint32(fixed[0:4]) == 0xfeef04bd { - fileMS := binary.LittleEndian.Uint32(fixed[8:12]) - fileLS := binary.LittleEndian.Uint32(fixed[12:16]) - prodMS := binary.LittleEndian.Uint32(fixed[16:20]) - prodLS := binary.LittleEndian.Uint32(fixed[20:24]) - out = append(out, &Field{Name: "fixed_file_info", Type: "version_fixed", Offset: off + int64(valueOff), Length: 52, Value: fmt.Sprintf("file=%d.%d.%d.%d product=%d.%d.%d.%d flags=0x%x type=0x%x subtype=0x%x", - fileMS>>16, fileMS&0xffff, fileLS>>16, fileLS&0xffff, - prodMS>>16, prodMS&0xffff, prodLS>>16, prodLS&0xffff, - binary.LittleEndian.Uint32(fixed[28:32]), binary.LittleEndian.Uint32(fixed[36:40]), binary.LittleEndian.Uint32(fixed[40:44]))}) - } - } - if strings.Contains(string(buf), "StringFileInfo") || strings.Contains(string(buf), "VarFileInfo") { - text := decodeUTF16LEASCII(buf) - for _, label := range []string{"CompanyName", "FileDescription", "FileVersion", "ProductName", "ProductVersion", "OriginalFilename"} { - if strings.Contains(text, label) { - out = append(out, &Field{Name: label, Type: "version_string_hint", Offset: off, Length: int64(len(buf)), Value: label}) - } - } - } - return out -} - -func readPEVersionKey(buf []byte, pos int) (string, int) { - start := pos - for pos+1 < len(buf) { - if buf[pos] == 0 && buf[pos+1] == 0 { - return decodeUTF16LE(buf[start:pos]), pos + 2 - } - pos += 2 - } - return "", pos -} - -func align4(n int) int { - return (n + 3) &^ 3 -} - -func peResourceTextPreview(buf []byte, codePage uint32) string { - if len(buf) == 0 { - return "" - } - if len(buf) >= 2 { - if buf[0] == 0xff && buf[1] == 0xfe { - return cleanResourcePreview(decodeUTF16LE(buf[2:])) - } - if buf[0] == 0xfe && buf[1] == 0xff { - return cleanResourcePreview(decodeUTF16BE(buf[2:])) - } - } - if codePage == 1200 { - return cleanResourcePreview(decodeUTF16LE(buf)) - } - if utf8.Valid(buf) { - return cleanResourcePreview(string(buf)) - } - return "" -} - -func cleanResourcePreview(s string) string { - s = strings.TrimSpace(strings.ReplaceAll(s, "\x00", "")) - if len(s) > 512 { - s = s[:512] + "..." - } - if strings.IndexFunc(s, func(r rune) bool { return r < 0x09 }) >= 0 { - return "" - } - return s -} - -func decodeUTF16LE(buf []byte) string { - u := make([]uint16, 0, len(buf)/2) - for i := 0; i+1 < len(buf); i += 2 { - u = append(u, binary.LittleEndian.Uint16(buf[i:i+2])) - } - return string(utf16.Decode(u)) -} - -func decodeUTF16BE(buf []byte) string { - u := make([]uint16, 0, len(buf)/2) - for i := 0; i+1 < len(buf); i += 2 { - u = append(u, binary.BigEndian.Uint16(buf[i:i+2])) - } - return string(utf16.Decode(u)) -} - -func decodeUTF16LEASCII(buf []byte) string { - var b strings.Builder - for i := 0; i+1 < len(buf); i += 2 { - r := binary.LittleEndian.Uint16(buf[i : i+2]) - if r >= 0x20 && r < 0x7f { - b.WriteByte(byte(r)) - } else { - b.WriteByte(' ') - } - } - return b.String() -} - -func parsePEImportThunks(d *document.Document, sections []peSectionInfo, oft, firstThunk uint32, is64 bool) []*Field { - thunkRVA := oft - if thunkRVA == 0 { - thunkRVA = firstThunk - } - thunkOff, ok := peRVAToOffset(sections, thunkRVA) - if !ok { - return nil - } - width := int64(4) - ordinalMask := uint64(0x80000000) - if is64 { - width = 8 - ordinalMask = 0x8000000000000000 - } - out := []*Field{} - for i := 0; i < 4096; i++ { - at := int64(thunkOff) + int64(i)*width - buf, err := d.Range(at, width) - if err != nil || int64(len(buf)) < width { - break - } - var thunk uint64 - if is64 { - thunk = binary.LittleEndian.Uint64(buf) - } else { - thunk = uint64(binary.LittleEndian.Uint32(buf)) - } - if thunk == 0 { - break - } - value := "" - if thunk&ordinalMask != 0 { - value = fmt.Sprintf("ordinal=%d", thunk&0xffff) - } else if nameOff, ok := peRVAToOffset(sections, uint32(thunk)); ok { - name := readImportByName(d, int64(nameOff)) - if name != "" { - value = "name=" + name - } else { - value = fmt.Sprintf("name_rva=0x%x", thunk) - } - } else { - value = fmt.Sprintf("thunk=0x%x", thunk) - } - out = append(out, &Field{ - Name: fmt.Sprintf("thunk[%d]", i), - Type: "import_thunk", - Offset: at, - Length: width, - Value: value, - }) - } - return out -} - -func readImportByName(d *document.Document, off int64) string { - buf, err := d.Range(off, 258) - if err != nil || len(buf) < 3 { - return "" - } - return readCStringFrom(buf[2:]) -} - -func peRVAToOffset(sections []peSectionInfo, rva uint32) (uint32, bool) { - for _, s := range sections { - span := s.VirtualSize - if s.RawSize > span { - span = s.RawSize - } - if span == 0 { - continue - } - if rva >= s.VirtualAddress && rva < s.VirtualAddress+span { - return s.RawOffset + (rva - s.VirtualAddress), true - } - } - return 0, false -} - -func readCString(d *document.Document, off int64, max int64) string { - buf, err := d.Range(off, max) - if err != nil { - return "" - } - return readCStringFrom(buf) -} - -func readCStringFrom(buf []byte) string { - for i, b := range buf { - if b == 0 { - return string(buf[:i]) - } - } - return string(buf) -} - -func peSubsystem(v uint16) string { - switch v { - case 1: - return "native" - case 2: - return "windows_gui" - case 3: - return "windows_cui" - case 7: - return "posix_cui" - case 9: - return "windows_ce_gui" - case 10: - return "efi_application" - case 11: - return "efi_boot_service_driver" - case 12: - return "efi_runtime_driver" - } - return fmt.Sprintf("0x%x", v) -} - -func peMachine(m uint16) string { - switch m { - case 0x14c: - return "i386" - case 0x8664: - return "x86_64" - case 0x1c0: - return "ARM" - case 0xaa64: - return "ARM64" - case 0x200: - return "IA64" - } - return "?" -} - -func trimZero(b []byte) []byte { - for i, c := range b { - if c == 0 { - return b[:i] - } - } - return b -} - -// ParseRIFF walks RIFF chunks. -func ParseRIFF(d *document.Document) (*Field, error) { - size := d.Size() - if size < 12 { - return nil, errors.New("too short") - } - hdr, err := d.Range(0, 12) - if err != nil { - return nil, err - } - total := int64(binary.LittleEndian.Uint32(hdr[4:8])) - form := string(hdr[8:12]) - end := total + 8 - if end > size { - end = size - } - root := &Field{ - Name: "RIFF", Type: "container", Offset: 0, Length: total + 8, - Value: fmt.Sprintf("form=%s (%s)", form, riffFormName(form)), - } - root.Children = riffChunks(d, 12, end, 0) - return root, nil -} - -// riffChunks walks the chunks in [off,end), recursing into LIST containers and -// decoding the common WAV/AVI chunks. -func riffChunks(d *document.Document, off, end int64, depth int) []*Field { - var out []*Field - for off+8 <= end && depth < 16 { - ch, err := d.Range(off, 8) - if err != nil || len(ch) < 8 { - break - } - id := string(ch[0:4]) - ln := int64(binary.LittleEndian.Uint32(ch[4:8])) - body := off + 8 - chunkEnd := body + ln - if chunkEnd > end { - chunkEnd = end - } - f := &Field{Name: riffChunkLabel(id), Type: "chunk", Offset: off, Length: 8 + ln, Value: fmt.Sprintf("length=%d", ln)} - switch id { - case "LIST", "RIFF": - if ln >= 4 { - lb, _ := d.Range(body, 4) - lt := string(lb) - f.Type = "list" - f.Value = fmt.Sprintf("type=%s (%s)", lt, riffFormName(lt)) - f.Children = riffChunks(d, body+4, chunkEnd, depth+1) - } - case "fmt ": - if fb, e := d.Range(body, min64(16, ln)); e == nil && len(fb) >= 16 { - f.Children = []*Field{ - {Name: "audio_format", Type: "u16le", Offset: body, Length: 2, Value: wavFormat(binary.LittleEndian.Uint16(fb[0:2]))}, - {Name: "channels", Type: "u16le", Offset: body + 2, Length: 2, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint16(fb[2:4]))}, - {Name: "sample_rate", Type: "u32le", Offset: body + 4, Length: 4, Value: fmt.Sprintf("%d Hz", binary.LittleEndian.Uint32(fb[4:8]))}, - {Name: "byte_rate", Type: "u32le", Offset: body + 8, Length: 4, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint32(fb[8:12]))}, - {Name: "block_align", Type: "u16le", Offset: body + 12, Length: 2, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint16(fb[12:14]))}, - {Name: "bits_per_sample", Type: "u16le", Offset: body + 14, Length: 2, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint16(fb[14:16]))}, - } - } - case "avih": - if ab, e := d.Range(body, min64(40, ln)); e == nil && len(ab) >= 16 { - f.Children = []*Field{ - {Name: "microsec_per_frame", Type: "u32le", Offset: body, Length: 4, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint32(ab[0:4]))}, - {Name: "max_bytes_per_sec", Type: "u32le", Offset: body + 4, Length: 4, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint32(ab[4:8]))}, - {Name: "total_frames", Type: "u32le", Offset: body + 16, Length: 4, Value: fmt.Sprintf("%d", binary.LittleEndian.Uint32(ab[16:20]))}, - } - } - } - out = append(out, f) - off = chunkEnd - if ln%2 != 0 { - off++ // chunks are word-aligned - } - } - return out -} - -func riffFormName(f string) string { - switch f { - case "WAVE": - return "audio/wav" - case "AVI ": - return "video/avi" - case "WEBP": - return "image/webp" - case "ANI ": - return "animated cursor" - case "RMID": - return "MIDI" - case "INFO": - return "metadata" - case "movi": - return "movie data" - case "hdrl": - return "header list" - case "strl": - return "stream list" - default: - return "?" - } -} - -func riffChunkLabel(id string) string { - return strings.TrimRight(id, " ") -} - -func wavFormat(f uint16) string { - names := map[uint16]string{1: "PCM", 2: "ADPCM", 3: "IEEE float", 6: "A-law", 7: "mu-law", 0xfffe: "extensible"} - if n, ok := names[f]; ok { - return fmt.Sprintf("%d (%s)", f, n) - } - return fmt.Sprintf("%d", f) -} - -func min64(a, b int64) int64 { - if a < b { - return a - } - return b -} diff --git a/internal/leviathan/parsers/formats_more.go b/internal/leviathan/parsers/formats_more.go deleted file mode 100644 index f15d391..0000000 --- a/internal/leviathan/parsers/formats_more.go +++ /dev/null @@ -1,662 +0,0 @@ -// Additional structure parsers that round out the structures panel into a -// forensic-grade set: gzip, tar, SQLite, TIFF, pcap/pcapng and the OLE/CFB -// compound-file container used by legacy Office documents and MSI installers. -// -// Each parser follows the same contract as the parsers in formats.go: it -// returns a *Field tree anchored at absolute document offsets, never panics on -// truncated input, and bounds every loop so a hostile file cannot wedge the UI. -package parsers - -import ( - "encoding/binary" - "errors" - "fmt" - "strconv" - "strings" - "time" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// leaf builds a terminal Field with no children. -func leaf(name, typ string, off, length int64, value string) *Field { - return &Field{Name: name, Type: typ, Offset: off, Length: length, Value: value} -} - -// ---------- gzip (RFC 1952) ---------- - -// ParseGZIP decodes the gzip member header, optional name/comment/extra fields -// and the trailing CRC-32 / ISIZE footer. -func ParseGZIP(d *document.Document) (*Field, error) { - size := d.Size() - hdr, err := d.Range(0, min64(10, size)) - if err != nil || len(hdr) < 10 { - return nil, errors.New("gzip too short") - } - if hdr[0] != 0x1f || hdr[1] != 0x8b { - return nil, errors.New("not gzip") - } - root := &Field{Name: "GZIP", Type: "container", Offset: 0, Length: size} - method := hdr[2] - methodName := "deflate" - if method != 8 { - methodName = fmt.Sprintf("unknown(%d)", method) - } - flg := hdr[3] - mtime := binary.LittleEndian.Uint32(hdr[4:8]) - root.Children = append(root.Children, - leaf("id", "bytes", 0, 2, "\\x1f\\x8b"), - leaf("method", "u8", 2, 1, methodName), - leaf("flags", "u8", 3, 1, gzipFlags(flg)), - leaf("mtime", "u32le", 4, 4, gzipTime(mtime)), - leaf("extra_flags", "u8", 8, 1, fmt.Sprintf("%d", hdr[8])), - leaf("os", "u8", 9, 1, gzipOS(hdr[9])), - ) - off := int64(10) - // FEXTRA - if flg&0x04 != 0 && off+2 <= size { - xb, _ := d.Range(off, 2) - xlen := int64(binary.LittleEndian.Uint16(xb)) - root.Children = append(root.Children, leaf("extra", "bytes", off, 2+xlen, fmt.Sprintf("%d bytes", xlen))) - off += 2 + xlen - } - // FNAME - if flg&0x08 != 0 { - s, n := readCStringField(d, off, size) - root.Children = append(root.Children, leaf("name", "asciiz", off, n, s)) - off += n - } - // FCOMMENT - if flg&0x10 != 0 { - s, n := readCStringField(d, off, size) - root.Children = append(root.Children, leaf("comment", "asciiz", off, n, s)) - off += n - } - // FHCRC - if flg&0x02 != 0 && off+2 <= size { - hb, _ := d.Range(off, 2) - root.Children = append(root.Children, leaf("header_crc16", "u16le", off, 2, fmt.Sprintf("0x%04x", binary.LittleEndian.Uint16(hb)))) - off += 2 - } - if size >= off+8 { - root.Children = append(root.Children, leaf("compressed_data", "bytes", off, size-off-8, fmt.Sprintf("%d bytes", size-off-8))) - ft, _ := d.Range(size-8, 8) - root.Children = append(root.Children, - leaf("crc32", "u32le", size-8, 4, fmt.Sprintf("0x%08x", binary.LittleEndian.Uint32(ft[0:4]))), - leaf("isize", "u32le", size-4, 4, fmt.Sprintf("%d bytes (mod 2^32)", binary.LittleEndian.Uint32(ft[4:8]))), - ) - } - return root, nil -} - -func gzipFlags(f byte) string { - var on []string - for _, b := range []struct { - mask byte - name string - }{{0x01, "TEXT"}, {0x02, "HCRC"}, {0x04, "EXTRA"}, {0x08, "NAME"}, {0x10, "COMMENT"}} { - if f&b.mask != 0 { - on = append(on, b.name) - } - } - if len(on) == 0 { - return fmt.Sprintf("0x%02x", f) - } - return fmt.Sprintf("0x%02x (%s)", f, strings.Join(on, "|")) -} - -func gzipOS(b byte) string { - names := map[byte]string{0: "FAT", 1: "Amiga", 2: "VMS", 3: "Unix", 6: "HPFS", 7: "Macintosh", 11: "NTFS", 255: "unknown"} - if n, ok := names[b]; ok { - return fmt.Sprintf("%d (%s)", b, n) - } - return fmt.Sprintf("%d", b) -} - -func gzipTime(t uint32) string { - if t == 0 { - return "0 (no timestamp)" - } - return fmt.Sprintf("%d (%s)", t, time.Unix(int64(t), 0).UTC().Format("2006-01-02 15:04:05 UTC")) -} - -// ---------- tar (POSIX ustar) ---------- - -// ParseTAR walks the 512-byte record headers of a ustar/GNU tar archive. -func ParseTAR(d *document.Document) (*Field, error) { - size := d.Size() - root := &Field{Name: "TAR", Type: "container", Offset: 0} - off := int64(0) - entries := 0 - for off+512 <= size && entries < 10000 { - blk, err := d.Range(off, 512) - if err != nil { - break - } - if allZero(blk) { - root.Children = append(root.Children, leaf("end_of_archive", "bytes", off, 512, "zero block")) - off += 512 - break - } - name := trimZeroStr(blk[0:100]) - prefix := trimZeroStr(blk[345:500]) - full := name - if prefix != "" { - full = prefix + "/" + name - } - fsize := octal(blk[124:136]) - mtime := octal(blk[136:148]) - typ := blk[156] - e := &Field{ - Name: full, Type: "entry", Offset: off, - Length: 512 + roundUp(fsize, 512), - Value: fmt.Sprintf("%s size=%d", tarType(typ), fsize), - Children: []*Field{ - leaf("name", "asciiz", off, 100, name), - leaf("mode", "octal", off+100, 8, fmt.Sprintf("0%o", octal(blk[100:108]))), - leaf("uid", "octal", off+108, 8, fmt.Sprintf("%d", octal(blk[108:116]))), - leaf("gid", "octal", off+116, 8, fmt.Sprintf("%d", octal(blk[116:124]))), - leaf("size", "octal", off+124, 12, fmt.Sprintf("%d", fsize)), - leaf("mtime", "octal", off+136, 12, tarTime(mtime)), - leaf("typeflag", "u8", off+156, 1, tarType(typ)), - leaf("magic", "ascii", off+257, 8, trimZeroStr(blk[257:265])), - leaf("uname", "asciiz", off+265, 32, trimZeroStr(blk[265:297])), - leaf("gname", "asciiz", off+297, 32, trimZeroStr(blk[297:329])), - }, - } - if prefix != "" { - e.Children = append(e.Children, leaf("prefix", "asciiz", off+345, 155, prefix)) - } - root.Children = append(root.Children, e) - off += 512 + roundUp(fsize, 512) - entries++ - } - root.Length = off - if len(root.Children) == 0 { - return nil, errors.New("no tar entries") - } - return root, nil -} - -func tarType(t byte) string { - switch t { - case 0, '0': - return "file" - case '1': - return "hardlink" - case '2': - return "symlink" - case '3': - return "char-device" - case '4': - return "block-device" - case '5': - return "directory" - case '6': - return "fifo" - case '7': - return "contiguous" - case 'x': - return "pax-header" - case 'g': - return "pax-global" - case 'L': - return "gnu-longname" - case 'K': - return "gnu-longlink" - default: - return fmt.Sprintf("type-%q", t) - } -} - -func tarTime(t int64) string { - if t == 0 { - return "0" - } - return fmt.Sprintf("%d (%s)", t, time.Unix(t, 0).UTC().Format("2006-01-02 15:04:05 UTC")) -} - -// ---------- SQLite 3 ---------- - -// ParseSQLite decodes the 100-byte SQLite database header. -func ParseSQLite(d *document.Document) (*Field, error) { - hdr, err := d.Range(0, min64(100, d.Size())) - if err != nil || len(hdr) < 100 { - return nil, errors.New("sqlite header too short") - } - if string(hdr[0:16]) != "SQLite format 3\x00" { - return nil, errors.New("not a SQLite database") - } - be := binary.BigEndian - pageSize := int64(be.Uint16(hdr[16:18])) - if pageSize == 1 { - pageSize = 65536 - } - enc := map[uint32]string{1: "UTF-8", 2: "UTF-16le", 3: "UTF-16be"}[be.Uint32(hdr[56:60])] - if enc == "" { - enc = "unknown" - } - root := &Field{ - Name: "SQLite", Type: "container", Offset: 0, Length: d.Size(), - Value: fmt.Sprintf("page=%d pages=%d %s", pageSize, be.Uint32(hdr[28:32]), enc), - Children: []*Field{ - leaf("magic", "ascii", 0, 16, "SQLite format 3"), - leaf("page_size", "u16be", 16, 2, fmt.Sprintf("%d", pageSize)), - leaf("write_version", "u8", 18, 1, sqliteFmtVer(hdr[18])), - leaf("read_version", "u8", 19, 1, sqliteFmtVer(hdr[19])), - leaf("reserved_space", "u8", 20, 1, fmt.Sprintf("%d", hdr[20])), - leaf("change_counter", "u32be", 24, 4, fmt.Sprintf("%d", be.Uint32(hdr[24:28]))), - leaf("page_count", "u32be", 28, 4, fmt.Sprintf("%d", be.Uint32(hdr[28:32]))), - leaf("freelist_head", "u32be", 32, 4, fmt.Sprintf("%d", be.Uint32(hdr[32:36]))), - leaf("freelist_pages", "u32be", 36, 4, fmt.Sprintf("%d", be.Uint32(hdr[36:40]))), - leaf("schema_cookie", "u32be", 40, 4, fmt.Sprintf("%d", be.Uint32(hdr[40:44]))), - leaf("schema_format", "u32be", 44, 4, fmt.Sprintf("%d", be.Uint32(hdr[44:48]))), - leaf("text_encoding", "u32be", 56, 4, enc), - leaf("user_version", "u32be", 60, 4, fmt.Sprintf("%d", be.Uint32(hdr[60:64]))), - leaf("vacuum_mode", "u32be", 64, 4, fmt.Sprintf("%d", be.Uint32(hdr[64:68]))), - leaf("application_id", "u32be", 68, 4, fmt.Sprintf("0x%08x", be.Uint32(hdr[68:72]))), - leaf("version_valid_for", "u32be", 92, 4, fmt.Sprintf("%d", be.Uint32(hdr[92:96]))), - leaf("sqlite_version", "u32be", 96, 4, sqliteVersion(be.Uint32(hdr[96:100]))), - }, - } - return root, nil -} - -func sqliteFmtVer(b byte) string { - switch b { - case 1: - return "1 (legacy/rollback)" - case 2: - return "2 (WAL)" - default: - return fmt.Sprintf("%d", b) - } -} - -func sqliteVersion(v uint32) string { - // SQLITE_VERSION_NUMBER = major*1_000_000 + minor*1000 + patch. - return fmt.Sprintf("%d (%d.%d.%d)", v, v/1000000, (v/1000)%1000, v%1000) -} - -// ---------- TIFF ---------- - -// ParseTIFF decodes the TIFF header and the entries of each image file -// directory (IFD), following the next-IFD chain. -func ParseTIFF(d *document.Document) (*Field, error) { - size := d.Size() - hdr, err := d.Range(0, min64(8, size)) - if err != nil || len(hdr) < 8 { - return nil, errors.New("tiff too short") - } - var bo binary.ByteOrder - switch string(hdr[0:2]) { - case "II": - bo = binary.LittleEndian - case "MM": - bo = binary.BigEndian - default: - return nil, errors.New("not TIFF") - } - if bo.Uint16(hdr[2:4]) != 42 { - return nil, errors.New("bad TIFF magic") - } - order := "little-endian" - if string(hdr[0:2]) == "MM" { - order = "big-endian" - } - root := &Field{ - Name: "TIFF", Type: "container", Offset: 0, Length: size, Value: order, - Children: []*Field{ - leaf("byte_order", "ascii", 0, 2, order), - leaf("magic", "u16", 2, 2, "42"), - leaf("first_ifd", "u32", 4, 4, fmt.Sprintf("0x%x", bo.Uint32(hdr[4:8]))), - }, - } - ifdOff := int64(bo.Uint32(hdr[4:8])) - for n := 0; ifdOff > 0 && ifdOff+2 <= size && n < 64; n++ { - cb, err := d.Range(ifdOff, 2) - if err != nil { - break - } - count := int64(bo.Uint16(cb)) - ifd := &Field{ - Name: fmt.Sprintf("IFD[%d]", n), Type: "directory", Offset: ifdOff, - Length: 2 + count*12 + 4, Value: fmt.Sprintf("%d entries", count), - } - for i := int64(0); i < count && i < 4096; i++ { - eoff := ifdOff + 2 + i*12 - eb, err := d.Range(eoff, 12) - if err != nil || len(eb) < 12 { - break - } - tag := bo.Uint16(eb[0:2]) - typ := bo.Uint16(eb[2:4]) - cnt := bo.Uint32(eb[4:8]) - val := bo.Uint32(eb[8:12]) - ifd.Children = append(ifd.Children, leaf( - fmt.Sprintf("tag 0x%04x %s", tag, tiffTag(tag)), "ifd_entry", eoff, 12, - fmt.Sprintf("type=%s count=%d value=%d", tiffType(typ), cnt, val))) - } - root.Children = append(root.Children, ifd) - nb, err := d.Range(ifdOff+2+count*12, 4) - if err != nil || len(nb) < 4 { - break - } - ifdOff = int64(bo.Uint32(nb)) - } - return root, nil -} - -func tiffType(t uint16) string { - names := map[uint16]string{1: "BYTE", 2: "ASCII", 3: "SHORT", 4: "LONG", 5: "RATIONAL", 6: "SBYTE", 7: "UNDEFINED", 8: "SSHORT", 9: "SLONG", 10: "SRATIONAL", 11: "FLOAT", 12: "DOUBLE"} - if n, ok := names[t]; ok { - return n - } - return fmt.Sprintf("type%d", t) -} - -func tiffTag(t uint16) string { - names := map[uint16]string{ - 256: "ImageWidth", 257: "ImageLength", 258: "BitsPerSample", 259: "Compression", - 262: "PhotometricInterpretation", 273: "StripOffsets", 277: "SamplesPerPixel", - 278: "RowsPerStrip", 279: "StripByteCounts", 282: "XResolution", 283: "YResolution", - 296: "ResolutionUnit", 305: "Software", 306: "DateTime", 315: "Artist", - 271: "Make", 272: "Model", 274: "Orientation", 34665: "ExifIFD", 34853: "GPSIFD", - } - if n, ok := names[t]; ok { - return n - } - return "" -} - -// ---------- pcap / pcapng ---------- - -// ParsePCAP decodes a classic libpcap global header and walks packet records. -func ParsePCAP(d *document.Document) (*Field, error) { - size := d.Size() - hdr, err := d.Range(0, min64(24, size)) - if err != nil || len(hdr) < 24 { - return nil, errors.New("pcap too short") - } - magic := binary.LittleEndian.Uint32(hdr[0:4]) - var bo binary.ByteOrder - nano := false - switch magic { - case 0xa1b2c3d4: - bo = binary.LittleEndian - case 0xd4c3b2a1: - bo = binary.BigEndian - case 0xa1b23c4d: - bo, nano = binary.LittleEndian, true - case 0x4d3cb2a1: - bo, nano = binary.BigEndian, true - default: - return nil, errors.New("not pcap") - } - res := "microsecond" - if nano { - res = "nanosecond" - } - linktype := bo.Uint32(hdr[20:24]) - root := &Field{ - Name: "PCAP", Type: "container", Offset: 0, Length: size, - Value: fmt.Sprintf("%s, %s", res, pcapLinkType(linktype)), - Children: []*Field{ - leaf("magic", "u32", 0, 4, fmt.Sprintf("0x%08x (%s)", magic, res)), - leaf("version", "u16", 4, 4, fmt.Sprintf("%d.%d", bo.Uint16(hdr[4:6]), bo.Uint16(hdr[6:8]))), - leaf("thiszone", "i32", 8, 4, fmt.Sprintf("%d", int32(bo.Uint32(hdr[8:12])))), - leaf("sigfigs", "u32", 12, 4, fmt.Sprintf("%d", bo.Uint32(hdr[12:16]))), - leaf("snaplen", "u32", 16, 4, fmt.Sprintf("%d", bo.Uint32(hdr[16:20]))), - leaf("linktype", "u32", 20, 4, fmt.Sprintf("%d (%s)", linktype, pcapLinkType(linktype))), - }, - } - off := int64(24) - for n := 0; off+16 <= size && n < 100000; n++ { - rb, err := d.Range(off, 16) - if err != nil { - break - } - ts := bo.Uint32(rb[0:4]) - us := bo.Uint32(rb[4:8]) - inclLen := int64(bo.Uint32(rb[8:12])) - origLen := bo.Uint32(rb[12:16]) - root.Children = append(root.Children, leaf( - fmt.Sprintf("packet[%d]", n), "record", off, 16+inclLen, - fmt.Sprintf("ts=%d.%06d incl=%d orig=%d", ts, us, inclLen, origLen))) - off += 16 + inclLen - } - return root, nil -} - -// ParsePCAPNG decodes the pcapng block structure (section header + block walk). -func ParsePCAPNG(d *document.Document) (*Field, error) { - size := d.Size() - hb, err := d.Range(0, min64(12, size)) - if err != nil || len(hb) < 12 { - return nil, errors.New("pcapng too short") - } - if binary.LittleEndian.Uint32(hb[0:4]) != 0x0a0d0d0a { - return nil, errors.New("not pcapng") - } - // Byte-order magic lives in the SHB body at offset 8. - var bo binary.ByteOrder - switch binary.LittleEndian.Uint32(hb[8:12]) { - case 0x1a2b3c4d: - bo = binary.LittleEndian - case 0x4d3c2b1a: - bo = binary.BigEndian - default: - return nil, errors.New("bad pcapng byte-order magic") - } - root := &Field{Name: "PCAPNG", Type: "container", Offset: 0, Length: size} - off := int64(0) - for n := 0; off+12 <= size && n < 100000; n++ { - bh, err := d.Range(off, 12) - if err != nil { - break - } - btype := bo.Uint32(bh[0:4]) - blen := int64(bo.Uint32(bh[4:8])) - if blen < 12 || off+blen > size { - break - } - root.Children = append(root.Children, leaf( - fmt.Sprintf("block[%d] %s", n, pcapngBlock(btype)), "block", off, blen, - fmt.Sprintf("type=0x%08x length=%d", btype, blen))) - off += blen - } - return root, nil -} - -func pcapngBlock(t uint32) string { - switch t { - case 0x0a0d0d0a: - return "SectionHeader" - case 0x00000001: - return "InterfaceDescription" - case 0x00000003: - return "SimplePacket" - case 0x00000004: - return "NameResolution" - case 0x00000005: - return "InterfaceStatistics" - case 0x00000006: - return "EnhancedPacket" - default: - return "block" - } -} - -func pcapLinkType(t uint32) string { - names := map[uint32]string{0: "NULL", 1: "ETHERNET", 6: "TOKEN_RING", 9: "PPP", 101: "RAW", 105: "IEEE802_11", 113: "LINUX_SLL", 127: "IEEE802_11_RADIOTAP", 228: "IPV4", 229: "IPV6", 276: "LINUX_SLL2"} - if n, ok := names[t]; ok { - return n - } - return fmt.Sprintf("linktype%d", t) -} - -// ---------- OLE / Compound File Binary (MS-CFB) ---------- - -var cfbMagic = []byte{0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1} - -// ParseCFB decodes the compound-file header and lists the entries in the first -// directory sector (legacy .doc/.xls/.ppt/.msi container). -func ParseCFB(d *document.Document) (*Field, error) { - size := d.Size() - hdr, err := d.Range(0, min64(512, size)) - if err != nil || len(hdr) < 76 { - return nil, errors.New("cfb too short") - } - if string(hdr[0:8]) != string(cfbMagic) { - return nil, errors.New("not a compound file") - } - le := binary.LittleEndian - major := le.Uint16(hdr[26:28]) - sectorShift := le.Uint16(hdr[30:32]) - sectorSize := int64(1) << sectorShift - firstDir := le.Uint32(hdr[48:52]) - root := &Field{ - Name: "CFB/OLE", Type: "container", Offset: 0, Length: size, - Value: fmt.Sprintf("v%d.%d sector=%d", major, le.Uint16(hdr[24:26]), sectorSize), - Children: []*Field{ - leaf("signature", "bytes", 0, 8, "D0 CF 11 E0 A1 B1 1A E1"), - leaf("minor_version", "u16le", 24, 2, fmt.Sprintf("%d", le.Uint16(hdr[24:26]))), - leaf("major_version", "u16le", 26, 2, fmt.Sprintf("%d", major)), - leaf("byte_order", "u16le", 28, 2, fmt.Sprintf("0x%04x", le.Uint16(hdr[28:30]))), - leaf("sector_shift", "u16le", 30, 2, fmt.Sprintf("%d (%d bytes)", sectorShift, sectorSize)), - leaf("mini_sector_shift", "u16le", 32, 2, fmt.Sprintf("%d (%d bytes)", le.Uint16(hdr[32:34]), int64(1)< size { - return nil - } - dir := &Field{Name: "directory", Type: "directory", Offset: off} - per := sectorSize / 128 - count := int64(0) - for i := int64(0); i < per; i++ { - eoff := off + i*128 - eb, err := d.Range(eoff, 128) - if err != nil || len(eb) < 128 { - break - } - nameLen := int64(binary.LittleEndian.Uint16(eb[64:66])) - objType := eb[66] - if objType == 0 { // unallocated entry - continue - } - name := "" - if nameLen >= 2 && nameLen <= 64 { - name = decodeUTF16LE(eb[0 : nameLen-2]) - } - streamSize := binary.LittleEndian.Uint64(eb[120:128]) - dir.Children = append(dir.Children, leaf( - cfbEntryLabel(name, count), "dir_entry", eoff, 128, - fmt.Sprintf("%s size=%d", cfbObjType(objType), streamSize))) - count++ - } - dir.Length = sectorSize - dir.Value = fmt.Sprintf("%d entries", count) - if count == 0 { - return nil - } - return dir -} - -func cfbEntryLabel(name string, i int64) string { - if name == "" { - return fmt.Sprintf("entry[%d]", i) - } - // Strip the control prefix Office uses on some stream names for display. - clean := strings.Map(func(r rune) rune { - if r < 0x20 { - return -1 - } - return r - }, name) - if clean == "" { - clean = name - } - return fmt.Sprintf("entry[%d] %s", i, clean) -} - -func cfbObjType(t byte) string { - switch t { - case 1: - return "storage" - case 2: - return "stream" - case 5: - return "root" - default: - return fmt.Sprintf("type%d", t) - } -} - -// ---------- shared helpers ---------- - -func readCStringField(d *document.Document, off, size int64) (string, int64) { - var sb strings.Builder - n := int64(0) - for off+n < size && n < 4096 { - b, err := d.At(off + n) - if err != nil { - break - } - n++ - if b == 0 { - break - } - sb.WriteByte(b) - } - return sb.String(), n -} - -func allZero(b []byte) bool { - for _, x := range b { - if x != 0 { - return false - } - } - return true -} - -func trimZeroStr(b []byte) string { - return strings.TrimRight(string(b), "\x00 ") -} - -// octal parses a space/NUL-padded octal ASCII field (tar number encoding). -func octal(b []byte) int64 { - s := strings.TrimSpace(strings.Trim(string(b), "\x00")) - if s == "" { - return 0 - } - v, err := strconv.ParseInt(s, 8, 64) - if err != nil { - return 0 - } - return v -} - -func roundUp(n, to int64) int64 { - if n <= 0 { - return 0 - } - return ((n + to - 1) / to) * to -} diff --git a/internal/leviathan/parsers/macho.go b/internal/leviathan/parsers/macho.go deleted file mode 100644 index db760a1..0000000 --- a/internal/leviathan/parsers/macho.go +++ /dev/null @@ -1,408 +0,0 @@ -package parsers - -import ( - "encoding/binary" - "errors" - "fmt" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// Mach-O magic values (32/64, LE/BE) plus fat (universal) magic. -const ( - machoMagic32 = 0xfeedface - machoCigam32 = 0xcefaedfe - machoMagic64 = 0xfeedfacf - machoCigam64 = 0xcffaedfe - machoFatMagic = 0xcafebabe // also Java .class; disambiguate by position + nfat_arch - machoFatCigam = 0xbebafeca - machoFatMagic64 = 0xcafebabf - machoFatCigam64 = 0xbfbafeca -) - -// IsMachO reports whether the given prefix looks like a Mach-O header. -// Fat magic 0xcafebabe is shared with Java class files; we accept it -// only when nfat_arch is plausible and the first arch's cputype points -// somewhere in-document. -func IsMachO(head []byte, size int64) bool { - if len(head) < 8 { - return false - } - m := binary.LittleEndian.Uint32(head[:4]) - switch m { - case machoMagic32, machoMagic64, machoCigam32, machoCigam64: - return true - } - mBE := binary.BigEndian.Uint32(head[:4]) - switch mBE { - case machoFatMagic, machoFatMagic64: - // nfat_arch is next BE u32; plausibly small. - n := binary.BigEndian.Uint32(head[4:8]) - return n > 0 && n < 64 && size >= 8+int64(n)*20 - } - _ = mBE - return false -} - -// ParseMachO returns a field tree for a Mach-O (fat or thin) file. -func ParseMachO(d *document.Document) (*Field, error) { - head, err := d.Range(0, min64(8, d.Size())) - if err != nil { - return nil, err - } - if len(head) < 8 { - return nil, errors.New("file too small for Mach-O") - } - mLE := binary.LittleEndian.Uint32(head[:4]) - mBE := binary.BigEndian.Uint32(head[:4]) - - switch mBE { - case machoFatMagic, machoFatMagic64: - return parseMachOFat(d, mBE == machoFatMagic64) - } - - // Thin header. - switch mLE { - case machoMagic32: - return parseMachOThin(d, 0, binary.LittleEndian, false) - case machoMagic64: - return parseMachOThin(d, 0, binary.LittleEndian, true) - case machoCigam32: - return parseMachOThin(d, 0, binary.BigEndian, false) - case machoCigam64: - return parseMachOThin(d, 0, binary.BigEndian, true) - } - return nil, errors.New("not a Mach-O file") -} - -func parseMachOFat(d *document.Document, is64 bool) (*Field, error) { - root := &Field{Name: "Mach-O fat", Type: "container", Offset: 0} - hdr, err := d.Range(0, 8) - if err != nil { - return nil, err - } - n := int64(binary.BigEndian.Uint32(hdr[4:8])) - root.Children = append(root.Children, &Field{ - Name: "magic", Type: "u32", Offset: 0, Length: 4, - Value: fmt.Sprintf("0x%08x", binary.BigEndian.Uint32(hdr[:4])), - }) - root.Children = append(root.Children, &Field{ - Name: "nfat_arch", Type: "u32", Offset: 4, Length: 4, - Value: fmt.Sprintf("%d", n), - }) - - archSize := int64(20) - if is64 { - archSize = 32 - } - off := int64(8) - for i := int64(0); i < n && off+archSize <= d.Size(); i++ { - arch, err := d.Range(off, archSize) - if err != nil { - break - } - cpuType := binary.BigEndian.Uint32(arch[0:4]) - cpuSub := binary.BigEndian.Uint32(arch[4:8]) - var archOff, archLen int64 - if is64 { - archOff = int64(binary.BigEndian.Uint64(arch[8:16])) - archLen = int64(binary.BigEndian.Uint64(arch[16:24])) - } else { - archOff = int64(binary.BigEndian.Uint32(arch[8:12])) - archLen = int64(binary.BigEndian.Uint32(arch[12:16])) - } - node := &Field{ - Name: fmt.Sprintf("arch[%d]", i), - Type: "fat_arch", - Offset: off, - Length: archSize, - Value: fmt.Sprintf("cpu=%s sub=0x%x offset=%d size=%d", cpuTypeName(cpuType), cpuSub, archOff, archLen), - } - // Parse the nested Mach-O at archOff if it fits. - if archOff > 0 && archOff+8 <= d.Size() { - if nested, err := parseMachOInline(d, archOff); err == nil { - node.Children = append(node.Children, nested) - } - } - root.Children = append(root.Children, node) - off += archSize - } - return root, nil -} - -// parseMachOInline is like ParseMachO but anchored at a given offset -// (used for each arch inside a fat binary). -func parseMachOInline(d *document.Document, at int64) (*Field, error) { - head, err := d.Range(at, 8) - if err != nil || len(head) < 8 { - return nil, errors.New("short arch payload") - } - mLE := binary.LittleEndian.Uint32(head[:4]) - switch mLE { - case machoMagic32: - return parseMachOThin(d, at, binary.LittleEndian, false) - case machoMagic64: - return parseMachOThin(d, at, binary.LittleEndian, true) - case machoCigam32: - return parseMachOThin(d, at, binary.BigEndian, false) - case machoCigam64: - return parseMachOThin(d, at, binary.BigEndian, true) - } - return nil, errors.New("arch payload is not Mach-O") -} - -func parseMachOThin(d *document.Document, base int64, bo binary.ByteOrder, is64 bool) (*Field, error) { - headerLen := int64(28) - if is64 { - headerLen = 32 - } - if base+headerLen > d.Size() { - return nil, errors.New("truncated Mach-O header") - } - hdr, err := d.Range(base, headerLen) - if err != nil { - return nil, err - } - - magic := bo.Uint32(hdr[0:4]) - cpuType := bo.Uint32(hdr[4:8]) - cpuSub := bo.Uint32(hdr[8:12]) - fileType := bo.Uint32(hdr[12:16]) - ncmds := bo.Uint32(hdr[16:20]) - sizeOfCmds := bo.Uint32(hdr[20:24]) - flags := bo.Uint32(hdr[24:28]) - - name := "Mach-O (32)" - if is64 { - name = "Mach-O (64)" - } - root := &Field{Name: name, Type: "container", Offset: base, Length: headerLen + int64(sizeOfCmds)} - root.Children = append(root.Children, - &Field{Name: "magic", Type: "u32", Offset: base, Length: 4, Value: fmt.Sprintf("0x%08x", magic)}, - &Field{Name: "cputype", Type: "u32", Offset: base + 4, Length: 4, Value: cpuTypeName(cpuType)}, - &Field{Name: "cpusubtype", Type: "u32", Offset: base + 8, Length: 4, Value: fmt.Sprintf("0x%x", cpuSub)}, - &Field{Name: "filetype", Type: "u32", Offset: base + 12, Length: 4, Value: machoFileType(fileType)}, - &Field{Name: "ncmds", Type: "u32", Offset: base + 16, Length: 4, Value: fmt.Sprintf("%d", ncmds)}, - &Field{Name: "sizeofcmds", Type: "u32", Offset: base + 20, Length: 4, Value: fmt.Sprintf("%d", sizeOfCmds)}, - &Field{Name: "flags", Type: "u32", Offset: base + 24, Length: 4, Value: fmt.Sprintf("0x%08x", flags)}, - ) - - // Load commands. - cmdOff := base + headerLen - cmdEnd := cmdOff + int64(sizeOfCmds) - if cmdEnd > d.Size() { - cmdEnd = d.Size() - } - cmds := &Field{Name: "load_commands", Type: "list", Offset: cmdOff, Length: int64(sizeOfCmds)} - for i := uint32(0); i < ncmds && cmdOff+8 <= cmdEnd; i++ { - h, err := d.Range(cmdOff, 8) - if err != nil { - break - } - cmd := bo.Uint32(h[0:4]) - sz := int64(bo.Uint32(h[4:8])) - if sz < 8 || cmdOff+sz > cmdEnd { - break - } - buf, _ := d.Range(cmdOff, sz) - node := decodeLoadCommand(base, cmdOff, cmd, sz, buf, bo, is64) - cmds.Children = append(cmds.Children, node) - cmdOff += sz - } - root.Children = append(root.Children, cmds) - return root, nil -} - -func decodeLoadCommand(base, off int64, cmd uint32, sz int64, buf []byte, bo binary.ByteOrder, is64 bool) *Field { - name, mask := loadCommandName(cmd) - node := &Field{ - Name: name, Type: "load_command", Offset: off, Length: sz, - Value: fmt.Sprintf("cmd=0x%08x size=%d", cmd, sz), - } - if mask != 0 { - node.Note = "required" - } - - switch { - case (cmd&^0x80000000) == 0x19 && is64 && sz >= 72: - // LC_SEGMENT_64 - segName := cstring(buf[8:24]) - vmAddr := bo.Uint64(buf[24:32]) - vmSize := bo.Uint64(buf[32:40]) - fileOff := bo.Uint64(buf[40:48]) - fileSize := bo.Uint64(buf[48:56]) - nsects := bo.Uint32(buf[64:68]) - node.Value = fmt.Sprintf("name=%q vm=%#x+%#x file=%#x+%#x nsects=%d", - segName, vmAddr, vmSize, fileOff, fileSize, nsects) - sectOff := int64(72) - for i := uint32(0); i < nsects && sectOff+80 <= sz; i++ { - s := buf[sectOff:] - sn := cstring(s[0:16]) - seg := cstring(s[16:32]) - addr := bo.Uint64(s[32:40]) - size := bo.Uint64(s[40:48]) - foff := bo.Uint32(s[48:52]) - node.Children = append(node.Children, &Field{ - Name: sn, Type: "section64", Offset: off + sectOff, Length: 80, - Value: fmt.Sprintf("seg=%q addr=%#x size=%#x file=%#x", seg, addr, size, foff), - }) - sectOff += 80 - } - case cmd == 0x01 && sz >= 56: - // LC_SEGMENT (32) - segName := cstring(buf[8:24]) - vmAddr := bo.Uint32(buf[24:28]) - vmSize := bo.Uint32(buf[28:32]) - fileOff := bo.Uint32(buf[32:36]) - fileSize := bo.Uint32(buf[36:40]) - nsects := bo.Uint32(buf[48:52]) - node.Value = fmt.Sprintf("name=%q vm=%#x+%#x file=%#x+%#x nsects=%d", - segName, vmAddr, vmSize, fileOff, fileSize, nsects) - case cmd == 0x1b && sz >= 24: - // LC_UUID - u := buf[8:24] - node.Value = fmt.Sprintf("UUID=%x-%x-%x-%x-%x", u[0:4], u[4:6], u[6:8], u[8:10], u[10:16]) - case (cmd&^0x80000000) == 0x0c && sz > 24: - // LC_LOAD_DYLIB / similar - strOff := bo.Uint32(buf[8:12]) - if int(strOff) < len(buf) { - node.Value = fmt.Sprintf("lib=%q", cstring(buf[strOff:])) - } - case cmd == 0x0e && sz > 12: - // LC_LOAD_DYLINKER - strOff := bo.Uint32(buf[8:12]) - if int(strOff) < len(buf) { - node.Value = fmt.Sprintf("dylinker=%q", cstring(buf[strOff:])) - } - case cmd == 0x21 && sz >= 16: - // LC_ENCRYPTION_INFO / _64 - node.Value = "encryption info" - case (cmd &^ 0x80000000) == 0x22: - node.Value = "LC_DYLD_INFO" - case cmd == 0x25: - node.Value = "LC_VERSION_MIN_MACOSX" - case cmd == 0x28 || cmd == 0x29: - node.Value = "LC_MAIN / entry point" - } - return node -} - -func cstring(b []byte) string { - for i, c := range b { - if c == 0 { - return string(b[:i]) - } - } - return string(b) -} - -func cpuTypeName(c uint32) string { - switch c { - case 0x7: - return "x86" - case 0x1000007: - return "x86_64" - case 0xc: - return "arm" - case 0x100000c: - return "arm64" - case 0x12: - return "ppc" - case 0x1000012: - return "ppc64" - } - return fmt.Sprintf("0x%x", c) -} - -func machoFileType(t uint32) string { - switch t { - case 1: - return "OBJECT" - case 2: - return "EXECUTE" - case 3: - return "FVMLIB" - case 4: - return "CORE" - case 5: - return "PRELOAD" - case 6: - return "DYLIB" - case 7: - return "DYLINKER" - case 8: - return "BUNDLE" - case 9: - return "DYLIB_STUB" - case 10: - return "DSYM" - case 11: - return "KEXT_BUNDLE" - } - return fmt.Sprintf("%d", t) -} - -// loadCommandName returns a human name and whether the required bit is set. -func loadCommandName(cmd uint32) (string, uint32) { - req := cmd & 0x80000000 - v := cmd &^ 0x80000000 - names := map[uint32]string{ - 0x01: "LC_SEGMENT", - 0x02: "LC_SYMTAB", - 0x03: "LC_SYMSEG", - 0x04: "LC_THREAD", - 0x05: "LC_UNIXTHREAD", - 0x06: "LC_LOADFVMLIB", - 0x07: "LC_IDFVMLIB", - 0x08: "LC_IDENT", - 0x09: "LC_FVMFILE", - 0x0a: "LC_PREPAGE", - 0x0b: "LC_DYSYMTAB", - 0x0c: "LC_LOAD_DYLIB", - 0x0d: "LC_ID_DYLIB", - 0x0e: "LC_LOAD_DYLINKER", - 0x0f: "LC_ID_DYLINKER", - 0x10: "LC_PREBOUND_DYLIB", - 0x11: "LC_ROUTINES", - 0x12: "LC_SUB_FRAMEWORK", - 0x13: "LC_SUB_UMBRELLA", - 0x14: "LC_SUB_CLIENT", - 0x15: "LC_SUB_LIBRARY", - 0x16: "LC_TWOLEVEL_HINTS", - 0x17: "LC_PREBIND_CKSUM", - 0x18: "LC_LOAD_WEAK_DYLIB", - 0x19: "LC_SEGMENT_64", - 0x1a: "LC_ROUTINES_64", - 0x1b: "LC_UUID", - 0x1c: "LC_RPATH", - 0x1d: "LC_CODE_SIGNATURE", - 0x1e: "LC_SEGMENT_SPLIT_INFO", - 0x1f: "LC_REEXPORT_DYLIB", - 0x20: "LC_LAZY_LOAD_DYLIB", - 0x21: "LC_ENCRYPTION_INFO", - 0x22: "LC_DYLD_INFO", - 0x23: "LC_LOAD_UPWARD_DYLIB", - 0x24: "LC_VERSION_MIN_MACOSX", - 0x25: "LC_VERSION_MIN_IPHONEOS", - 0x26: "LC_FUNCTION_STARTS", - 0x27: "LC_DYLD_ENVIRONMENT", - 0x28: "LC_MAIN", - 0x29: "LC_DATA_IN_CODE", - 0x2a: "LC_SOURCE_VERSION", - 0x2b: "LC_DYLIB_CODE_SIGN_DRS", - 0x2c: "LC_ENCRYPTION_INFO_64", - 0x2d: "LC_LINKER_OPTION", - 0x2e: "LC_LINKER_OPTIMIZATION_HINT", - 0x2f: "LC_VERSION_MIN_TVOS", - 0x30: "LC_VERSION_MIN_WATCHOS", - 0x31: "LC_NOTE", - 0x32: "LC_BUILD_VERSION", - 0x33: "LC_DYLD_EXPORTS_TRIE", - 0x34: "LC_DYLD_CHAINED_FIXUPS", - 0x35: "LC_FILESET_ENTRY", - } - if n, ok := names[v]; ok { - return n, req - } - return fmt.Sprintf("LC_0x%x", v), req -} diff --git a/internal/leviathan/parsers/peimports.go b/internal/leviathan/parsers/peimports.go deleted file mode 100644 index 2db1f14..0000000 --- a/internal/leviathan/parsers/peimports.go +++ /dev/null @@ -1,150 +0,0 @@ -package parsers - -import ( - "encoding/binary" - "errors" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// ImportSym is one resolved PE import (a function name, or an ordinal). -type ImportSym struct { - DLL string `json:"dll"` - Func string `json:"func"` - Ordinal int `json:"ordinal"` - ByOrdinal bool `json:"byOrdinal"` -} - -// PEImports walks the import directory of a PE file and returns the ordered -// list of imported symbols. It reuses the same RVA mapping and header parsing -// as ParsePE but yields a clean typed result (for imphash, capability -// analysis, etc.) rather than a display tree. -func PEImports(d *document.Document) ([]ImportSym, error) { - sections, dataDirs, is64, err := peLayout(d) - if err != nil { - return nil, err - } - if len(dataDirs) < 2 || dataDirs[1].RVA == 0 { - return nil, errors.New("no import directory") - } - off, ok := peRVAToOffset(sections, dataDirs[1].RVA) - if !ok { - return nil, errors.New("import RVA unmapped") - } - var out []ImportSym - for i := 0; i < 1024; i++ { - at := int64(off) + int64(i*20) - buf, err := d.Range(at, 20) - if err != nil || len(buf) < 20 { - break - } - oft := binary.LittleEndian.Uint32(buf[0:4]) - nameRVA := binary.LittleEndian.Uint32(buf[12:16]) - firstThunk := binary.LittleEndian.Uint32(buf[16:20]) - if oft == 0 && nameRVA == 0 && firstThunk == 0 { - break - } - dll := "" - if nameOff, ok := peRVAToOffset(sections, nameRVA); ok { - dll = readCString(d, int64(nameOff), 256) - } - if dll == "" { - continue - } - out = append(out, peThunkSyms(d, sections, dll, oft, firstThunk, is64)...) - if len(out) > 100000 { - break - } - } - return out, nil -} - -func peThunkSyms(d *document.Document, sections []peSectionInfo, dll string, oft, firstThunk uint32, is64 bool) []ImportSym { - thunkRVA := oft - if thunkRVA == 0 { - thunkRVA = firstThunk - } - thunkOff, ok := peRVAToOffset(sections, thunkRVA) - if !ok { - return nil - } - width := int64(4) - ordinalMask := uint64(0x80000000) - if is64 { - width = 8 - ordinalMask = 0x8000000000000000 - } - var out []ImportSym - for i := 0; i < 8192; i++ { - at := int64(thunkOff) + int64(i)*width - buf, err := d.Range(at, width) - if err != nil || int64(len(buf)) < width { - break - } - var thunk uint64 - if is64 { - thunk = binary.LittleEndian.Uint64(buf) - } else { - thunk = uint64(binary.LittleEndian.Uint32(buf)) - } - if thunk == 0 { - break - } - if thunk&ordinalMask != 0 { - out = append(out, ImportSym{DLL: dll, Ordinal: int(thunk & 0xffff), ByOrdinal: true}) - } else if nameOff, ok := peRVAToOffset(sections, uint32(thunk)); ok { - name := readImportByName(d, int64(nameOff)) - if name != "" { - out = append(out, ImportSym{DLL: dll, Func: name}) - } - } - } - return out -} - -// peLayout extracts the section table, data directories and 32/64-bitness from -// a PE without building the display field tree. -func peLayout(d *document.Document) ([]peSectionInfo, []peDataDirectory, bool, error) { - hdr, err := d.Range(0, 64) - if err != nil || len(hdr) < 64 { - return nil, nil, false, errors.New("too short") - } - lfanew := int64(binary.LittleEndian.Uint32(hdr[0x3c:0x40])) - peHdr, err := d.Range(lfanew, 24) - if err != nil || len(peHdr) < 24 || string(peHdr[:4]) != "PE\x00\x00" { - return nil, nil, false, errors.New("no PE signature") - } - numSections := int(binary.LittleEndian.Uint16(peHdr[6:8])) - sizeOptHdr := int64(binary.LittleEndian.Uint16(peHdr[20:22])) - optOff := lfanew + 24 - - var dataDirs []peDataDirectory - is64 := false - if sizeOptHdr > 0 { - opt, err := d.Range(optOff, sizeOptHdr) - if err == nil && len(opt) >= 2 { - _, dirs := parsePEOptionalHeader(opt, optOff) - dataDirs = dirs - is64 = binary.LittleEndian.Uint16(opt[0:2]) == 0x20b - } - } - - sectOff := lfanew + 24 + sizeOptHdr - sections := make([]peSectionInfo, 0, numSections) - for i := 0; i < numSections; i++ { - sh, err := d.Range(sectOff, 40) - if err != nil || len(sh) < 40 { - break - } - sections = append(sections, peSectionInfo{ - Name: string(trimZero(sh[0:8])), - VirtualSize: binary.LittleEndian.Uint32(sh[8:12]), - VirtualAddress: binary.LittleEndian.Uint32(sh[12:16]), - RawSize: binary.LittleEndian.Uint32(sh[16:20]), - RawOffset: binary.LittleEndian.Uint32(sh[20:24]), - Characteristics: binary.LittleEndian.Uint32(sh[36:40]), - }) - sectOff += 40 - } - return sections, dataDirs, is64, nil -} diff --git a/internal/leviathan/patch/script.go b/internal/leviathan/patch/script.go deleted file mode 100644 index f8ba3fd..0000000 --- a/internal/leviathan/patch/script.go +++ /dev/null @@ -1,232 +0,0 @@ -// Package patch implements the simple Leviathan patch script language. -// -// set OFFSET HEX... — write hex bytes at OFFSET -// fill START LEN BYTE — fill a range with a single byte -// xor START LEN KEY — XOR a range with an ASCII or hex: key -// copy SRC LEN DST — copy bytes from SRC to DST -// # comments and blank lines are allowed. -package patch - -import ( - "encoding/hex" - "errors" - "fmt" - "strconv" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// Step is one parsed instruction. -type Step struct { - Op string - Args []string - Line int - Source string -} - -// Parse parses the script into steps. -func Parse(src string) ([]Step, error) { - out := []Step{} - for i, raw := range strings.Split(src, "\n") { - line := strings.TrimSpace(raw) - if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") { - continue - } - fields := strings.Fields(line) - op := strings.ToLower(fields[0]) - out = append(out, Step{ - Op: op, Args: fields[1:], Line: i + 1, Source: raw, - }) - } - return out, nil -} - -// Plan validates all steps against document size without applying. -// Returns per-step info or an error on validation failure. -func Plan(d *document.Document, steps []Step) ([]map[string]any, error) { - size := d.Size() - info := make([]map[string]any, 0, len(steps)) - for _, s := range steps { - switch s.Op { - case "set": - if len(s.Args) < 2 { - return info, fmt.Errorf("line %d: set needs OFFSET and HEX", s.Line) - } - off, err := parseOffset(s.Args[0]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - b, err := parseHexRest(s.Args[1:]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - if !validRange(size, off, int64(len(b))) { - return info, fmt.Errorf("line %d: out of range", s.Line) - } - info = append(info, map[string]any{ - "op": "set", "line": s.Line, "offset": off, "length": len(b), - }) - case "fill": - if len(s.Args) != 3 { - return info, fmt.Errorf("line %d: fill needs START LEN BYTE", s.Line) - } - off, err := parseOffset(s.Args[0]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - n, err := parseOffset(s.Args[1]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - by, err := parseHexByte(s.Args[2]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - if !validRange(size, off, n) { - return info, fmt.Errorf("line %d: out of range", s.Line) - } - info = append(info, map[string]any{ - "op": "fill", "line": s.Line, "offset": off, "length": n, "byte": by, - }) - case "xor": - if len(s.Args) != 3 { - return info, fmt.Errorf("line %d: xor needs START LEN KEY", s.Line) - } - off, err := parseOffset(s.Args[0]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - n, err := parseOffset(s.Args[1]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - key, err := parseKey(s.Args[2]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - if !validRange(size, off, n) { - return info, fmt.Errorf("line %d: out of range", s.Line) - } - info = append(info, map[string]any{ - "op": "xor", "line": s.Line, "offset": off, "length": n, "keyLen": len(key), - }) - case "copy": - if len(s.Args) != 3 { - return info, fmt.Errorf("line %d: copy needs SRC LEN DST", s.Line) - } - src, err := parseOffset(s.Args[0]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - n, err := parseOffset(s.Args[1]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - dst, err := parseOffset(s.Args[2]) - if err != nil { - return info, fmt.Errorf("line %d: %v", s.Line, err) - } - if !validRange(size, src, n) || !validRange(size, dst, n) { - return info, fmt.Errorf("line %d: out of range", s.Line) - } - info = append(info, map[string]any{ - "op": "copy", "line": s.Line, "src": src, "dst": dst, "length": n, - }) - default: - return info, fmt.Errorf("line %d: unknown op %q", s.Line, s.Op) - } - } - return info, nil -} - -// Apply runs the script against the document. Callers may want to -// first snapshot a checkpoint so apply can be reverted. -func Apply(d *document.Document, steps []Step) error { - if _, err := Plan(d, steps); err != nil { - return err - } - for _, s := range steps { - switch s.Op { - case "set": - off, _ := parseOffset(s.Args[0]) - b, _ := parseHexRest(s.Args[1:]) - for i, bb := range b { - if _, err := d.Set(off+int64(i), bb); err != nil { - return err - } - } - case "fill": - off, _ := parseOffset(s.Args[0]) - n, _ := parseOffset(s.Args[1]) - by, _ := parseHexByte(s.Args[2]) - if err := d.Fill(off, n, by); err != nil { - return err - } - case "xor": - off, _ := parseOffset(s.Args[0]) - n, _ := parseOffset(s.Args[1]) - key, _ := parseKey(s.Args[2]) - if err := d.XOR(off, n, key); err != nil { - return err - } - case "copy": - src, _ := parseOffset(s.Args[0]) - n, _ := parseOffset(s.Args[1]) - dst, _ := parseOffset(s.Args[2]) - if err := d.Copy(src, n, dst); err != nil { - return err - } - } - } - return nil -} - -func validRange(size, start, length int64) bool { - return start >= 0 && start <= size && length >= 0 && length <= size-start -} - -func parseOffset(s string) (int64, error) { - s = strings.TrimSpace(s) - if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { - v, err := strconv.ParseInt(s[2:], 16, 64) - return v, err - } - return strconv.ParseInt(s, 10, 64) -} - -func parseHexRest(args []string) ([]byte, error) { - joined := strings.Join(args, "") - joined = strings.ReplaceAll(joined, " ", "") - joined = strings.ReplaceAll(joined, ",", "") - return hex.DecodeString(joined) -} - -func parseHexByte(s string) (byte, error) { - if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") { - v, err := strconv.ParseUint(s[2:], 16, 8) - return byte(v), err - } - b, err := hex.DecodeString(s) - if err != nil { - v, err := strconv.ParseUint(s, 10, 8) - if err != nil { - return 0, errors.New("not a byte") - } - return byte(v), nil - } - if len(b) != 1 { - return 0, errors.New("expected one byte") - } - return b[0], nil -} - -func parseKey(s string) ([]byte, error) { - if strings.HasPrefix(s, "hex:") { - return hex.DecodeString(s[4:]) - } - if s == "" { - return nil, errors.New("empty key") - } - return []byte(s), nil -} diff --git a/internal/leviathan/pdf/filters.go b/internal/leviathan/pdf/filters.go deleted file mode 100644 index 67aca73..0000000 --- a/internal/leviathan/pdf/filters.go +++ /dev/null @@ -1,411 +0,0 @@ -package pdf - -import ( - "bytes" - "errors" - "fmt" - - "github.com/andydixon/link/internal/leviathan/transforms" -) - -const MaxDecodedStreamOutput = 64 << 20 - -// ApplyFilter decodes one PDF stream filter. When a predictor is in use -// it must be applied separately after this function returns raw filter -// output; see ApplyPredictor. -func ApplyFilter(name string, in []byte) ([]byte, error) { - switch name { - case "FlateDecode", "Fl": - if out, err := transforms.InflateZlib(in); err == nil { - return out, nil - } - return transforms.InflateRaw(in) - case "ASCIIHexDecode", "AHx": - return decodeASCIIHex(in) - case "ASCII85Decode", "A85": - return decodeASCII85(in) - case "RunLengthDecode", "RL": - return decodeRunLength(in) - case "LZWDecode", "LZW": - return decodeLZW(in) - case "CCITTFaxDecode", "CCF": - return nil, errors.New("CCITTFaxDecode not supported (returns raw bytes)") - case "JBIG2Decode": - return nil, errors.New("JBIG2Decode not supported") - case "DCTDecode", "DCT": - // JPEG payload — return as-is, caller can save as .jpg. - return in, nil - case "JPXDecode": - return in, nil - case "Crypt": - return nil, errors.New("Crypt filter not supported") - } - return nil, fmt.Errorf("unknown filter %q", name) -} - -// ApplyPredictor undoes the PNG-family predictor used with Flate/LZW. If -// predictor <= 1 the data is returned unchanged. -func ApplyPredictor(in []byte, predictor, columns, colors, bits int) ([]byte, error) { - if predictor <= 1 { - return in, nil - } - if len(in) > MaxDecodedStreamOutput { - return nil, fmt.Errorf("predictor input exceeds %d byte limit", MaxDecodedStreamOutput) - } - bytesPerPixel, rowLen, err := predictorGeometry(columns, colors, bits) - if err != nil { - return nil, err - } - - switch { - case predictor == 2: - // TIFF predictor — apply left delta per sample. - out := make([]byte, len(in)) - copy(out, in) - step := bytesPerPixel - for i := step; i < len(out); i++ { - if i%rowLen < step { - continue - } - out[i] = out[i] + out[i-step] - } - return out, nil - case predictor >= 10 && predictor <= 15: - // PNG predictors — each row is prefixed with a filter-type byte. - stride := rowLen + 1 - out := make([]byte, 0, len(in)) - var prev []byte - for i := 0; i < len(in); i += stride { - if i+stride > len(in) { - break - } - filterType := in[i] - row := make([]byte, rowLen) - copy(row, in[i+1:i+stride]) - switch filterType { - case 0: // None - case 1: // Sub - for j := bytesPerPixel; j < rowLen; j++ { - row[j] += row[j-bytesPerPixel] - } - case 2: // Up - if prev != nil { - for j := 0; j < rowLen; j++ { - row[j] += prev[j] - } - } - case 3: // Average - for j := 0; j < rowLen; j++ { - left := byte(0) - if j >= bytesPerPixel { - left = row[j-bytesPerPixel] - } - up := byte(0) - if prev != nil { - up = prev[j] - } - row[j] += byte((int(left) + int(up)) / 2) - } - case 4: // Paeth - for j := 0; j < rowLen; j++ { - a := 0 - if j >= bytesPerPixel { - a = int(row[j-bytesPerPixel]) - } - b := 0 - if prev != nil { - b = int(prev[j]) - } - c := 0 - if prev != nil && j >= bytesPerPixel { - c = int(prev[j-bytesPerPixel]) - } - p := a + b - c - pa := abs(p - a) - pb := abs(p - b) - pc := abs(p - c) - pred := 0 - switch { - case pa <= pb && pa <= pc: - pred = a - case pb <= pc: - pred = b - default: - pred = c - } - row[j] += byte(pred) - } - default: - return nil, fmt.Errorf("PNG predictor: invalid filter type %d", filterType) - } - out = append(out, row...) - prev = row - } - return out, nil - } - return nil, fmt.Errorf("predictor %d not supported", predictor) -} - -func predictorGeometry(columns, colors, bits int) (int, int, error) { - if columns <= 0 || colors <= 0 { - return 0, 0, errors.New("predictor columns and colors must be positive") - } - switch bits { - case 1, 2, 4, 8, 16: - default: - return 0, 0, fmt.Errorf("predictor bits per component %d is invalid", bits) - } - pixelBits := int64(colors) * int64(bits) - rowBits := int64(columns) * pixelBits - if pixelBits <= 0 || rowBits <= 0 || rowBits > int64(MaxDecodedStreamOutput)*8 { - return 0, 0, errors.New("predictor dimensions exceed resource limits") - } - return int((pixelBits + 7) / 8), int((rowBits + 7) / 8), nil -} - -func ensureDecodedSize(current, additional int) error { - if additional < 0 || current > MaxDecodedStreamOutput-additional { - return fmt.Errorf("decoded stream exceeds %d byte limit", MaxDecodedStreamOutput) - } - return nil -} - -func abs(x int) int { - if x < 0 { - return -x - } - return x -} - -// --------------------------------------------------------------------------- -// ASCIIHexDecode -// --------------------------------------------------------------------------- - -func decodeASCIIHex(in []byte) ([]byte, error) { - out := make([]byte, 0, len(in)/2) - hi := -1 - for _, b := range in { - switch { - case b >= '0' && b <= '9': - b = b - '0' - case b >= 'a' && b <= 'f': - b = b - 'a' + 10 - case b >= 'A' && b <= 'F': - b = b - 'A' + 10 - case b == '>': - if hi >= 0 { - out = append(out, byte(hi)<<4) - } - return out, nil - case isPDFWhitespace(b): - continue - default: - continue - } - if hi < 0 { - hi = int(b) - } else { - out = append(out, byte(hi)<<4|b) - hi = -1 - } - } - if hi >= 0 { - out = append(out, byte(hi)<<4) - } - return out, nil -} - -func isPDFWhitespace(b byte) bool { - return b == 0 || b == '\t' || b == '\n' || b == '\f' || b == '\r' || b == ' ' -} - -// --------------------------------------------------------------------------- -// ASCII85Decode -// --------------------------------------------------------------------------- - -func decodeASCII85(in []byte) ([]byte, error) { - // Strip optional <~ ... ~> framing and whitespace. - start := 0 - if bytes.HasPrefix(in, []byte("<~")) { - start = 2 - } - out := make([]byte, 0, len(in)) - var group [5]byte - n := 0 - for i := start; i < len(in); i++ { - c := in[i] - if isPDFWhitespace(c) { - continue - } - if c == '~' { - break - } - if c == 'z' && n == 0 { - if err := ensureDecodedSize(len(out), 4); err != nil { - return nil, err - } - out = append(out, 0, 0, 0, 0) - continue - } - if c < '!' || c > 'u' { - return nil, fmt.Errorf("ASCII85: invalid byte 0x%02x", c) - } - group[n] = c - '!' - n++ - if n == 5 { - v := uint32(group[0])*85*85*85*85 + - uint32(group[1])*85*85*85 + - uint32(group[2])*85*85 + - uint32(group[3])*85 + - uint32(group[4]) - if err := ensureDecodedSize(len(out), 4); err != nil { - return nil, err - } - out = append(out, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) - n = 0 - } - } - if n > 0 { - for i := n; i < 5; i++ { - group[i] = 84 - } - v := uint32(group[0])*85*85*85*85 + - uint32(group[1])*85*85*85 + - uint32(group[2])*85*85 + - uint32(group[3])*85 + - uint32(group[4]) - full := []byte{byte(v >> 24), byte(v >> 16), byte(v >> 8), byte(v)} - if err := ensureDecodedSize(len(out), n-1); err != nil { - return nil, err - } - out = append(out, full[:n-1]...) - } - return out, nil -} - -// --------------------------------------------------------------------------- -// RunLengthDecode -// --------------------------------------------------------------------------- - -func decodeRunLength(in []byte) ([]byte, error) { - out := make([]byte, 0, len(in)) - i := 0 - for i < len(in) { - b := in[i] - i++ - switch { - case b == 128: - return out, nil - case b < 128: - n := int(b) + 1 - if i+n > len(in) { - return nil, errors.New("RunLength: truncated literal") - } - if err := ensureDecodedSize(len(out), n); err != nil { - return nil, err - } - out = append(out, in[i:i+n]...) - i += n - default: - n := 257 - int(b) - if i >= len(in) { - return nil, errors.New("RunLength: truncated run") - } - if err := ensureDecodedSize(len(out), n); err != nil { - return nil, err - } - for k := 0; k < n; k++ { - out = append(out, in[i]) - } - i++ - } - } - return out, nil -} - -// --------------------------------------------------------------------------- -// LZWDecode (variable-width, early-change = 1) -// --------------------------------------------------------------------------- - -func decodeLZW(in []byte) ([]byte, error) { - const ( - clearCode = 256 - eodCode = 257 - ) - reader := &bitReader{data: in} - table := make([][]byte, 258) - for i := 0; i < 256; i++ { - table[i] = []byte{byte(i)} - } - codeWidth := 9 - out := []byte{} - prev := []byte(nil) - for { - c, err := reader.read(codeWidth) - if err != nil { - return out, nil - } - if c == eodCode { - return out, nil - } - if c == clearCode { - table = table[:258] - codeWidth = 9 - prev = nil - continue - } - var entry []byte - switch { - case c < len(table): - entry = table[c] - case c == len(table) && prev != nil: - entry = append([]byte{}, prev...) - entry = append(entry, prev[0]) - default: - return out, errors.New("LZW: invalid code") - } - if err := ensureDecodedSize(len(out), len(entry)); err != nil { - return nil, err - } - out = append(out, entry...) - if prev != nil { - ne := append([]byte{}, prev...) - ne = append(ne, entry[0]) - table = append(table, ne) - // PDF LZW uses "early change": widen one code before full. - switch len(table) { - case 511, 1023, 2047: - codeWidth++ - case 4096: - // Implicit CLEAR expected; some encoders omit it. - table = table[:258] - codeWidth = 9 - prev = nil - continue - } - } - prev = entry - } -} - -type bitReader struct { - data []byte - bi int // bit offset into data -} - -func (r *bitReader) read(n int) (int, error) { - if n <= 0 || n > 32 { - return 0, errors.New("bitReader: bad width") - } - v := 0 - for i := 0; i < n; i++ { - byteIdx := r.bi / 8 - if byteIdx >= len(r.data) { - return 0, errors.New("bitReader: EOF") - } - bit := (r.data[byteIdx] >> uint(7-(r.bi%8))) & 1 - v = (v << 1) | int(bit) - r.bi++ - } - return v, nil -} diff --git a/internal/leviathan/pdf/pdf.go b/internal/leviathan/pdf/pdf.go deleted file mode 100644 index 76d6860..0000000 --- a/internal/leviathan/pdf/pdf.go +++ /dev/null @@ -1,352 +0,0 @@ -// Package pdf provides PDF surgery: object enumeration, full stream -// filter chain, cross-reference parsing (classical tables and xref -// streams), compressed object-stream decoding, and surfacing of hostile -// or otherwise-interesting constructs (JavaScript, embedded files, URI -// and launch actions, XFA, OpenAction triggers). -// -// It is not a rendering engine — it will not produce pixels. It is a -// structural tool for humans and detection pipelines. -package pdf - -import ( - "bytes" - "errors" - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -const maxPDFParseBytes = 128 << 20 - -// Object is one PDF indirect object. -type Object struct { - Num int64 - Gen int64 - Offset int64 - Dict string - HasStream bool - StreamOff int64 - StreamLen int64 - Filters []string - Type string - Subtype string - Source string // "direct" or "objstm" - Container int64 // when Source == "objstm", the ObjStm number -} - -// Parse returns a summary of the PDF structure, including cross- -// reference entries, an object list (direct + recovered from ObjStms), -// surfaced suspicious constructs, and top-level flags. -func Parse(d *document.Document) (map[string]any, error) { - size := d.Size() - head, _ := d.Range(0, 16) - if len(head) < 5 || string(head[:5]) != "%PDF-" { - return nil, errors.New("not a PDF header") - } - if size > maxPDFParseBytes { - return nil, fmt.Errorf("pdf parse skipped: file > %d MiB", maxPDFParseBytes>>20) - } - version := string(head[5:8]) - - whole, err := d.Range(0, size) - if err != nil { - return nil, err - } - - objs := scanIndirectObjects(whole) - - // Try xref first; fall back gracefully if it isn't parseable. - xref, xrefMode, xrefErr := ParseXRef(d, whole) - xrefNote := "" - if xrefErr != nil { - xrefNote = xrefErr.Error() - } - - // Decode any ObjStm streams we find and append recovered children. - recovered := recoverObjectStreams(whole, d, objs) - objs = append(objs, recovered...) - - // Surface flags. - surfaced := Surface(objs) - encrypted := bytes.Contains(whole, []byte("/Encrypt")) - linearised := HasLinearisation(whole) - - // Locate trailer text (for quick eyeball). - trailerText := "" - if tIdx := bytes.LastIndex(whole, []byte("trailer")); tIdx >= 0 { - end := bytes.Index(whole[tIdx:], []byte("startxref")) - if end >= 0 { - trailerText = string(whole[tIdx : tIdx+end]) - } - } - - return map[string]any{ - "version": version, - "size": size, - "encrypted": encrypted, - "linearised": linearised, - "trailer": trailerText, - "objects": objectsToMaps(objs), - "xref": xrefEntriesToMaps(xref), - "xrefMode": xrefMode, - "xrefNote": xrefNote, - "suspicious": surfaceToMaps(surfaced), - }, nil -} - -func scanIndirectObjects(whole []byte) []Object { - objRE := regexp.MustCompile(`(?m)(\d+)\s+(\d+)\s+obj\b`) - locs := objRE.FindAllSubmatchIndex(whole, -1) - objs := make([]Object, 0, len(locs)) - for _, loc := range locs { - start := int64(loc[0]) - num, _ := strconv.ParseInt(string(whole[loc[2]:loc[3]]), 10, 64) - gen, _ := strconv.ParseInt(string(whole[loc[4]:loc[5]]), 10, 64) - o := Object{Num: num, Gen: gen, Offset: start, Source: "direct"} - endIdx := bytes.Index(whole[loc[1]:], []byte("endobj")) - if endIdx < 0 { - continue - } - body := whole[loc[1] : loc[1]+endIdx] - streamIdx := bytes.Index(body, []byte("stream")) - var dict []byte - if streamIdx >= 0 { - dict = body[:streamIdx] - s := streamIdx + len("stream") - if s < len(body) && body[s] == '\r' { - s++ - } - if s < len(body) && body[s] == '\n' { - s++ - } - o.HasStream = true - o.StreamOff = int64(loc[1]) + int64(s) - endStreamIdx := bytes.LastIndex(body, []byte("endstream")) - if endStreamIdx < 0 { - continue - } - endStreamAbs := int64(loc[1]) + int64(endStreamIdx) - // trim trailing newline/cr - for k := 0; k < 2; k++ { - if endStreamAbs > 0 && endStreamAbs-1 < int64(len(whole)) { - if whole[endStreamAbs-1] == '\n' || whole[endStreamAbs-1] == '\r' { - endStreamAbs-- - } - } - } - o.StreamLen = endStreamAbs - o.StreamOff - } else { - dict = body - } - o.Dict = strings.TrimSpace(string(dict)) - o.Filters = detectFilters(o.Dict) - o.Type = detectType(o.Dict) - o.Subtype = detectSubtype(o.Dict) - objs = append(objs, o) - } - return objs -} - -func recoverObjectStreams(whole []byte, d *document.Document, objs []Object) []Object { - out := []Object{} - for _, o := range objs { - if !o.HasStream { - continue - } - if !strings.Contains(o.Dict, "/Type /ObjStm") && !strings.Contains(o.Dict, "/Type/ObjStm") { - continue - } - raw, err := d.Range(o.StreamOff, o.StreamLen) - if err != nil { - continue - } - // Apply filters in order before handing to ObjStm decoder. - children, err := DecodeObjStm(raw, o.Dict) - if err != nil { - continue - } - for num, body := range children { - out = append(out, Object{ - Num: num, - Gen: 0, - Offset: o.StreamOff, - Dict: body, - HasStream: false, - Type: detectType(body), - Subtype: detectSubtype(body), - Source: "objstm", - Container: o.Num, - }) - } - } - return out -} - -func objectsToMaps(objs []Object) []map[string]any { - out := make([]map[string]any, len(objs)) - for i, o := range objs { - filters := make([]any, len(o.Filters)) - for j, f := range o.Filters { - filters[j] = f - } - out[i] = map[string]any{ - "num": o.Num, - "gen": o.Gen, - "offset": o.Offset, - "dict": o.Dict, - "hasStream": o.HasStream, - "streamOff": o.StreamOff, - "streamLen": o.StreamLen, - "filters": filters, - "type": o.Type, - "subtype": o.Subtype, - "source": o.Source, - "container": o.Container, - } - } - return out -} - -func xrefEntriesToMaps(entries []XRefEntry) []map[string]any { - out := make([]map[string]any, len(entries)) - for i, e := range entries { - out[i] = map[string]any{ - "type": e.Type, - "num": e.ObjNum, - "gen": e.Gen, - "offset": e.Offset, - "objStm": e.ObjStmNum, - "index": e.Index, - } - } - return out -} - -func surfaceToMaps(hits []SuspiciousHit) []map[string]any { - out := make([]map[string]any, len(hits)) - for i, h := range hits { - out[i] = map[string]any{ - "kind": h.Kind, - "num": h.ObjNum, - "gen": h.Gen, - "offset": h.Offset, - "detail": h.Detail, - } - } - return out -} - -var filtersRE = regexp.MustCompile(`/Filter\s*(?:\[([^\]]*)\]|/([A-Za-z0-9]+))`) - -func detectFilters(dict string) []string { - m := filtersRE.FindStringSubmatch(dict) - if m == nil { - return nil - } - if m[2] != "" { - return []string{m[2]} - } - parts := strings.Fields(m[1]) - out := make([]string, 0, len(parts)) - for _, p := range parts { - p = strings.TrimPrefix(p, "/") - if p != "" { - out = append(out, p) - } - } - return out -} - -var ( - typeRE = regexp.MustCompile(`/Type\s*/([A-Za-z0-9]+)`) - subtypeRE = regexp.MustCompile(`/Subtype\s*/([A-Za-z0-9]+)`) -) - -func detectType(dict string) string { - m := typeRE.FindStringSubmatch(dict) - if len(m) > 1 { - return m[1] - } - return "" -} - -func detectSubtype(dict string) string { - m := subtypeRE.FindStringSubmatch(dict) - if len(m) > 1 { - return m[1] - } - return "" -} - -// DecodeStream reads the stream for object objNum and runs it through -// the declared filter chain (with predictor). Works for both direct -// objects and those recovered from ObjStm containers. -func DecodeStream(d *document.Document, objNum int64) (map[string]any, error) { - summary, err := Parse(d) - if err != nil { - return nil, err - } - for _, o := range summary["objects"].([]map[string]any) { - n, _ := o["num"].(int64) - if n != objNum { - continue - } - hasStream, _ := o["hasStream"].(bool) - if !hasStream { - return nil, fmt.Errorf("object %d has no stream", objNum) - } - off, _ := o["streamOff"].(int64) - ln, _ := o["streamLen"].(int64) - raw, err := d.Range(off, ln) - if err != nil { - return nil, err - } - dict, _ := o["dict"].(string) - filters, _ := o["filters"].([]any) - predictor, columns, colors, bits := parseDecodeParms(dict) - - decoded := append([]byte(nil), raw...) - applied := []string{} - var runErr error - for _, f := range filters { - name, _ := f.(string) - decoded, runErr = ApplyFilter(name, decoded) - if runErr != nil { - return map[string]any{ - "objNum": objNum, - "raw": raw, - "decoded": nil, - "error": runErr.Error(), - "applied": applied, - "filters": filters, - }, nil - } - applied = append(applied, name) - } - if predictor > 1 { - decoded, runErr = ApplyPredictor(decoded, predictor, columns, colors, bits) - if runErr != nil { - return map[string]any{ - "objNum": objNum, - "raw": raw, - "decoded": nil, - "error": runErr.Error(), - "applied": applied, - "filters": filters, - }, nil - } - applied = append(applied, fmt.Sprintf("predictor=%d", predictor)) - } - return map[string]any{ - "objNum": objNum, - "raw": raw, - "decoded": decoded, - "applied": applied, - "filters": filters, - }, nil - } - return nil, fmt.Errorf("object %d not found", objNum) -} diff --git a/internal/leviathan/pdf/pdf_test.go b/internal/leviathan/pdf/pdf_test.go deleted file mode 100644 index 9934edd..0000000 --- a/internal/leviathan/pdf/pdf_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package pdf - -import ( - "os" - "strings" - "testing" - - "github.com/andydixon/link/internal/leviathan/document" -) - -func TestParseRejectsOversizedPDFBeforeWholeRange(t *testing.T) { - file, err := os.CreateTemp(t.TempDir(), "huge-*.pdf") - if err != nil { - t.Fatalf("CreateTemp() failed: %v", err) - } - if _, err := file.WriteString("%PDF-1.7\n"); err != nil { - t.Fatalf("WriteString() failed: %v", err) - } - if err := file.Truncate(maxPDFParseBytes + 1); err != nil { - t.Fatalf("Truncate() failed: %v", err) - } - if err := file.Close(); err != nil { - t.Fatalf("Close() failed: %v", err) - } - - doc, err := document.Open(file.Name()) - if err != nil { - t.Fatalf("document.Open() failed: %v", err) - } - _, err = Parse(doc) - if err == nil { - t.Fatal("expected oversized PDF to be rejected") - } - if !strings.Contains(err.Error(), "pdf parse skipped") { - t.Fatalf("unexpected error: %v", err) - } -} diff --git a/internal/leviathan/pdf/surface.go b/internal/leviathan/pdf/surface.go deleted file mode 100644 index 5dc0395..0000000 --- a/internal/leviathan/pdf/surface.go +++ /dev/null @@ -1,128 +0,0 @@ -package pdf - -import ( - "bytes" - "regexp" - "strings" -) - -// SuspiciousHit marks a dictionary fragment worth a closer look. It is -// surfaced to the UI with its containing object number so a user can -// jump straight to it. -type SuspiciousHit struct { - Kind string `json:"kind"` - ObjNum int64 `json:"objNum"` - Gen int64 `json:"gen"` - Offset int64 `json:"offset"` - Detail string `json:"detail,omitempty"` -} - -// Surface walks the enumerated object list and flags anything routinely -// abused by hostile PDFs: embedded files, JavaScript, launch actions, -// URI actions, form-submission targets, OpenAction / AA triggers. -func Surface(objects []Object) []SuspiciousHit { - hits := []SuspiciousHit{} - for _, o := range objects { - dict := o.Dict - push := func(kind, detail string) { - hits = append(hits, SuspiciousHit{ - Kind: kind, ObjNum: o.Num, Gen: o.Gen, - Offset: o.Offset, Detail: detail, - }) - } - // Key markers. Order matters only for readability of the UI. - if containsName(dict, "JavaScript") || containsName(dict, "JS") { - push("javascript", firstLine(dict, 240)) - } - if containsName(dict, "EmbeddedFile") || containsName(dict, "Filespec") { - push("embedded-file", extractName(dict, "/F")) - } - if containsName(dict, "Launch") { - push("launch-action", extractLaunchTarget(dict)) - } - if containsName(dict, "URI") { - if uri := extractURI(dict); uri != "" { - push("uri-action", uri) - } - } - if containsName(dict, "SubmitForm") { - push("submit-form", "") - } - if containsName(dict, "OpenAction") { - push("open-action", "") - } - if strings.Contains(dict, "/AA") { - push("additional-action", "") - } - if strings.Contains(dict, "/Type /XFA") || strings.Contains(dict, "/XFA ") { - push("xfa-form", "") - } - if strings.Contains(dict, "/RichMedia") { - push("rich-media", "") - } - if strings.Contains(dict, "/Type /Metadata") && strings.Contains(dict, "/Subtype /XML") { - push("xmp-metadata", "") - } - } - return hits -} - -func containsName(dict, name string) bool { - // Match `/Name` as a whole token — not inside /Names/ or /FooJavaScript. - pat := regexp.MustCompile(`/` + regexp.QuoteMeta(name) + `\b`) - return pat.MatchString(dict) -} - -// Go's regexp caps bounded repetitions at 1000 — do not raise this. -var uriRE = regexp.MustCompile(`/URI\s*\(([^)]{1,1000})\)`) - -func extractURI(dict string) string { - m := uriRE.FindStringSubmatch(dict) - if m == nil { - return "" - } - return m[1] -} - -var fnameRE = regexp.MustCompile(`/F\s*\(([^)]{1,512})\)`) - -func extractName(dict, key string) string { - _ = key - m := fnameRE.FindStringSubmatch(dict) - if m == nil { - return "" - } - return m[1] -} - -var launchRE = regexp.MustCompile(`/F\s*\(([^)]{1,512})\)`) - -func extractLaunchTarget(dict string) string { - m := launchRE.FindStringSubmatch(dict) - if m == nil { - return "" - } - return m[1] -} - -func firstLine(s string, max int) string { - nl := strings.IndexByte(s, '\n') - if nl > 0 { - s = s[:nl] - } - s = strings.TrimSpace(s) - if len(s) > max { - s = s[:max] + "…" - } - return s -} - -// HasLinearisation reports whether a PDF is linearised (Fast Web View). -// A linearised PDF has an obj dict near the start containing /Linearized. -func HasLinearisation(whole []byte) bool { - n := 4096 - if n > len(whole) { - n = len(whole) - } - return bytes.Contains(whole[:n], []byte("/Linearized")) -} diff --git a/internal/leviathan/pdf/xref.go b/internal/leviathan/pdf/xref.go deleted file mode 100644 index f6efb22..0000000 --- a/internal/leviathan/pdf/xref.go +++ /dev/null @@ -1,422 +0,0 @@ -package pdf - -import ( - "bytes" - "errors" - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// XRefEntry describes one cross-reference entry after parsing either a -// classical xref table or a modern xref stream. -// -// Type 0 — free object -// Type 1 — in-use object; Offset is the byte offset in the file -// Type 2 — object inside an object stream; ObjStmNum identifies the -// container ObjStm and Index is the 0-based position within it -type XRefEntry struct { - Type int `json:"type"` - ObjNum int64 `json:"objNum"` - Gen int64 `json:"gen"` - Offset int64 `json:"offset"` - ObjStmNum int64 `json:"objStmNum,omitempty"` - Index int64 `json:"index,omitempty"` -} - -const maxObjectStreamEntries = 1 << 20 - -// ParseXRef attempts to reconstruct the cross-reference map. It handles -// both classical "xref\n0 N\n..." tables and xref streams (/Type /XRef). -// Returns a flat slice of entries covering every object it could resolve. -func ParseXRef(d *document.Document, whole []byte) ([]XRefEntry, string, error) { - mode := "" - entries := map[int64]XRefEntry{} - - // Find "startxref" near the end. - idx := bytes.LastIndex(whole, []byte("startxref")) - if idx < 0 { - return nil, "", errors.New("no startxref") - } - after := whole[idx+len("startxref"):] - after = bytes.TrimLeft(after, " \r\n\t") - var xrefPos int64 - fmt.Sscanf(string(after), "%d", &xrefPos) - if xrefPos <= 0 || xrefPos >= int64(len(whole)) { - return nil, "", fmt.Errorf("bad startxref offset %d", xrefPos) - } - - // Walk the xref chain via /Prev pointers. - visited := map[int64]bool{} - pos := xrefPos - for pos > 0 && !visited[pos] { - visited[pos] = true - // Classical table if the first non-whitespace token is "xref". - tail := whole[pos:] - trimmed := bytes.TrimLeft(tail, " \r\n\t") - if bytes.HasPrefix(trimmed, []byte("xref")) { - if mode == "" { - mode = "table" - } else if mode != "table" { - mode = "hybrid" - } - nextPrev, err := parseClassicalXRef(trimmed, entries) - if err != nil { - return mapToSlice(entries), mode, err - } - pos = nextPrev - continue - } - // Otherwise an xref stream: an indirect object at `pos`. - if mode == "" { - mode = "stream" - } else if mode != "stream" { - mode = "hybrid" - } - nextPrev, err := parseXRefStream(d, whole, pos, entries) - if err != nil { - return mapToSlice(entries), mode, err - } - pos = nextPrev - } - return mapToSlice(entries), mode, nil -} - -func mapToSlice(m map[int64]XRefEntry) []XRefEntry { - out := make([]XRefEntry, 0, len(m)) - for _, e := range m { - out = append(out, e) - } - return out -} - -var xrefRangeRE = regexp.MustCompile(`(?m)^\s*(\d+)\s+(\d+)\s*$`) -var xrefRowRE = regexp.MustCompile(`^(\d{10})\s+(\d{5})\s+([nf])`) -var prevRE = regexp.MustCompile(`/Prev\s+(\d+)`) - -func parseClassicalXRef(block []byte, entries map[int64]XRefEntry) (int64, error) { - // Skip "xref" line. - nl := bytes.IndexByte(block, '\n') - if nl < 0 { - return 0, errors.New("xref: no newline after keyword") - } - cursor := block[nl+1:] - for { - m := xrefRangeRE.FindSubmatchIndex(cursor) - if m == nil { - break - } - if m[0] > 0 { - // Subsection must begin at the start of the cursor. - break - } - start, _ := strconv.ParseInt(string(cursor[m[2]:m[3]]), 10, 64) - count, _ := strconv.ParseInt(string(cursor[m[4]:m[5]]), 10, 64) - // Advance past the subsection line. - nl2 := bytes.IndexByte(cursor, '\n') - if nl2 < 0 { - break - } - cursor = cursor[nl2+1:] - for i := int64(0); i < count; i++ { - if len(cursor) < 20 { - return 0, errors.New("xref: truncated subsection") - } - row := cursor[:20] - rm := xrefRowRE.FindSubmatch(row) - if rm != nil { - off, _ := strconv.ParseInt(string(rm[1]), 10, 64) - gen, _ := strconv.ParseInt(string(rm[2]), 10, 64) - typ := 1 - if rm[3][0] == 'f' { - typ = 0 - } - num := start + i - if _, seen := entries[num]; !seen { - entries[num] = XRefEntry{ - Type: typ, ObjNum: num, Gen: gen, Offset: off, - } - } - } - cursor = cursor[20:] - } - } - // Locate trailer and /Prev. - tIdx := bytes.Index(block, []byte("trailer")) - if tIdx < 0 { - return 0, nil - } - trailer := block[tIdx:] - if e := bytes.Index(trailer, []byte("startxref")); e > 0 { - trailer = trailer[:e] - } - if pm := prevRE.FindSubmatch(trailer); pm != nil { - prev, _ := strconv.ParseInt(string(pm[1]), 10, 64) - return prev, nil - } - return 0, nil -} - -func parseXRefStream(d *document.Document, whole []byte, pos int64, entries map[int64]XRefEntry) (int64, error) { - // Locate "obj" after pos, then "stream"/"endstream". - if pos < 0 || int(pos) >= len(whole) { - return 0, errors.New("xref stream: bad offset") - } - body := whole[pos:] - objIdx := bytes.Index(body, []byte("obj")) - if objIdx < 0 { - return 0, errors.New("xref stream: no obj") - } - dictEnd := bytes.Index(body[objIdx:], []byte("stream")) - if dictEnd < 0 { - return 0, errors.New("xref stream: no stream") - } - dict := string(body[objIdx : objIdx+dictEnd]) - streamStart := objIdx + dictEnd + len("stream") - if streamStart < len(body) && body[streamStart] == '\r' { - streamStart++ - } - if streamStart < len(body) && body[streamStart] == '\n' { - streamStart++ - } - endIdx := bytes.Index(body[streamStart:], []byte("endstream")) - if endIdx < 0 { - return 0, errors.New("xref stream: no endstream") - } - raw := body[streamStart : streamStart+endIdx] - // trim trailing newline. - if len(raw) > 0 && raw[len(raw)-1] == '\n' { - raw = raw[:len(raw)-1] - } - if len(raw) > 0 && raw[len(raw)-1] == '\r' { - raw = raw[:len(raw)-1] - } - - // Decode the xref-stream dictionary fields. - filters := parseDictNameList(dict, "/Filter") - w := parseDictIntArray(dict, "/W") - index := parseDictIntArray(dict, "/Index") - sizeN := parseDictInt(dict, "/Size") - predictor, columns, colors, bits := parseDecodeParms(dict) - - // Apply filters. - data := append([]byte(nil), raw...) - var err error - for _, f := range filters { - data, err = ApplyFilter(f, data) - if err != nil { - return 0, fmt.Errorf("xref stream: filter %s: %w", f, err) - } - } - if predictor > 1 { - data, err = ApplyPredictor(data, predictor, columns, colors, bits) - if err != nil { - return 0, err - } - } - if len(w) != 3 { - return 0, fmt.Errorf("xref stream: bad /W array %v", w) - } - entryLen := 0 - for _, width := range w { - if width < 0 || width > 8 { - return 0, fmt.Errorf("xref stream: invalid field width %d", width) - } - entryLen += width - } - if entryLen <= 0 { - return 0, errors.New("xref stream: zero-length entries") - } - if len(index) == 0 { - if sizeN < 0 { - return 0, errors.New("xref stream: negative /Size") - } - index = []int{0, sizeN} - } - if len(index)%2 != 0 { - return 0, errors.New("xref stream: /Index must contain start/count pairs") - } - // Walk subsections. - pi := 0 - for s := 0; s+1 < len(index); s += 2 { - first := int64(index[s]) - count := int64(index[s+1]) - if first < 0 || count < 0 { - return 0, errors.New("xref stream: negative /Index value") - } - for i := int64(0); i < count; i++ { - if pi+entryLen > len(data) { - break - } - row := data[pi : pi+entryLen] - pi += entryLen - t := readBE(row[:w[0]]) - f2 := readBE(row[w[0] : w[0]+w[1]]) - f3 := readBE(row[w[0]+w[1] : w[0]+w[1]+w[2]]) - if w[0] == 0 { - t = 1 // default Type 1 per spec - } - num := first + i - if _, seen := entries[num]; seen { - continue - } - switch t { - case 0: - entries[num] = XRefEntry{Type: 0, ObjNum: num, Gen: f3} - case 1: - entries[num] = XRefEntry{Type: 1, ObjNum: num, Gen: f3, Offset: f2} - case 2: - entries[num] = XRefEntry{ - Type: 2, ObjNum: num, ObjStmNum: f2, Index: f3, - } - } - } - } - if prev := parseDictInt(dict, "/Prev"); prev > 0 { - return int64(prev), nil - } - return 0, nil -} - -func readBE(b []byte) int64 { - v := int64(0) - for _, c := range b { - v = (v << 8) | int64(c) - } - return v -} - -// --------------------------------------------------------------------------- -// ObjStm — compressed object streams -// --------------------------------------------------------------------------- - -// DecodeObjStm decodes an /ObjStm stream and returns a map of -// object-number → raw object body (text, never with "stream"). -func DecodeObjStm(raw []byte, dict string) (map[int64]string, error) { - filters := parseDictNameList(dict, "/Filter") - predictor, columns, colors, bits := parseDecodeParms(dict) - data := append([]byte(nil), raw...) - var err error - for _, f := range filters { - data, err = ApplyFilter(f, data) - if err != nil { - return nil, err - } - } - if predictor > 1 { - data, err = ApplyPredictor(data, predictor, columns, colors, bits) - if err != nil { - return nil, err - } - } - n := parseDictInt(dict, "/N") - first := parseDictInt(dict, "/First") - if n <= 0 || first <= 0 || first >= len(data) { - return nil, fmt.Errorf("ObjStm: bad /N=%d /First=%d", n, first) - } - header := strings.Fields(string(data[:first])) - if n > maxObjectStreamEntries || n > len(header)/2 { - return nil, fmt.Errorf("ObjStm: header has %d tokens, need %d", len(header), n*2) - } - out := map[int64]string{} - offsets := make([]int, n) - nums := make([]int64, n) - for i := 0; i < n; i++ { - num, _ := strconv.ParseInt(header[i*2], 10, 64) - off, _ := strconv.Atoi(header[i*2+1]) - nums[i] = num - offsets[i] = off - } - for i := 0; i < n; i++ { - start := first + offsets[i] - end := len(data) - if i+1 < n { - end = first + offsets[i+1] - } - if start < 0 || end > len(data) || start > end { - continue - } - out[nums[i]] = strings.TrimSpace(string(data[start:end])) - } - return out, nil -} - -// --------------------------------------------------------------------------- -// Small dictionary helpers -// --------------------------------------------------------------------------- - -var decodeParmRE = regexp.MustCompile(`/DecodeParms\s*<<([^>]*)>>`) - -func parseDictInt(dict, key string) int { - pat := regexp.MustCompile(regexp.QuoteMeta(key) + `\s+(-?\d+)\b`) - m := pat.FindStringSubmatch(dict) - if m == nil { - return 0 - } - n, _ := strconv.Atoi(m[1]) - return n -} - -func parseDictIntArray(dict, key string) []int { - pat := regexp.MustCompile(regexp.QuoteMeta(key) + `\s*\[([^\]]*)\]`) - m := pat.FindStringSubmatch(dict) - if m == nil { - return nil - } - parts := strings.Fields(m[1]) - out := make([]int, 0, len(parts)) - for _, p := range parts { - n, err := strconv.Atoi(p) - if err != nil { - continue - } - out = append(out, n) - } - return out -} - -func parseDictNameList(dict, key string) []string { - // Handles both `/Filter /FlateDecode` and `/Filter [/A85 /Flate]`. - pat1 := regexp.MustCompile(regexp.QuoteMeta(key) + `\s*/([A-Za-z0-9]+)`) - if m := pat1.FindStringSubmatch(dict); m != nil { - return []string{m[1]} - } - pat2 := regexp.MustCompile(regexp.QuoteMeta(key) + `\s*\[([^\]]*)\]`) - if m := pat2.FindStringSubmatch(dict); m != nil { - parts := strings.Fields(m[1]) - out := make([]string, 0, len(parts)) - for _, p := range parts { - out = append(out, strings.TrimPrefix(p, "/")) - } - return out - } - return nil -} - -func parseDecodeParms(dict string) (predictor, columns, colors, bits int) { - m := decodeParmRE.FindStringSubmatch(dict) - if m == nil { - return 1, 1, 1, 8 - } - inner := m[1] - predictor = parseDictInt(inner, "/Predictor") - if predictor == 0 { - predictor = 1 - } - columns = parseDictInt(inner, "/Columns") - if columns == 0 { - columns = 1 - } - colors = parseDictInt(inner, "/Colors") - if colors == 0 { - colors = 1 - } - bits = parseDictInt(inner, "/BitsPerComponent") - if bits == 0 { - bits = 8 - } - return -} diff --git a/internal/leviathan/spectrogram/spectrogram.go b/internal/leviathan/spectrogram/spectrogram.go deleted file mode 100644 index de37675..0000000 --- a/internal/leviathan/spectrogram/spectrogram.go +++ /dev/null @@ -1,798 +0,0 @@ -package spectrogram - -import ( - "bytes" - "encoding/binary" - "errors" - "fmt" - "image" - "image/color" - "image/png" - "io" - "math" - "path/filepath" - "strings" - - _ "image/gif" - _ "image/jpeg" - _ "image/png" - - "github.com/hajimehoshi/go-mp3" -) - -const ( - defaultSampleRate = 44100 - defaultDuration = 5.0 - // MaxImageBytes caps compressed image inputs before decode. Pixel count is - // checked separately because small compressed images can still expand huge. - MaxImageBytes = 64 << 20 - // MaxAudioBytes caps encoded WAV/MP3 inputs before full in-memory decode. - MaxAudioBytes = 128 << 20 - maxImagePixels = 24_000_000 - maxDecodedBytes = 64 << 20 - twoPi = 2 * math.Pi -) - -// EncodeOptions controls image-to-audio spectrogram synthesis. -type EncodeOptions struct { - DurationSec float64 - SampleRate int - MinHz float64 - MaxHz float64 - Bins int - Columns int - Invert bool - PreserveAR bool - Gain float64 -} - -// InsertOptions controls embedding synthesized spectrogram audio into a carrier. -type InsertOptions struct { - EncodeOptions - StartSec float64 - MixLevel float64 -} - -// ViewOptions controls spectrogram image rendering from audio. -type ViewOptions struct { - FFTSize int - Height int - MaxWidth int - Aspect float64 - MinHz float64 - MaxHz float64 - Brightness float64 - Contrast float64 -} - -type audioBuffer struct { - SampleRate int - Channels int - Samples []float64 -} - -// ImageToWAV converts an image into a WAV whose spectrogram reveals that image. -func ImageToWAV(imageData []byte, opts EncodeOptions) ([]byte, map[string]any, error) { - grid, meta, opts, err := imageGrid(imageData, opts) - if err != nil { - return nil, nil, err - } - mono := synthesize(grid, opts) - audio := audioBuffer{SampleRate: opts.SampleRate, Channels: 1, Samples: mono} - wav, err := encodeWAV(audio) - if err != nil { - return nil, nil, err - } - summary := map[string]any{ - "mode": "image-to-wav", - "imageFormat": meta["format"], - "imageWidth": meta["width"], - "imageHeight": meta["height"], - "imageAspect": meta["aspect"], - "durationSec": opts.DurationSec, - "sampleRate": opts.SampleRate, - "minHz": opts.MinHz, - "maxHz": opts.MaxHz, - "bins": opts.Bins, - "columns": opts.Columns, - "preserveAR": opts.PreserveAR, - "wavBytes": len(wav), - } - return wav, summary, nil -} - -// InsertImageIntoAudio mixes a synthesized image spectrogram into WAV or MP3 carrier audio. -func InsertImageIntoAudio(imageData, audioData []byte, audioName string, opts InsertOptions) ([]byte, map[string]any, error) { - carrier, err := DecodeAudio(audioData, audioName) - if err != nil { - return nil, nil, err - } - opts.EncodeOptions.SampleRate = carrier.SampleRate - grid, meta, encodeOpts, err := imageGrid(imageData, opts.EncodeOptions) - if err != nil { - return nil, nil, err - } - tone := synthesize(grid, encodeOpts) - if opts.MixLevel <= 0 { - opts.MixLevel = 0.35 - } - if opts.MixLevel > 1 { - opts.MixLevel = 1 - } - if opts.StartSec < 0 { - opts.StartSec = 0 - } - startFrame := int(math.Round(opts.StartSec * float64(carrier.SampleRate))) - requiredFrames := startFrame + len(tone) - currentFrames := len(carrier.Samples) / carrier.Channels - if requiredFrames > currentFrames { - extended := make([]float64, requiredFrames*carrier.Channels) - copy(extended, carrier.Samples) - carrier.Samples = extended - } - for i, sample := range tone { - frame := startFrame + i - for ch := 0; ch < carrier.Channels; ch++ { - idx := frame*carrier.Channels + ch - carrier.Samples[idx] += sample * opts.MixLevel - } - } - limitPeak(carrier.Samples, 0.98) - wav, err := encodeWAV(carrier) - if err != nil { - return nil, nil, err - } - summary := map[string]any{ - "mode": "insert-into-audio", - "imageFormat": meta["format"], - "imageWidth": meta["width"], - "imageHeight": meta["height"], - "imageAspect": meta["aspect"], - "carrierSampleRate": carrier.SampleRate, - "carrierChannels": carrier.Channels, - "startSec": opts.StartSec, - "mixLevel": opts.MixLevel, - "durationSec": encodeOpts.DurationSec, - "minHz": encodeOpts.MinHz, - "maxHz": encodeOpts.MaxHz, - "bins": encodeOpts.Bins, - "columns": encodeOpts.Columns, - "preserveAR": encodeOpts.PreserveAR, - "wavBytes": len(wav), - } - return wav, summary, nil -} - -// AudioSpectrogramPNG renders WAV or MP3 audio as a PNG spectrogram. -func AudioSpectrogramPNG(audioData []byte, audioName string, opts ViewOptions) ([]byte, map[string]any, error) { - audio, err := DecodeAudio(audioData, audioName) - if err != nil { - return nil, nil, err - } - mono := toMono(audio) - if len(mono) == 0 { - return nil, nil, errors.New("audio contains no samples") - } - opts = normalizeViewOptions(opts, audio.SampleRate) - img := renderSpectrogram(mono, audio.SampleRate, opts) - var buf bytes.Buffer - if err := png.Encode(&buf, img); err != nil { - return nil, nil, err - } - duration := float64(len(mono)) / float64(audio.SampleRate) - summary := map[string]any{ - "mode": "audio-to-spectrogram", - "sampleRate": audio.SampleRate, - "channels": audio.Channels, - "durationSec": duration, - "fftSize": opts.FFTSize, - "width": img.Bounds().Dx(), - "height": img.Bounds().Dy(), - "aspectRatio": opts.Aspect, - "dynamicRange": 80, - "minHz": opts.MinHz, - "maxHz": opts.MaxHz, - "pngBytes": buf.Len(), - } - return buf.Bytes(), summary, nil -} - -// DecodeAudio decodes WAV directly and MP3 through go-mp3. -func DecodeAudio(data []byte, name string) (audioBuffer, error) { - if len(data) == 0 { - return audioBuffer{}, errors.New("audio file is empty") - } - if len(data) > MaxAudioBytes { - return audioBuffer{}, fmt.Errorf("audio input is too large: %d bytes exceeds %d bytes", len(data), MaxAudioBytes) - } - if isWAV(data) { - return decodeWAV(data) - } - ext := strings.ToLower(filepath.Ext(name)) - if ext == ".mp3" || isMP3(data) { - return decodeMP3(data) - } - return audioBuffer{}, errors.New("unsupported audio format: load a WAV or MP3 file") -} - -func imageGrid(data []byte, opts EncodeOptions) ([][]float64, map[string]any, EncodeOptions, error) { - if len(data) == 0 { - return nil, nil, opts, errors.New("image file is empty") - } - if len(data) > MaxImageBytes { - return nil, nil, opts, fmt.Errorf("image input is too large: %d bytes exceeds %d bytes", len(data), MaxImageBytes) - } - cfg, format, err := image.DecodeConfig(bytes.NewReader(data)) - if err != nil { - return nil, nil, opts, fmt.Errorf("decode image config: %w", err) - } - if cfg.Width <= 0 || cfg.Height <= 0 { - return nil, nil, opts, errors.New("image has no pixels") - } - if int64(cfg.Width)*int64(cfg.Height) > maxImagePixels { - return nil, nil, opts, fmt.Errorf("image dimensions are too large: %dx%d exceeds %d pixels", cfg.Width, cfg.Height, maxImagePixels) - } - img, format, err := image.Decode(bytes.NewReader(data)) - if err != nil { - return nil, nil, opts, fmt.Errorf("decode image: %w", err) - } - bounds := img.Bounds() - if bounds.Dx() <= 0 || bounds.Dy() <= 0 { - return nil, nil, opts, errors.New("image has no pixels") - } - opts = normalizeEncodeOptions(opts, bounds.Dx(), bounds.Dy()) - grid := make([][]float64, opts.Bins) - for bin := 0; bin < opts.Bins; bin++ { - grid[bin] = make([]float64, opts.Columns) - sourceY := bounds.Min.Y + int((float64(opts.Bins-1-bin)+0.5)*float64(bounds.Dy())/float64(opts.Bins)) - if sourceY >= bounds.Max.Y { - sourceY = bounds.Max.Y - 1 - } - for col := 0; col < opts.Columns; col++ { - sourceX := bounds.Min.X + int((float64(col)+0.5)*float64(bounds.Dx())/float64(opts.Columns)) - if sourceX >= bounds.Max.X { - sourceX = bounds.Max.X - 1 - } - grid[bin][col] = pixelAmplitude(img.At(sourceX, sourceY), opts.Invert) - } - } - meta := map[string]any{"format": format, "width": bounds.Dx(), "height": bounds.Dy(), "aspect": float64(bounds.Dy()) / float64(bounds.Dx())} - return grid, meta, opts, nil -} - -func normalizeEncodeOptions(opts EncodeOptions, imageWidth, imageHeight int) EncodeOptions { - if opts.DurationSec <= 0 { - opts.DurationSec = defaultDuration - } - opts.DurationSec = clampFloat(opts.DurationSec, 0.25, 60) - if opts.SampleRate <= 0 { - opts.SampleRate = defaultSampleRate - } - opts.SampleRate = clampInt(opts.SampleRate, 8000, 96000) - if opts.MinHz <= 0 { - opts.MinHz = 200 - } - if opts.MaxHz <= opts.MinHz { - opts.MaxHz = math.Min(float64(opts.SampleRate)/2-200, 20000) - } - opts.MinHz = clampFloat(opts.MinHz, 20, float64(opts.SampleRate)/2-100) - opts.MaxHz = clampFloat(opts.MaxHz, opts.MinHz+100, float64(opts.SampleRate)/2-50) - if opts.PreserveAR && imageWidth > 0 && imageHeight > 0 { - aspect := float64(imageHeight) / float64(imageWidth) - baseRange := opts.MaxHz - opts.MinHz - desiredRange := baseRange * aspect - opts.MaxHz = clampFloat(opts.MinHz+desiredRange, opts.MinHz+100, float64(opts.SampleRate)/2-50) - } - if opts.Bins <= 0 { - opts.Bins = clampInt(imageHeight, 48, 192) - } - opts.Bins = clampInt(opts.Bins, 16, 384) - if opts.Columns <= 0 { - opts.Columns = clampInt(imageWidth, 64, 1024) - } - opts.Columns = clampInt(opts.Columns, 16, 2048) - if opts.Gain <= 0 { - opts.Gain = 0.85 - } - opts.Gain = clampFloat(opts.Gain, 0.05, 1) - return opts -} - -func normalizeViewOptions(opts ViewOptions, sampleRate int) ViewOptions { - if opts.FFTSize <= 0 { - opts.FFTSize = 2048 - } - opts.FFTSize = nearestPowerOfTwo(clampInt(opts.FFTSize, 256, 8192)) - if opts.Height <= 0 { - opts.Height = 512 - } - opts.Height = clampInt(opts.Height, 64, 1024) - if opts.MaxWidth <= 0 { - opts.MaxWidth = 1400 - } - opts.MaxWidth = clampInt(opts.MaxWidth, 64, 2400) - if opts.Aspect <= 0 { - opts.Aspect = 1 - } - opts.Aspect = clampFloat(opts.Aspect, 0.25, 4) - if opts.MinHz < 0 { - opts.MinHz = 0 - } - if opts.MaxHz <= opts.MinHz { - opts.MaxHz = float64(sampleRate) / 2 - } - opts.MinHz = clampFloat(opts.MinHz, 0, float64(sampleRate)/2-10) - opts.MaxHz = clampFloat(opts.MaxHz, opts.MinHz+10, float64(sampleRate)/2) - if opts.Brightness <= 0 { - opts.Brightness = 1 - } - opts.Brightness = clampFloat(opts.Brightness, 0.1, 4) - if opts.Contrast <= 0 { - opts.Contrast = 1 - } - opts.Contrast = clampFloat(opts.Contrast, 0.1, 4) - return opts -} - -func pixelAmplitude(c color.Color, invert bool) float64 { - r, g, b, a := c.RGBA() - alpha := float64(a) / 65535 - if alpha <= 0.001 { - return 0 - } - luma := (0.2126*float64(r) + 0.7152*float64(g) + 0.0722*float64(b)) / 65535 - amp := luma - if invert { - amp = 1 - luma - } - amp *= alpha - return math.Pow(clampFloat(amp, 0, 1), 1.15) -} - -func synthesize(grid [][]float64, opts EncodeOptions) []float64 { - bins := len(grid) - cols := len(grid[0]) - frames := int(math.Round(opts.DurationSec * float64(opts.SampleRate))) - if frames < 1 { - frames = 1 - } - out := make([]float64, frames) - phase := make([]float64, bins) - inc := make([]float64, bins) - for i := range inc { - var pos float64 - if bins > 1 { - pos = float64(i) / float64(bins-1) - } - freq := opts.MinHz + (opts.MaxHz-opts.MinHz)*pos - inc[i] = twoPi * freq / float64(opts.SampleRate) - } - for i := 0; i < frames; i++ { - var x float64 - if frames > 1 { - x = float64(i) * float64(cols-1) / float64(frames-1) - } - c0 := int(math.Floor(x)) - c1 := c0 + 1 - if c1 >= cols { - c1 = cols - 1 - } - t := x - float64(c0) - var sample, weight float64 - for bin := 0; bin < bins; bin++ { - amp := grid[bin][c0]*(1-t) + grid[bin][c1]*t - if amp < 0.002 { - continue - } - phase[bin] += inc[bin] - if phase[bin] > twoPi { - phase[bin] -= twoPi - } - sample += math.Sin(phase[bin]) * amp - weight += amp - } - if weight > 1 { - sample /= math.Sqrt(weight) - } - out[i] = sample - } - normalizePeak(out, opts.Gain) - fade(out, opts.SampleRate/100) - return out -} - -func renderSpectrogram(samples []float64, sampleRate int, opts ViewOptions) image.Image { - fftSize := opts.FFTSize - hop := fftSize / 4 - if hop < 1 { - hop = 1 - } - frameCount := 1 - if len(samples) > fftSize { - frameCount = 1 + (len(samples)-fftSize)/hop - } - width := frameCount - if width > opts.MaxWidth { - width = opts.MaxWidth - } - if width < 1 { - width = 1 - } - height := clampInt(int(math.Round(float64(opts.Height)*opts.Aspect)), 32, 4096) - img := image.NewRGBA(image.Rect(0, 0, width, height)) - values := make([]float32, width*height) - maxDB := math.Inf(-1) - window := hamming(fftSize) - for x := 0; x < width; x++ { - frameIndex := 0 - if width > 1 && frameCount > 1 { - frameIndex = int(math.Round(float64(x) * float64(frameCount-1) / float64(width-1))) - } - start := frameIndex * hop - buf := make([]complex128, fftSize) - for i := 0; i < fftSize; i++ { - if start+i < len(samples) { - buf[i] = complex(samples[start+i]*window[i], 0) - } - } - fft(buf) - for y := 0; y < height; y++ { - pos := 0.0 - if height > 1 { - pos = float64(height-1-y) / float64(height-1) - } - freq := opts.MinHz + (opts.MaxHz-opts.MinHz)*pos - bin := int(math.Round(freq * float64(fftSize) / float64(sampleRate))) - if bin < 0 { - bin = 0 - } - if bin >= fftSize/2 { - bin = fftSize/2 - 1 - } - mag := cmplxAbs(buf[bin]) / float64(fftSize) - db := 20 * math.Log10(mag+1e-10) - values[y*width+x] = float32(db) - if db > maxDB { - maxDB = db - } - } - } - if math.IsInf(maxDB, -1) { - maxDB = -80 - } - minDB := maxDB - 80 - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - db := float64(values[y*width+x]) - norm := clampFloat((db-minDB)/(maxDB-minDB), 0, 1) - norm = clampFloat((norm-0.5)*opts.Contrast+0.5, 0, 1) - norm = clampFloat(norm*opts.Brightness, 0, 1) - img.Set(x, y, palette(norm)) - } - } - return img -} - -func decodeWAV(data []byte) (audioBuffer, error) { - if !isWAV(data) { - return audioBuffer{}, errors.New("not a WAV file") - } - var fmtChunk, dataChunk []byte - pos := 12 - for pos+8 <= len(data) { - id := string(data[pos : pos+4]) - size := int(binary.LittleEndian.Uint32(data[pos+4 : pos+8])) - pos += 8 - if size < 0 || pos+size > len(data) { - return audioBuffer{}, errors.New("invalid WAV chunk size") - } - chunk := data[pos : pos+size] - switch id { - case "fmt ": - fmtChunk = chunk - case "data": - dataChunk = chunk - } - pos += size - if size%2 == 1 { - pos++ - } - } - if len(fmtChunk) < 16 || len(dataChunk) == 0 { - return audioBuffer{}, errors.New("WAV is missing fmt or data chunk") - } - audioFormat := binary.LittleEndian.Uint16(fmtChunk[0:2]) - channels := int(binary.LittleEndian.Uint16(fmtChunk[2:4])) - sampleRate := int(binary.LittleEndian.Uint32(fmtChunk[4:8])) - blockAlign := int(binary.LittleEndian.Uint16(fmtChunk[12:14])) - bits := int(binary.LittleEndian.Uint16(fmtChunk[14:16])) - if channels < 1 || channels > 8 || sampleRate < 1 || blockAlign < channels { - return audioBuffer{}, errors.New("unsupported WAV stream parameters") - } - if len(dataChunk) > maxDecodedBytes { - return audioBuffer{}, errors.New("audio data is too large to decode safely") - } - bytesPerSample := bits / 8 - if bytesPerSample < 1 || blockAlign < channels*bytesPerSample { - return audioBuffer{}, errors.New("unsupported WAV bit depth") - } - frames := len(dataChunk) / blockAlign - samples := make([]float64, frames*channels) - for frame := 0; frame < frames; frame++ { - base := frame * blockAlign - for ch := 0; ch < channels; ch++ { - off := base + ch*bytesPerSample - samples[frame*channels+ch] = decodeWAVSample(dataChunk[off:], audioFormat, bits) - } - } - return audioBuffer{SampleRate: sampleRate, Channels: channels, Samples: samples}, nil -} - -func decodeWAVSample(b []byte, audioFormat uint16, bits int) float64 { - switch audioFormat { - case 1: - switch bits { - case 8: - return (float64(b[0]) - 128) / 128 - case 16: - return float64(int16(binary.LittleEndian.Uint16(b[:2]))) / 32768 - case 24: - v := int32(b[0]) | int32(b[1])<<8 | int32(b[2])<<16 - if v&0x800000 != 0 { - v |= ^0xffffff - } - return float64(v) / 8388608 - case 32: - return float64(int32(binary.LittleEndian.Uint32(b[:4]))) / 2147483648 - } - case 3: - switch bits { - case 32: - return float64(math.Float32frombits(binary.LittleEndian.Uint32(b[:4]))) - case 64: - return math.Float64frombits(binary.LittleEndian.Uint64(b[:8])) - } - } - return 0 -} - -func decodeMP3(data []byte) (audioBuffer, error) { - decoder, err := mp3.NewDecoder(bytes.NewReader(data)) - if err != nil { - return audioBuffer{}, err - } - limited := io.LimitReader(decoder, maxDecodedBytes+1) - pcm, err := io.ReadAll(limited) - if err != nil { - return audioBuffer{}, err - } - if len(pcm) > maxDecodedBytes { - return audioBuffer{}, errors.New("decoded MP3 is too large to process safely") - } - if len(pcm) < 2 { - return audioBuffer{}, errors.New("MP3 decoded no samples") - } - sampleCount := len(pcm) / 2 - channels := 2 - if sampleCount%2 != 0 { - channels = 1 - } - samples := make([]float64, sampleCount) - for i := 0; i < sampleCount; i++ { - samples[i] = float64(int16(binary.LittleEndian.Uint16(pcm[i*2:i*2+2]))) / 32768 - } - return audioBuffer{SampleRate: decoder.SampleRate(), Channels: channels, Samples: samples}, nil -} - -func encodeWAV(audio audioBuffer) ([]byte, error) { - if audio.SampleRate <= 0 || audio.Channels <= 0 { - return nil, errors.New("invalid audio parameters") - } - var buf bytes.Buffer - dataSize := int64(len(audio.Samples)) * 2 - if dataSize > int64(^uint32(0))-44 { - return nil, errors.New("audio is too large for a WAV file") - } - buf.WriteString("RIFF") - _ = binary.Write(&buf, binary.LittleEndian, uint32(36+dataSize)) - buf.WriteString("WAVE") - buf.WriteString("fmt ") - _ = binary.Write(&buf, binary.LittleEndian, uint32(16)) - _ = binary.Write(&buf, binary.LittleEndian, uint16(1)) - _ = binary.Write(&buf, binary.LittleEndian, uint16(audio.Channels)) - _ = binary.Write(&buf, binary.LittleEndian, uint32(audio.SampleRate)) - byteRate := audio.SampleRate * audio.Channels * 2 - _ = binary.Write(&buf, binary.LittleEndian, uint32(byteRate)) - _ = binary.Write(&buf, binary.LittleEndian, uint16(audio.Channels*2)) - _ = binary.Write(&buf, binary.LittleEndian, uint16(16)) - buf.WriteString("data") - _ = binary.Write(&buf, binary.LittleEndian, uint32(dataSize)) - for _, sample := range audio.Samples { - sample = clampFloat(sample, -1, 1) - _ = binary.Write(&buf, binary.LittleEndian, int16(math.Round(sample*32767))) - } - return buf.Bytes(), nil -} - -func toMono(audio audioBuffer) []float64 { - if audio.Channels == 1 { - out := make([]float64, len(audio.Samples)) - copy(out, audio.Samples) - return out - } - frames := len(audio.Samples) / audio.Channels - out := make([]float64, frames) - for frame := 0; frame < frames; frame++ { - var sum float64 - for ch := 0; ch < audio.Channels; ch++ { - sum += audio.Samples[frame*audio.Channels+ch] - } - out[frame] = sum / float64(audio.Channels) - } - return out -} - -func isWAV(data []byte) bool { - return len(data) >= 12 && string(data[:4]) == "RIFF" && string(data[8:12]) == "WAVE" -} - -func isMP3(data []byte) bool { - return len(data) >= 3 && (string(data[:3]) == "ID3" || (data[0] == 0xff && data[1]&0xe0 == 0xe0)) -} - -func normalizePeak(samples []float64, target float64) { - var peak float64 - for _, s := range samples { - if a := math.Abs(s); a > peak { - peak = a - } - } - if peak <= 0 { - return - } - scale := target / peak - for i := range samples { - samples[i] *= scale - } -} - -func limitPeak(samples []float64, target float64) { - var peak float64 - for _, s := range samples { - if a := math.Abs(s); a > peak { - peak = a - } - } - if peak <= target || peak <= 0 { - return - } - scale := target / peak - for i := range samples { - samples[i] *= scale - } -} - -func fade(samples []float64, fadeLen int) { - if fadeLen <= 0 || len(samples) == 0 { - return - } - if fadeLen*2 > len(samples) { - fadeLen = len(samples) / 2 - } - for i := 0; i < fadeLen; i++ { - gain := float64(i) / float64(fadeLen) - samples[i] *= gain - samples[len(samples)-1-i] *= gain - } -} - -func hamming(n int) []float64 { - w := make([]float64, n) - if n <= 1 { - if n == 1 { - w[0] = 1 - } - return w - } - for i := range w { - w[i] = 0.54 - 0.46*math.Cos(twoPi*float64(i)/float64(n-1)) - } - return w -} - -func fft(a []complex128) { - n := len(a) - j := 0 - for i := 1; i < n; i++ { - bit := n >> 1 - for ; j&bit != 0; bit >>= 1 { - j &^= bit - } - j |= bit - if i < j { - a[i], a[j] = a[j], a[i] - } - } - for length := 2; length <= n; length <<= 1 { - angle := -twoPi / float64(length) - wlen := complex(math.Cos(angle), math.Sin(angle)) - for i := 0; i < n; i += length { - w := complex(1, 0) - for k := 0; k < length/2; k++ { - u := a[i+k] - v := a[i+k+length/2] * w - a[i+k] = u + v - a[i+k+length/2] = u - v - w *= wlen - } - } - } -} - -func cmplxAbs(c complex128) float64 { - return math.Hypot(real(c), imag(c)) -} - -func palette(v float64) color.RGBA { - v = clampFloat(v, 0, 1) - colors := []color.RGBA{ - {R: 68, G: 1, B: 84, A: 255}, - {R: 72, G: 40, B: 120, A: 255}, - {R: 62, G: 73, B: 137, A: 255}, - {R: 49, G: 104, B: 142, A: 255}, - {R: 38, G: 130, B: 142, A: 255}, - {R: 31, G: 158, B: 137, A: 255}, - {R: 53, G: 183, B: 121, A: 255}, - {R: 110, G: 206, B: 88, A: 255}, - {R: 181, G: 222, B: 43, A: 255}, - {R: 253, G: 231, B: 37, A: 255}, - } - scaled := v * float64(len(colors)-1) - idx := int(math.Floor(scaled)) - if idx >= len(colors)-1 { - return colors[len(colors)-1] - } - t := scaled - float64(idx) - a, b := colors[idx], colors[idx+1] - return color.RGBA{ - R: uint8(float64(a.R) + (float64(b.R)-float64(a.R))*t), - G: uint8(float64(a.G) + (float64(b.G)-float64(a.G))*t), - B: uint8(float64(a.B) + (float64(b.B)-float64(a.B))*t), - A: 255, - } -} - -func nearestPowerOfTwo(n int) int { - if n <= 1 { - return 1 - } - p := 1 - for p < n { - p <<= 1 - } - prev := p >> 1 - if p-n < n-prev { - return p - } - return prev -} - -func clampInt(v, min, max int) int { - if v < min { - return min - } - if v > max { - return max - } - return v -} - -func clampFloat(v, min, max float64) float64 { - if v < min { - return min - } - if v > max { - return max - } - return v -} diff --git a/internal/leviathan/spectrogram/spectrogram_test.go b/internal/leviathan/spectrogram/spectrogram_test.go deleted file mode 100644 index 0507c64..0000000 --- a/internal/leviathan/spectrogram/spectrogram_test.go +++ /dev/null @@ -1,177 +0,0 @@ -package spectrogram - -import ( - "bytes" - "encoding/binary" - "hash/crc32" - "image" - "image/color" - "image/png" - "testing" -) - -func TestImageToWAVAndRenderSpectrogram(t *testing.T) { - img := testPNG(t) - wav, summary, err := ImageToWAV(img, EncodeOptions{ - DurationSec: 0.25, - SampleRate: 8000, - MinHz: 300, - MaxHz: 3000, - Bins: 32, - Columns: 32, - Invert: true, - }) - if err != nil { - t.Fatalf("ImageToWAV failed: %v", err) - } - if !isWAV(wav) { - t.Fatalf("expected WAV header") - } - if summary["wavBytes"].(int) != len(wav) { - t.Fatalf("summary wavBytes mismatch") - } - - rendered, viewSummary, err := AudioSpectrogramPNG(wav, "test.wav", ViewOptions{ - FFTSize: 512, - Height: 96, - Aspect: 2, - MaxWidth: 128, - }) - if err != nil { - t.Fatalf("AudioSpectrogramPNG failed: %v", err) - } - if _, err := png.Decode(bytes.NewReader(rendered)); err != nil { - t.Fatalf("rendered spectrogram is not a PNG: %v", err) - } - if viewSummary["sampleRate"].(int) != 8000 { - t.Fatalf("unexpected sample rate: %v", viewSummary["sampleRate"]) - } - if viewSummary["height"].(int) != 192 { - t.Fatalf("expected aspect-adjusted height 192, got %v", viewSummary["height"]) - } -} - -func TestPreserveAspectRatioAdjustsFrequencyRange(t *testing.T) { - img := testPNGSize(t, 32, 16) - _, summary, err := ImageToWAV(img, EncodeOptions{ - DurationSec: 0.1, - SampleRate: 8000, - MinHz: 200, - MaxHz: 3000, - Bins: 16, - Columns: 16, - PreserveAR: true, - Invert: true, - }) - if err != nil { - t.Fatalf("ImageToWAV failed: %v", err) - } - if summary["preserveAR"] != true { - t.Fatalf("expected preserveAR summary") - } - if got := summary["maxHz"].(float64); got != 1600 { - t.Fatalf("expected landscape image to halve frequency range to 1600Hz, got %v", got) - } -} - -func TestImageToWAVRejectsOversizedImageDimensionsBeforeDecode(t *testing.T) { - _, _, err := ImageToWAV(hugePNGHeader(t, 100000, 100000), EncodeOptions{}) - if err == nil { - t.Fatal("expected oversized image dimensions to be rejected") - } - if !bytes.Contains([]byte(err.Error()), []byte("image dimensions are too large")) { - t.Fatalf("unexpected error: %v", err) - } -} - -func TestInsertImageIntoAudioExtendsCarrier(t *testing.T) { - img := testPNG(t) - carrier, _, err := ImageToWAV(img, EncodeOptions{ - DurationSec: 0.1, - SampleRate: 8000, - MinHz: 300, - MaxHz: 2500, - Bins: 16, - Columns: 16, - Invert: true, - }) - if err != nil { - t.Fatalf("carrier generation failed: %v", err) - } - out, summary, err := InsertImageIntoAudio(img, carrier, "carrier.wav", InsertOptions{ - EncodeOptions: EncodeOptions{ - DurationSec: 0.2, - MinHz: 300, - MaxHz: 2500, - Bins: 16, - Columns: 16, - Invert: true, - }, - StartSec: 0.05, - MixLevel: 0.25, - }) - if err != nil { - t.Fatalf("InsertImageIntoAudio failed: %v", err) - } - if !isWAV(out) { - t.Fatalf("expected WAV output") - } - if summary["mode"] != "insert-into-audio" { - t.Fatalf("unexpected mode: %v", summary["mode"]) - } - if len(out) <= len(carrier) { - t.Fatalf("expected carrier to be extended") - } -} - -func testPNG(t *testing.T) []byte { - t.Helper() - return testPNGSize(t, 32, 32) -} - -func testPNGSize(t *testing.T, width, height int) []byte { - t.Helper() - img := image.NewRGBA(image.Rect(0, 0, width, height)) - for y := 0; y < height; y++ { - for x := 0; x < width; x++ { - img.Set(x, y, color.RGBA{R: 255, G: 255, B: 255, A: 255}) - } - } - limit := width - if height < limit { - limit = height - } - for i := 0; i < limit; i++ { - img.Set(i, i, color.RGBA{A: 255}) - img.Set(width-1-i, i, color.RGBA{A: 255}) - } - var buf bytes.Buffer - if err := png.Encode(&buf, img); err != nil { - t.Fatalf("encode png: %v", err) - } - return buf.Bytes() -} - -func hugePNGHeader(t *testing.T, width, height uint32) []byte { - t.Helper() - var buf bytes.Buffer - buf.Write([]byte{0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n'}) - ihdr := make([]byte, 13) - binary.BigEndian.PutUint32(ihdr[0:4], width) - binary.BigEndian.PutUint32(ihdr[4:8], height) - ihdr[8] = 8 - ihdr[9] = 2 - writePNGChunk(&buf, "IHDR", ihdr) - writePNGChunk(&buf, "IEND", nil) - return buf.Bytes() -} - -func writePNGChunk(buf *bytes.Buffer, kind string, data []byte) { - _ = binary.Write(buf, binary.BigEndian, uint32(len(data))) - buf.WriteString(kind) - buf.Write(data) - crc := crc32.NewIEEE() - crc.Write([]byte(kind)) - crc.Write(data) - _ = binary.Write(buf, binary.BigEndian, crc.Sum32()) -} diff --git a/internal/leviathan/squashfs/squashfs.go b/internal/leviathan/squashfs/squashfs.go deleted file mode 100644 index 75a010e..0000000 --- a/internal/leviathan/squashfs/squashfs.go +++ /dev/null @@ -1,782 +0,0 @@ -// Package squashfs implements a read-only SquashFS 4.0 extractor. -// -// Scope is deliberately pragmatic: gzip, xz and zstd-compressed images, -// basic and extended file and directory inodes, symlink inodes. Other -// compressors (lzma/lzo/lz4) return a structured error so the UI can show -// what's missing. -// -// The module never trusts its input: every table offset and length is -// bounded against the image size and a hard cap on uncompressed metadata -// block size. A malformed or truncated image returns an error rather -// than causing a wild read. -package squashfs - -import ( - "bytes" - "compress/zlib" - "encoding/binary" - "errors" - "fmt" - "io" - "path" - "sort" - - "github.com/andydixon/link/internal/leviathan/document" - "github.com/klauspost/compress/zstd" - "github.com/ulikunitz/xz" -) - -const ( - magicLE uint32 = 0x73717368 // "hsqs" - metadataBlockSize = 8192 - dataBlockMax = 1 << 20 // 1 MiB guard for a single data block - maxEntries = 1 << 20 // total files + dirs we're willing to walk -) - -// Compression IDs from the spec. -const ( - compGZIP = 1 - compLZMA = 2 - compLZO = 3 - compXZ = 4 - compLZ4 = 5 - compZSTD = 6 -) - -// Inode type codes. -const ( - itBasicDir = 1 - itBasicFile = 2 - itBasicSymlink = 3 - itBasicBlockDev = 4 - itBasicCharDev = 5 - itBasicFifo = 6 - itBasicSocket = 7 - itExtDir = 8 - itExtFile = 9 - itExtSymlink = 10 - itExtBlockDev = 11 - itExtCharDev = 12 - itExtFifo = 13 - itExtSocket = 14 -) - -// Superblock is the parsed 96-byte header. -type Superblock struct { - Magic uint32 - InodeCount uint32 - ModTime uint32 - BlockSize uint32 - FragCount uint32 - CompressionID uint16 - BlockLog uint16 - Flags uint16 - IDCount uint16 - VersionMajor uint16 - VersionMinor uint16 - RootInodeRef uint64 - BytesUsed uint64 - IDTableStart uint64 - XattrTableStart uint64 - InodeTable uint64 - DirTable uint64 - FragTable uint64 - ExportTable uint64 -} - -// Entry describes a filesystem entry discovered by the walker. -type Entry struct { - Path string - Type string // "file", "dir", "symlink", "special" - Size int64 - Mode uint16 - UID uint16 // id_idx (not resolved) - GID uint16 - Mtime uint32 - InodeNumber uint32 - InodeRef uint64 // 64-bit metadata reference (for targeted extraction) - LinkTarget string // for symlinks - // For file extraction; zero for non-files. - blocksStart uint64 - fileSize int64 - fragIndex uint32 - fragOffset uint32 - blockSizes []uint32 // raw (top bit=uncompressed) -} - -// FS is the opened image handle. -type FS struct { - d *document.Document - sb Superblock -} - -// Open validates the superblock and returns an FS for further queries. -func Open(d *document.Document) (*FS, error) { - head, err := d.Range(0, 96) - if err != nil { - return nil, fmt.Errorf("read superblock: %w", err) - } - sb, err := parseSuperblock(head) - if err != nil { - return nil, err - } - if sb.VersionMajor != 4 { - return nil, fmt.Errorf("unsupported squashfs version %d.%d (only 4.x)", sb.VersionMajor, sb.VersionMinor) - } - if sb.BytesUsed > uint64(d.Size()) { - return nil, fmt.Errorf("superblock bytes_used=%d exceeds document size=%d", sb.BytesUsed, d.Size()) - } - return &FS{d: d, sb: sb}, nil -} - -// Superblock returns the parsed header (copy). -func (f *FS) Superblock() Superblock { return f.sb } - -// CompressionName returns the human name of the image compressor. -func (f *FS) CompressionName() string { - return compressionName(f.sb.CompressionID) -} - -func compressionName(id uint16) string { - switch id { - case compGZIP: - return "gzip" - case compLZMA: - return "lzma" - case compLZO: - return "lzo" - case compXZ: - return "xz" - case compLZ4: - return "lz4" - case compZSTD: - return "zstd" - } - return fmt.Sprintf("unknown(%d)", id) -} - -func parseSuperblock(b []byte) (Superblock, error) { - var s Superblock - if len(b) < 96 { - return s, errors.New("superblock: short read") - } - s.Magic = binary.LittleEndian.Uint32(b[0:4]) - if s.Magic != magicLE { - return s, fmt.Errorf("bad magic 0x%08x (want 0x%08x)", s.Magic, magicLE) - } - s.InodeCount = binary.LittleEndian.Uint32(b[4:8]) - s.ModTime = binary.LittleEndian.Uint32(b[8:12]) - s.BlockSize = binary.LittleEndian.Uint32(b[12:16]) - s.FragCount = binary.LittleEndian.Uint32(b[16:20]) - s.CompressionID = binary.LittleEndian.Uint16(b[20:22]) - s.BlockLog = binary.LittleEndian.Uint16(b[22:24]) - s.Flags = binary.LittleEndian.Uint16(b[24:26]) - s.IDCount = binary.LittleEndian.Uint16(b[26:28]) - s.VersionMajor = binary.LittleEndian.Uint16(b[28:30]) - s.VersionMinor = binary.LittleEndian.Uint16(b[30:32]) - s.RootInodeRef = binary.LittleEndian.Uint64(b[32:40]) - s.BytesUsed = binary.LittleEndian.Uint64(b[40:48]) - s.IDTableStart = binary.LittleEndian.Uint64(b[48:56]) - s.XattrTableStart = binary.LittleEndian.Uint64(b[56:64]) - s.InodeTable = binary.LittleEndian.Uint64(b[64:72]) - s.DirTable = binary.LittleEndian.Uint64(b[72:80]) - s.FragTable = binary.LittleEndian.Uint64(b[80:88]) - s.ExportTable = binary.LittleEndian.Uint64(b[88:96]) - return s, nil -} - -// ---------- metadata block reader ---------- - -func (f *FS) decompress(buf []byte) ([]byte, error) { - return f.decompressWithLimit(buf, metadataBlockSize+16) -} - -func (f *FS) decompressWithLimit(buf []byte, limit int64) ([]byte, error) { - var r io.ReadCloser - switch f.sb.CompressionID { - case compGZIP: - zr, err := zlib.NewReader(bytes.NewReader(buf)) - if err != nil { - return nil, fmt.Errorf("zlib init: %w", err) - } - r = zr - case compXZ: - xr, err := xz.NewReader(bytes.NewReader(buf)) - if err != nil { - return nil, fmt.Errorf("xz init: %w", err) - } - r = io.NopCloser(xr) - case compZSTD: - zr, err := zstd.NewReader(bytes.NewReader(buf)) - if err != nil { - return nil, fmt.Errorf("zstd init: %w", err) - } - r = zr.IOReadCloser() - default: - return nil, fmt.Errorf("compressor %q not implemented in this build", compressionName(f.sb.CompressionID)) - } - defer r.Close() - return readLimited(r, limit) -} - -// readMetadataBlock reads one metadata block starting at absolute offset -// `off` and returns (data, bytesConsumed, error). bytesConsumed includes -// the 2-byte header. -func (f *FS) readMetadataBlock(off int64) ([]byte, int64, error) { - if off < 0 || off+2 > f.d.Size() { - return nil, 0, fmt.Errorf("metadata block offset %d out of range", off) - } - hdrBuf, err := f.d.Range(off, 2) - if err != nil { - return nil, 0, err - } - hdr := binary.LittleEndian.Uint16(hdrBuf) - compressed := hdr&0x8000 == 0 - size := int64(hdr & 0x7fff) - if size == 0 || size > metadataBlockSize { - return nil, 0, fmt.Errorf("metadata block size %d invalid", size) - } - body, err := f.d.Range(off+2, size) - if err != nil { - return nil, 0, err - } - if !compressed { - return append([]byte(nil), body...), size + 2, nil - } - out, err := f.decompress(body) - if err != nil { - return nil, 0, err - } - return out, size + 2, nil -} - -// metaCursor streams uncompressed data out of a chain of metadata blocks. -// table[i] is the absolute file offset of the i-th metadata block, plus -// a running byte cursor within the uncompressed stream. -type metaCursor struct { - f *FS - base int64 // absolute file offset of first metadata block - blkAbs int64 // absolute file offset of *current* metadata block - inBlk int // index within current uncompressed block - buffer []byte // current decompressed block -} - -func (f *FS) metaCursorAt(base int64, blockOffset uint32, inBlk uint16) (*metaCursor, error) { - mc := &metaCursor{f: f, base: base, blkAbs: base + int64(blockOffset)} - if err := mc.load(); err != nil { - return nil, err - } - if int(inBlk) > len(mc.buffer) { - return nil, fmt.Errorf("inode intra-block offset %d > block length %d", inBlk, len(mc.buffer)) - } - mc.inBlk = int(inBlk) - return mc, nil -} - -func (mc *metaCursor) load() error { - data, _, err := mc.f.readMetadataBlock(mc.blkAbs) - if err != nil { - return err - } - mc.buffer = data - mc.inBlk = 0 - return nil -} - -// advance one metadata block. The on-disk size is the consumed length of -// the current compressed block, which we must read from the header again. -func (mc *metaCursor) nextBlock() error { - // Re-read just the 2-byte header of current block to find its length. - hdrBuf, err := mc.f.d.Range(mc.blkAbs, 2) - if err != nil { - return err - } - hdr := binary.LittleEndian.Uint16(hdrBuf) - size := int64(hdr & 0x7fff) - mc.blkAbs += 2 + size - return mc.load() -} - -// readN returns n uncompressed bytes, crossing metadata-block boundaries -// as required. -func (mc *metaCursor) readN(n int) ([]byte, error) { - if n <= 0 { - return nil, nil - } - out := make([]byte, 0, n) - for n > 0 { - if mc.inBlk >= len(mc.buffer) { - if err := mc.nextBlock(); err != nil { - return nil, err - } - } - take := len(mc.buffer) - mc.inBlk - if take > n { - take = n - } - out = append(out, mc.buffer[mc.inBlk:mc.inBlk+take]...) - mc.inBlk += take - n -= take - } - return out, nil -} - -// ---------- inode ---------- - -type inodeHeader struct { - Type uint16 - Mode uint16 - UID uint16 - GID uint16 - Mtime uint32 - Num uint32 -} - -func readInodeHeader(b []byte) (inodeHeader, error) { - var h inodeHeader - if len(b) < 16 { - return h, errors.New("inode header short") - } - h.Type = binary.LittleEndian.Uint16(b[0:2]) - h.Mode = binary.LittleEndian.Uint16(b[2:4]) - h.UID = binary.LittleEndian.Uint16(b[4:6]) - h.GID = binary.LittleEndian.Uint16(b[6:8]) - h.Mtime = binary.LittleEndian.Uint32(b[8:12]) - h.Num = binary.LittleEndian.Uint32(b[12:16]) - return h, nil -} - -// readInodeAt reads the inode described by a 64-bit ref and returns the -// filled Entry plus the metadata location of the directory contents (if -// it's a directory). -type inodeContent struct { - entry Entry - dirStart uint32 // offset into DirTable (bytes from sb.DirTable) - dirBlockOff uint16 // intra-metadata-block offset for first dir header - dirSize uint32 // directory listing size, real value (file_size - 3) - parentInode uint32 -} - -func (f *FS) readInode(ref uint64) (*inodeContent, error) { - blkOff := uint32(ref >> 16) - inBlk := uint16(ref & 0xffff) - mc, err := f.metaCursorAt(int64(f.sb.InodeTable), blkOff, inBlk) - if err != nil { - return nil, err - } - hdrBytes, err := mc.readN(16) - if err != nil { - return nil, err - } - h, err := readInodeHeader(hdrBytes) - if err != nil { - return nil, err - } - ic := &inodeContent{ - entry: Entry{ - Mode: h.Mode, - UID: h.UID, - GID: h.GID, - Mtime: h.Mtime, - InodeNumber: h.Num, - InodeRef: ref, - }, - } - switch h.Type { - case itBasicDir: - body, err := mc.readN(16) - if err != nil { - return nil, err - } - ic.entry.Type = "dir" - ic.dirStart = binary.LittleEndian.Uint32(body[0:4]) - // nlink := binary.LittleEndian.Uint32(body[4:8]) - fileSize := binary.LittleEndian.Uint16(body[8:10]) - ic.dirBlockOff = binary.LittleEndian.Uint16(body[10:12]) - ic.parentInode = binary.LittleEndian.Uint32(body[12:16]) - // file_size in dir inodes is real dir bytes + 3. Zero-size (3) → empty dir. - if fileSize >= 3 { - ic.dirSize = uint32(fileSize) - 3 - } - case itExtDir: - body, err := mc.readN(24) - if err != nil { - return nil, err - } - ic.entry.Type = "dir" - // nlink := binary.LittleEndian.Uint32(body[0:4]) - fileSize := binary.LittleEndian.Uint32(body[4:8]) - ic.dirStart = binary.LittleEndian.Uint32(body[8:12]) - ic.parentInode = binary.LittleEndian.Uint32(body[12:16]) - indexCount := binary.LittleEndian.Uint16(body[16:18]) - ic.dirBlockOff = binary.LittleEndian.Uint16(body[18:20]) - // xattr := binary.LittleEndian.Uint32(body[20:24]) - if fileSize >= 3 { - ic.dirSize = fileSize - 3 - } - // Skip index entries: indexCount × (4 + 4 + 4 + name[...variable]). - for i := uint16(0); i < indexCount; i++ { - hdr, err := mc.readN(12) - if err != nil { - return nil, err - } - nameSize := binary.LittleEndian.Uint32(hdr[8:12]) - if nameSize > 256 { - return nil, fmt.Errorf("dir index name_size too large: %d", nameSize) - } - if _, err := mc.readN(int(nameSize) + 1); err != nil { - return nil, err - } - } - case itBasicFile: - body, err := mc.readN(16) - if err != nil { - return nil, err - } - ic.entry.Type = "file" - ic.entry.blocksStart = uint64(binary.LittleEndian.Uint32(body[0:4])) - ic.entry.fragIndex = binary.LittleEndian.Uint32(body[4:8]) - ic.entry.fragOffset = binary.LittleEndian.Uint32(body[8:12]) - fileSize := binary.LittleEndian.Uint32(body[12:16]) - ic.entry.fileSize = int64(fileSize) - ic.entry.Size = int64(fileSize) - blocks, err := readBlockSizes(mc, fileSize, ic.entry.fragIndex, f.sb.BlockSize) - if err != nil { - return nil, err - } - ic.entry.blockSizes = blocks - case itExtFile: - body, err := mc.readN(40) - if err != nil { - return nil, err - } - ic.entry.Type = "file" - ic.entry.blocksStart = binary.LittleEndian.Uint64(body[0:8]) - fileSize := binary.LittleEndian.Uint64(body[8:16]) - // sparse := binary.LittleEndian.Uint64(body[16:24]) - // nlink := binary.LittleEndian.Uint32(body[24:28]) - ic.entry.fragIndex = binary.LittleEndian.Uint32(body[28:32]) - ic.entry.fragOffset = binary.LittleEndian.Uint32(body[32:36]) - // xattr := binary.LittleEndian.Uint32(body[36:40]) - ic.entry.fileSize = int64(fileSize) - ic.entry.Size = int64(fileSize) - if fileSize > uint64(1)<<48 { - return nil, fmt.Errorf("unreasonable file size %d", fileSize) - } - blocks, err := readBlockSizes(mc, uint32(fileSize), ic.entry.fragIndex, f.sb.BlockSize) - if err != nil { - return nil, err - } - ic.entry.blockSizes = blocks - case itBasicSymlink, itExtSymlink: - body, err := mc.readN(8) - if err != nil { - return nil, err - } - // nlink := binary.LittleEndian.Uint32(body[0:4]) - target := binary.LittleEndian.Uint32(body[4:8]) - if target > 4096 { - return nil, fmt.Errorf("symlink target length %d too large", target) - } - link, err := mc.readN(int(target)) - if err != nil { - return nil, err - } - ic.entry.Type = "symlink" - ic.entry.LinkTarget = string(link) - ic.entry.Size = int64(target) - if h.Type == itExtSymlink { - // trailing xattr u32 - if _, err := mc.readN(4); err != nil { - return nil, err - } - } - default: - ic.entry.Type = "special" - } - return ic, nil -} - -func readBlockSizes(mc *metaCursor, fileSize uint32, fragIdx uint32, blockSize uint32) ([]uint32, error) { - // If there is a fragment (fragIdx != 0xFFFFFFFF), the tail of the file - // (< blockSize) is stored there — do not count it as a full block. - full := fileSize / blockSize - if fragIdx == 0xFFFFFFFF && fileSize%blockSize != 0 { - full++ // final partial block stored on disk as a block - } - if full > (1 << 20) { - return nil, fmt.Errorf("unreasonable block count %d", full) - } - out := make([]uint32, full) - for i := range out { - b, err := mc.readN(4) - if err != nil { - return nil, err - } - out[i] = binary.LittleEndian.Uint32(b) - } - return out, nil -} - -// ---------- directory walking ---------- - -// readDirEntries returns all (name, inode-ref) tuples in a directory. -func (f *FS) readDirEntries(parent *inodeContent) ([]dirEntry, error) { - if parent.dirSize == 0 { - return nil, nil - } - // dirStart is an offset into the dir table (relative to sb.DirTable). - // dirBlockOff is the intra-block start. - mc, err := f.metaCursorAt(int64(f.sb.DirTable), parent.dirStart, parent.dirBlockOff) - if err != nil { - return nil, err - } - remaining := int(parent.dirSize) - var out []dirEntry - for remaining > 0 { - hdr, err := mc.readN(12) - if err != nil { - return nil, err - } - remaining -= 12 - count := binary.LittleEndian.Uint32(hdr[0:4]) + 1 // stored as count-1 - startBlock := binary.LittleEndian.Uint32(hdr[4:8]) - baseInode := binary.LittleEndian.Uint32(hdr[8:12]) - for i := uint32(0); i < count; i++ { - entHdr, err := mc.readN(8) - if err != nil { - return nil, err - } - remaining -= 8 - entOff := binary.LittleEndian.Uint16(entHdr[0:2]) - iDelta := int16(binary.LittleEndian.Uint16(entHdr[2:4])) - entType := binary.LittleEndian.Uint16(entHdr[4:6]) - nameSize := binary.LittleEndian.Uint16(entHdr[6:8]) - if nameSize+1 > 256 { - return nil, fmt.Errorf("directory entry name_size %d too large", nameSize) - } - name, err := mc.readN(int(nameSize) + 1) // stored as name_size-1 - if err != nil { - return nil, err - } - remaining -= int(nameSize) + 1 - ref := (uint64(startBlock) << 16) | uint64(entOff) - out = append(out, dirEntry{ - name: string(name), - ref: ref, - inodeNum: uint32(int64(baseInode) + int64(iDelta)), - entType: entType, - }) - if len(out) > maxEntries { - return nil, fmt.Errorf("directory entry cap %d exceeded", maxEntries) - } - } - } - return out, nil -} - -type dirEntry struct { - name string - ref uint64 - inodeNum uint32 - entType uint16 -} - -// Walk walks the entire filesystem from the root, emitting every Entry. -// The returned list is sorted by path. Walk is bounded by maxEntries -// and by recursion depth. -func (f *FS) Walk() ([]Entry, error) { - root, err := f.readInode(f.sb.RootInodeRef) - if err != nil { - return nil, fmt.Errorf("root inode: %w", err) - } - if root.entry.Type != "dir" { - return nil, fmt.Errorf("root is not a directory (type %q)", root.entry.Type) - } - out := []Entry{} - root.entry.Path = "/" - out = append(out, root.entry) - if err := f.walk(root, "/", &out, 0); err != nil { - return nil, err - } - sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path }) - return out, nil -} - -func (f *FS) walk(node *inodeContent, prefix string, out *[]Entry, depth int) error { - if depth > 64 { - return errors.New("directory depth exceeds 64 — possible cycle") - } - entries, err := f.readDirEntries(node) - if err != nil { - return err - } - for _, de := range entries { - child, err := f.readInode(de.ref) - if err != nil { - return fmt.Errorf("%s/%s: %w", prefix, de.name, err) - } - full := path.Join(prefix, de.name) - child.entry.Path = full - *out = append(*out, child.entry) - if len(*out) > maxEntries { - return fmt.Errorf("entry cap %d exceeded", maxEntries) - } - if child.entry.Type == "dir" { - if err := f.walk(child, full, out, depth+1); err != nil { - return err - } - } - } - return nil -} - -// ---------- file extraction ---------- - -// fragmentEntry describes one entry in the fragment table. -type fragmentEntry struct { - Start uint64 - Size uint32 // top bit = uncompressed - Compressed bool -} - -// fragmentTable reads the entire fragment lookup table and returns the -// flat list of entries by index. Result is cached on first call. -func (f *FS) fragmentTable() ([]fragmentEntry, error) { - if f.sb.FragCount == 0 { - return nil, nil - } - // The fragment_table_start points at a u64[] of metadata-block offsets, - // one per 512 fragments (ceil). - indirCount := (int(f.sb.FragCount) + 511) / 512 - indirBytes, err := f.d.Range(int64(f.sb.FragTable), int64(indirCount*8)) - if err != nil { - return nil, fmt.Errorf("frag indirection: %w", err) - } - out := make([]fragmentEntry, 0, f.sb.FragCount) - remaining := int(f.sb.FragCount) - for i := 0; i < indirCount && remaining > 0; i++ { - absOff := int64(binary.LittleEndian.Uint64(indirBytes[i*8 : i*8+8])) - block, _, err := f.readMetadataBlock(absOff) - if err != nil { - return nil, err - } - // Each fragment entry is 16 bytes: u64 start, u32 size, u32 unused. - take := remaining - if take > 512 { - take = 512 - } - if len(block) < take*16 { - return nil, fmt.Errorf("fragment block short: have %d need %d", len(block), take*16) - } - for j := 0; j < take; j++ { - off := j * 16 - sz := binary.LittleEndian.Uint32(block[off+8 : off+12]) - out = append(out, fragmentEntry{ - Start: binary.LittleEndian.Uint64(block[off : off+8]), - Size: sz & 0x00ffffff, // documented size field occupies low 24 bits - Compressed: sz&0x01000000 == 0, - }) - } - remaining -= take - } - return out, nil -} - -// ExtractFile returns the full decompressed contents of the file at the -// given InodeRef. Returns an error if the inode is not a file. -func (f *FS) ExtractFile(ref uint64) ([]byte, error) { - ic, err := f.readInode(ref) - if err != nil { - return nil, err - } - if ic.entry.Type != "file" { - return nil, fmt.Errorf("inode %d is not a file (%s)", ic.entry.InodeNumber, ic.entry.Type) - } - out := make([]byte, 0, ic.entry.fileSize) - cursor := int64(ic.entry.blocksStart) - for i, bs := range ic.entry.blockSizes { - size := int64(bs & 0x00ffffff) - compressed := bs&0x01000000 == 0 - if size == 0 { - // sparse hole: emit blockSize zeroes (or trailing partial) - remaining := ic.entry.fileSize - int64(len(out)) - n := int64(f.sb.BlockSize) - if remaining < n { - n = remaining - } - out = append(out, make([]byte, n)...) - continue - } - if size > dataBlockMax { - return nil, fmt.Errorf("data block %d size %d exceeds cap", i, size) - } - body, err := f.d.Range(cursor, size) - if err != nil { - return nil, fmt.Errorf("data block %d: %w", i, err) - } - cursor += size - if !compressed { - out = append(out, body...) - continue - } - dec, err := f.decompressBlock(body) - if err != nil { - return nil, fmt.Errorf("data block %d: %w", i, err) - } - out = append(out, dec...) - } - if ic.entry.fragIndex != 0xFFFFFFFF { - frags, err := f.fragmentTable() - if err != nil { - return nil, err - } - if int(ic.entry.fragIndex) >= len(frags) { - return nil, fmt.Errorf("fragment index %d out of range (%d entries)", ic.entry.fragIndex, len(frags)) - } - fe := frags[ic.entry.fragIndex] - fragBody, err := f.d.Range(int64(fe.Start), int64(fe.Size)) - if err != nil { - return nil, fmt.Errorf("fragment data: %w", err) - } - var payload []byte - if fe.Compressed { - payload, err = f.decompressBlock(fragBody) - if err != nil { - return nil, err - } - } else { - payload = fragBody - } - tailLen := int(ic.entry.fileSize) - len(out) - if int(ic.entry.fragOffset)+tailLen > len(payload) { - return nil, fmt.Errorf("fragment slice out of range: off=%d len=%d payload=%d", - ic.entry.fragOffset, tailLen, len(payload)) - } - out = append(out, payload[ic.entry.fragOffset:int(ic.entry.fragOffset)+tailLen]...) - } - if int64(len(out)) != ic.entry.fileSize { - return nil, fmt.Errorf("extraction length mismatch: got %d, want %d", len(out), ic.entry.fileSize) - } - return out, nil -} - -// decompressBlock is like decompress but bounded by dataBlockMax; used for fragment payloads. -func (f *FS) decompressBlock(buf []byte) ([]byte, error) { - return f.decompressWithLimit(buf, dataBlockMax) -} - -func readLimited(r io.Reader, limit int64) ([]byte, error) { - if limit <= 0 { - return nil, errors.New("invalid decompression limit") - } - lr := &io.LimitedReader{R: r, N: limit + 1} - out, err := io.ReadAll(lr) - if err != nil { - return nil, err - } - if int64(len(out)) > limit { - return nil, fmt.Errorf("decompressed block exceeds cap %d", limit) - } - return out, nil -} diff --git a/internal/leviathan/stego/metadata.go b/internal/leviathan/stego/metadata.go deleted file mode 100644 index 4570438..0000000 --- a/internal/leviathan/stego/metadata.go +++ /dev/null @@ -1,694 +0,0 @@ -package stego - -import ( - "bytes" - "compress/zlib" - "encoding/binary" - "errors" - "fmt" - "io" - "strings" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// MetaEntry is one piece of textual/EXIF metadata recovered from an image. -type MetaEntry struct { - Offset int64 `json:"offset"` - Length int64 `json:"length"` - Container string `json:"container"` - Kind string `json:"kind"` - Keyword string `json:"keyword"` - Value string `json:"value"` - Note string `json:"note,omitempty"` -} - -// ScanMetadata walks PNG chunks or JPEG segments (APPn / COM) and decodes -// human-readable metadata. Output is best-effort — malformed containers -// return whatever has been parsed up to the failure point. -func ScanMetadata(d *document.Document) ([]MetaEntry, error) { - size := d.Size() - if size < 8 { - return nil, errors.New("file too small") - } - head, err := d.Range(0, minInt64(16, size)) - if err != nil { - return nil, err - } - switch { - case len(head) >= 8 && string(head[:8]) == "\x89PNG\r\n\x1a\n": - return scanPNGMeta(d) - case len(head) >= 3 && head[0] == 0xff && head[1] == 0xd8 && head[2] == 0xff: - return scanJPEGMeta(d) - case len(head) >= 6 && (string(head[:6]) == "GIF87a" || string(head[:6]) == "GIF89a"): - return scanGIFMeta(d) - } - return nil, errors.New("unsupported container (PNG, JPEG and GIF handled)") -} - -func scanPNGMeta(d *document.Document) ([]MetaEntry, error) { - size := d.Size() - out := []MetaEntry{} - off := int64(8) - for off+12 <= size { - hdr, err := d.Range(off, 8) - if err != nil { - break - } - ln := int64(binary.BigEndian.Uint32(hdr[0:4])) - typ := string(hdr[4:8]) - if ln < 0 || off+12+ln > size { - break - } - if typ == "tEXt" || typ == "zTXt" || typ == "iTXt" { - data, err := d.Range(off+8, ln) - if err == nil { - entry := decodePNGTextChunk(typ, data) - entry.Offset = off - entry.Length = 12 + ln - entry.Container = "png" - out = append(out, entry) - } - } - off += 12 + ln - if typ == "IEND" { - break - } - } - return out, nil -} - -func decodePNGTextChunk(typ string, data []byte) MetaEntry { - e := MetaEntry{Kind: typ} - i := bytes.IndexByte(data, 0) - if i < 0 { - e.Note = "no keyword separator" - e.Value = safeText(data) - return e - } - e.Keyword = string(data[:i]) - rest := data[i+1:] - switch typ { - case "tEXt": - e.Value = safeText(rest) - case "zTXt": - if len(rest) < 1 { - e.Note = "truncated" - return e - } - // rest[0] = compression method (0 = zlib) - method := rest[0] - raw, err := zlibDecompress(rest[1:]) - if err != nil { - e.Note = fmt.Sprintf("zlib fail (method=%d): %v", method, err) - return e - } - e.Value = safeText(raw) - case "iTXt": - // compression flag, compression method, language tag\0, translated keyword\0, text - if len(rest) < 2 { - e.Note = "truncated" - return e - } - compressed := rest[0] == 1 - rest = rest[2:] - j := bytes.IndexByte(rest, 0) - if j < 0 { - e.Value = safeText(rest) - return e - } - lang := string(rest[:j]) - rest = rest[j+1:] - k := bytes.IndexByte(rest, 0) - if k < 0 { - e.Value = safeText(rest) - return e - } - trk := string(rest[:k]) - rest = rest[k+1:] - if compressed { - if raw, err := zlibDecompress(rest); err == nil { - rest = raw - } else { - e.Note = "zlib fail: " + err.Error() - } - } - v := safeText(rest) - if lang != "" || trk != "" { - v = fmt.Sprintf("[%s/%s] %s", lang, trk, v) - } - e.Value = v - } - return e -} - -func scanJPEGMeta(d *document.Document) ([]MetaEntry, error) { - size := d.Size() - out := []MetaEntry{} - off := int64(2) // skip SOI - for off+4 <= size { - hdr, err := d.Range(off, 2) - if err != nil || hdr[0] != 0xff { - break - } - marker := hdr[1] - // Standalone markers (no payload): SOI/EOI/RSTn - if marker == 0xd9 || marker == 0xd8 || (marker >= 0xd0 && marker <= 0xd7) { - off += 2 - continue - } - // Read segment length (big-endian, includes the length bytes themselves) - lnBytes, err := d.Range(off+2, 2) - if err != nil { - break - } - segLen := int64(binary.BigEndian.Uint16(lnBytes)) - if segLen < 2 || off+2+segLen > size { - break - } - payload, err := d.Range(off+4, segLen-2) - if err != nil { - break - } - switch { - case marker == 0xfe: // COM - out = append(out, MetaEntry{ - Offset: off, Length: 2 + segLen, - Container: "jpeg", Kind: "COM", - Value: safeText(payload), - }) - case marker == 0xe1: // APP1 — EXIF / XMP - entries := decodeJPEGApp1(payload) - for _, e := range entries { - e.Offset = off - e.Length = 2 + segLen - e.Container = "jpeg" - out = append(out, e) - } - case marker == 0xe0: // APP0 — JFIF - out = append(out, MetaEntry{ - Offset: off, Length: 2 + segLen, - Container: "jpeg", Kind: "APP0", - Keyword: sniffMarker(payload), - Value: fmt.Sprintf("%d bytes", len(payload)), - }) - case marker >= 0xe2 && marker <= 0xef: // APP2..APP15 - out = append(out, MetaEntry{ - Offset: off, Length: 2 + segLen, - Container: "jpeg", Kind: fmt.Sprintf("APP%d", marker-0xe0), - Keyword: sniffMarker(payload), - Value: fmt.Sprintf("%d bytes", len(payload)), - }) - } - off += 2 + segLen - // SOS (Start of Scan) — entropy-coded data follows, stop walking. - if marker == 0xda { - break - } - } - return out, nil -} - -func sniffMarker(p []byte) string { - i := bytes.IndexByte(p, 0) - if i < 0 || i > 16 { - if len(p) > 16 { - return string(p[:16]) - } - return "" - } - return string(p[:i]) -} - -func decodeJPEGApp1(p []byte) []MetaEntry { - out := []MetaEntry{} - if len(p) >= 6 && string(p[:6]) == "Exif\x00\x00" { - tiff := p[6:] - entries, err := decodeEXIF(tiff) - if err == nil { - out = append(out, entries...) - } else { - out = append(out, MetaEntry{Kind: "APP1/EXIF", Note: err.Error()}) - } - return out - } - if len(p) >= 28 && string(p[:28]) == "http://ns.adobe.com/xap/1.0/" { - xmp := p[29:] - out = append(out, MetaEntry{ - Kind: "APP1/XMP", Keyword: "xmp", Value: safeText(xmp), - }) - return out - } - out = append(out, MetaEntry{ - Kind: "APP1", Keyword: sniffMarker(p), Value: fmt.Sprintf("%d bytes", len(p)), - }) - return out -} - -// EXIF reader: TIFF header + IFD0 + IFD1 + Exif SubIFD + GPS SubIFD + -// Interoperability IFD. Handles ASCII/BYTE/SHORT/LONG/RATIONAL/SRATIONAL -// values, plus UNDEFINED as hex preview. Detects MakerNote presence. -func decodeEXIF(tiff []byte) ([]MetaEntry, error) { - if len(tiff) < 8 { - return nil, errors.New("short TIFF header") - } - var bo binary.ByteOrder - switch string(tiff[:2]) { - case "II": - bo = binary.LittleEndian - case "MM": - bo = binary.BigEndian - default: - return nil, errors.New("bad TIFF byte-order mark") - } - if bo.Uint16(tiff[2:4]) != 0x002a { - return nil, errors.New("bad TIFF magic") - } - ifd0 := int(bo.Uint32(tiff[4:8])) - out := []MetaEntry{} - ptrs := ifdPointers{} - nextIFD := 0 - out = append(out, readIFD(tiff, ifd0, bo, "IFD0", &ptrs, &nextIFD)...) - // IFD1 (thumbnail) if present. - if nextIFD > 0 { - out = append(out, readIFD(tiff, nextIFD, bo, "IFD1", nil, nil)...) - } - if ptrs.exif > 0 { - out = append(out, readIFD(tiff, ptrs.exif, bo, "ExifIFD", &ptrs, nil)...) - } - if ptrs.gps > 0 { - out = append(out, readIFD(tiff, ptrs.gps, bo, "GPSIFD", nil, nil)...) - } - if ptrs.interop > 0 { - out = append(out, readIFD(tiff, ptrs.interop, bo, "InteropIFD", nil, nil)...) - } - return out, nil -} - -type ifdPointers struct { - exif int - gps int - interop int -} - -func readIFD(tiff []byte, off int, bo binary.ByteOrder, name string, ptrs *ifdPointers, nextPtr *int) []MetaEntry { - out := []MetaEntry{} - if off < 0 || off+2 > len(tiff) { - return out - } - n := int(bo.Uint16(tiff[off : off+2])) - base := off + 2 - // Cap directory count to head off hostile input. - if n > 512 { - n = 512 - } - for i := 0; i < n; i++ { - p := base + i*12 - if p+12 > len(tiff) { - break - } - tag := bo.Uint16(tiff[p : p+2]) - typ := bo.Uint16(tiff[p+2 : p+4]) - count := int(bo.Uint32(tiff[p+4 : p+8])) - valRaw := tiff[p+8 : p+12] - // Sub-IFD pointers never appear as regular entries in the output. - if ptrs != nil { - switch tag { - case 0x8769: // Exif SubIFD pointer - ptrs.exif = int(bo.Uint32(valRaw)) - continue - case 0x8825: // GPS IFD pointer - ptrs.gps = int(bo.Uint32(valRaw)) - continue - case 0xa005: // Interoperability IFD pointer - ptrs.interop = int(bo.Uint32(valRaw)) - continue - } - } - e := MetaEntry{Kind: name, Keyword: exifTagName(tag, name)} - if tag == 0x927c { // MakerNote - e.Value = fmt.Sprintf("%d bytes (vendor-specific, not decoded)", count) - out = append(out, e) - continue - } - e.Value = readEXIFValue(tiff, bo, typ, count, valRaw) - out = append(out, e) - } - // "Next IFD" pointer follows the directory entries (4 bytes). - if nextPtr != nil { - tail := base + n*12 - if tail+4 <= len(tiff) { - *nextPtr = int(bo.Uint32(tiff[tail : tail+4])) - } - } - return out -} - -func readEXIFValue(tiff []byte, bo binary.ByteOrder, typ uint16, count int, valRaw []byte) string { - typeSize := map[uint16]int{1: 1, 2: 1, 3: 2, 4: 4, 5: 8, 7: 1, 9: 4, 10: 8} - sz, ok := typeSize[typ] - if !ok { - return fmt.Sprintf("type=%d count=%d", typ, count) - } - total := sz * count - var data []byte - if total <= 4 { - data = valRaw[:total] - } else { - off := int(bo.Uint32(valRaw)) - if off < 0 || off+total > len(tiff) { - return fmt.Sprintf("type=%d count=%d (oob)", typ, count) - } - data = tiff[off : off+total] - } - switch typ { - case 2: // ASCII (null-terminated) - return safeText(bytes.TrimRight(data, "\x00")) - case 1, 7: // BYTE, UNDEFINED — show as hex preview - if len(data) > 32 { - return fmt.Sprintf("%x… (%d bytes)", data[:32], len(data)) - } - return fmt.Sprintf("%x", data) - case 3: // SHORT - parts := []string{} - for i := 0; i < count && i < 8; i++ { - parts = append(parts, fmt.Sprintf("%d", bo.Uint16(data[i*2:i*2+2]))) - } - if count > 8 { - parts = append(parts, "…") - } - return strings.Join(parts, ", ") - case 4: // LONG - parts := []string{} - for i := 0; i < count && i < 6; i++ { - parts = append(parts, fmt.Sprintf("%d", bo.Uint32(data[i*4:i*4+4]))) - } - if count > 6 { - parts = append(parts, "…") - } - return strings.Join(parts, ", ") - case 5, 10: // RATIONAL / SRATIONAL - parts := []string{} - for i := 0; i < count && i < 4; i++ { - n := bo.Uint32(data[i*8 : i*8+4]) - dn := bo.Uint32(data[i*8+4 : i*8+8]) - if dn == 0 { - parts = append(parts, fmt.Sprintf("%d/0", n)) - } else { - parts = append(parts, fmt.Sprintf("%d/%d", n, dn)) - } - } - return strings.Join(parts, ", ") - case 9: // SLONG - parts := []string{} - for i := 0; i < count && i < 6; i++ { - parts = append(parts, fmt.Sprintf("%d", int32(bo.Uint32(data[i*4:i*4+4])))) - } - return strings.Join(parts, ", ") - } - return fmt.Sprintf("type=%d count=%d", typ, count) -} - -func exifTagName(tag uint16, ifd string) string { - if ifd == "GPSIFD" { - if n, ok := gpsTags[tag]; ok { - return n - } - return fmt.Sprintf("GPS tag 0x%04x", tag) - } - if n, ok := exifTags[tag]; ok { - return n - } - return fmt.Sprintf("tag 0x%04x", tag) -} - -// exifTags covers the standard TIFF/Exif tags routinely seen in -// JPEG/TIFF images produced by consumer cameras and phones. -var exifTags = map[uint16]string{ - // TIFF / IFD0 - 0x0100: "ImageWidth", 0x0101: "ImageLength", - 0x0102: "BitsPerSample", 0x0103: "Compression", - 0x0106: "PhotometricInterpretation", 0x010a: "FillOrder", - 0x010d: "DocumentName", 0x010e: "ImageDescription", - 0x010f: "Make", 0x0110: "Model", 0x0111: "StripOffsets", - 0x0112: "Orientation", 0x0115: "SamplesPerPixel", - 0x0116: "RowsPerStrip", 0x0117: "StripByteCounts", - 0x011a: "XResolution", 0x011b: "YResolution", - 0x011c: "PlanarConfiguration", 0x0128: "ResolutionUnit", - 0x012d: "TransferFunction", 0x0131: "Software", 0x0132: "DateTime", - 0x013b: "Artist", 0x013c: "HostComputer", - 0x013e: "WhitePoint", 0x013f: "PrimaryChromaticities", - 0x0211: "YCbCrCoefficients", 0x0212: "YCbCrSubSampling", - 0x0213: "YCbCrPositioning", 0x0214: "ReferenceBlackWhite", - 0x02bc: "XMP", - 0x8298: "Copyright", 0x83bb: "IPTCNAA", - 0x8769: "ExifIFDPointer", 0x8773: "InterColorProfile", - 0x8825: "GPSIFDPointer", - // Exif SubIFD - 0x829a: "ExposureTime", 0x829d: "FNumber", - 0x8822: "ExposureProgram", 0x8824: "SpectralSensitivity", - 0x8827: "ISOSpeedRatings", 0x8828: "OECF", - 0x9000: "ExifVersion", - 0x9003: "DateTimeOriginal", 0x9004: "DateTimeDigitized", - 0x9010: "OffsetTime", 0x9011: "OffsetTimeOriginal", 0x9012: "OffsetTimeDigitized", - 0x9101: "ComponentsConfiguration", 0x9102: "CompressedBitsPerPixel", - 0x9201: "ShutterSpeedValue", 0x9202: "ApertureValue", - 0x9203: "BrightnessValue", 0x9204: "ExposureBiasValue", - 0x9205: "MaxApertureValue", 0x9206: "SubjectDistance", - 0x9207: "MeteringMode", 0x9208: "LightSource", 0x9209: "Flash", - 0x920a: "FocalLength", - 0x9214: "SubjectArea", 0x927c: "MakerNote", 0x9286: "UserComment", - 0x9290: "SubSecTime", 0x9291: "SubSecTimeOriginal", 0x9292: "SubSecTimeDigitized", - 0xa000: "FlashpixVersion", 0xa001: "ColorSpace", - 0xa002: "PixelXDimension", 0xa003: "PixelYDimension", - 0xa004: "RelatedSoundFile", 0xa005: "InteropIFDPointer", - 0xa20b: "FlashEnergy", 0xa20c: "SpatialFrequencyResponse", - 0xa20e: "FocalPlaneXResolution", 0xa20f: "FocalPlaneYResolution", - 0xa210: "FocalPlaneResolutionUnit", - 0xa214: "SubjectLocation", 0xa215: "ExposureIndex", - 0xa217: "SensingMethod", 0xa300: "FileSource", 0xa301: "SceneType", - 0xa302: "CFAPattern", 0xa401: "CustomRendered", - 0xa402: "ExposureMode", 0xa403: "WhiteBalance", - 0xa404: "DigitalZoomRatio", 0xa405: "FocalLengthIn35mmFilm", - 0xa406: "SceneCaptureType", 0xa407: "GainControl", - 0xa408: "Contrast", 0xa409: "Saturation", 0xa40a: "Sharpness", - 0xa40b: "DeviceSettingDescription", 0xa40c: "SubjectDistanceRange", - 0xa420: "ImageUniqueID", 0xa430: "OwnerName", 0xa431: "SerialNumber", - 0xa432: "LensSpecification", 0xa433: "LensMake", 0xa434: "LensModel", - 0xa435: "LensSerialNumber", - // Thumbnail IFD1 re-uses IFD0 tags; additional ones: - 0x0201: "JPEGInterchangeFormat", 0x0202: "JPEGInterchangeFormatLength", -} - -var gpsTags = map[uint16]string{ - 0x0000: "GPSVersionID", 0x0001: "GPSLatitudeRef", 0x0002: "GPSLatitude", - 0x0003: "GPSLongitudeRef", 0x0004: "GPSLongitude", - 0x0005: "GPSAltitudeRef", 0x0006: "GPSAltitude", - 0x0007: "GPSTimeStamp", 0x0008: "GPSSatellites", 0x0009: "GPSStatus", - 0x000a: "GPSMeasureMode", 0x000b: "GPSDOP", 0x000c: "GPSSpeedRef", - 0x000d: "GPSSpeed", 0x000e: "GPSTrackRef", 0x000f: "GPSTrack", - 0x0010: "GPSImgDirectionRef", 0x0011: "GPSImgDirection", - 0x0012: "GPSMapDatum", 0x0013: "GPSDestLatitudeRef", 0x0014: "GPSDestLatitude", - 0x0015: "GPSDestLongitudeRef", 0x0016: "GPSDestLongitude", - 0x0017: "GPSDestBearingRef", 0x0018: "GPSDestBearing", - 0x0019: "GPSDestDistanceRef", 0x001a: "GPSDestDistance", - 0x001b: "GPSProcessingMethod", 0x001c: "GPSAreaInformation", - 0x001d: "GPSDateStamp", 0x001e: "GPSDifferential", 0x001f: "GPSHPositioningError", -} - -func zlibDecompress(b []byte) ([]byte, error) { - r, err := zlib.NewReader(bytes.NewReader(b)) - if err != nil { - return nil, err - } - defer r.Close() - // 2 MiB cap — textual metadata should never be near this. - lim := &io.LimitedReader{R: r, N: 2 * 1024 * 1024} - return io.ReadAll(lim) -} - -func safeText(b []byte) string { - // Preserve text but trim extreme size and strip control chars other than tab/newline. - if len(b) > 4096 { - b = b[:4096] - } - sb := strings.Builder{} - sb.Grow(len(b)) - for _, c := range b { - if c == '\t' || c == '\n' || c == '\r' || (c >= 0x20 && c <= 0x7e) { - sb.WriteByte(c) - continue - } - if c >= 0x80 { - // Pass through; browser will render as UTF-8 if valid. - sb.WriteByte(c) - continue - } - // Drop other controls. - } - return sb.String() -} - -func minInt64(a, b int64) int64 { - if a < b { - return a - } - return b -} - -// scanGIFMeta walks a GIF file and surfaces every extension block that -// can carry human-readable data: Comment (0x21 0xFE), Plain Text -// (0x21 0x01), and Application (0x21 0xFF — XMP, Netscape loop, etc.). -func scanGIFMeta(d *document.Document) ([]MetaEntry, error) { - size := d.Size() - out := []MetaEntry{} - // Skip header (6) + logical screen descriptor (7 fixed) and the - // optional global colour table. - hdr, err := d.Range(0, 13) - if err != nil { - return nil, err - } - off := int64(13) - packed := hdr[10] - if packed&0x80 != 0 { - gctSize := int64(3) * (int64(1) << uint((packed&0x07)+1)) - off += gctSize - } - for off < size { - b, err := d.Range(off, 1) - if err != nil { - break - } - switch b[0] { - case 0x3b: // trailer - return out, nil - case 0x2c: // Image Descriptor: skip descriptor + optional LCT + LZW data - next, ok := skipGIFImage(d, off) - if !ok { - return out, nil - } - off = next - case 0x21: // Extension Introducer - if off+2 > size { - return out, nil - } - lbl, _ := d.Range(off+1, 1) - start := off - payload, next, ok := readGIFSubBlocks(d, off+2) - if !ok { - return out, nil - } - switch lbl[0] { - case 0xfe: - out = append(out, MetaEntry{ - Offset: start, Length: next - start, - Container: "gif", Kind: "Comment", - Value: safeText(payload), - }) - case 0x01: - // Plain Text: first 12 bytes are text-grid parameters. - text := payload - if len(text) > 12 { - text = text[12:] - } else { - text = nil - } - out = append(out, MetaEntry{ - Offset: start, Length: next - start, - Container: "gif", Kind: "PlainText", - Value: safeText(text), - }) - case 0xff: - // Application Extension: first sub-block is an 11-byte - // identifier like "NETSCAPE2.0" or "XMP DataXMP". - if len(payload) >= 11 { - appID := string(bytes.TrimRight(payload[:11], "\x00 ")) - rest := payload[11:] - out = append(out, MetaEntry{ - Offset: start, Length: next - start, - Container: "gif", Kind: "Application", - Keyword: appID, - Value: safeText(rest), - }) - } else { - out = append(out, MetaEntry{ - Offset: start, Length: next - start, - Container: "gif", Kind: "Application", - Value: safeText(payload), - }) - } - default: - // Graphic control (0xf9) and others — skip. - } - off = next - default: - // Unknown byte; bail. - return out, nil - } - } - return out, nil -} - -// readGIFSubBlocks reads a chain of GIF sub-blocks: each starts with a -// length byte (0 terminates). Returns (concatenated payload, offset of -// next byte after terminator, ok). -func readGIFSubBlocks(d *document.Document, off int64) ([]byte, int64, bool) { - size := d.Size() - out := []byte{} - for off < size { - ln, err := d.Range(off, 1) - if err != nil { - return nil, off, false - } - n := int64(ln[0]) - off++ - if n == 0 { - return out, off, true - } - if off+n > size { - return nil, off, false - } - block, err := d.Range(off, n) - if err != nil { - return nil, off, false - } - out = append(out, block...) - off += n - // Hard cap to prevent OOM on malformed input. - if len(out) > 16*1024*1024 { - return out, off, true - } - } - return nil, off, false -} - -// skipGIFImage walks an Image Descriptor block starting at off (the 0x2c -// byte), skipping the descriptor, any local colour table, the LZW -// minimum-code-size byte and the image data sub-blocks. Returns the -// offset of the next block, or (0, false) on truncation. -func skipGIFImage(d *document.Document, off int64) (int64, bool) { - size := d.Size() - if off+10 > size { - return 0, false - } - desc, err := d.Range(off, 10) - if err != nil { - return 0, false - } - packed := desc[9] - off += 10 - if packed&0x80 != 0 { - lctSize := int64(3) * (int64(1) << uint((packed&0x07)+1)) - off += lctSize - } - // LZW minimum code size byte, then sub-blocks. - if off+1 > size { - return 0, false - } - off++ - _, next, ok := readGIFSubBlocks(d, off) - if !ok { - return 0, false - } - return next, true -} diff --git a/internal/leviathan/stego/stego.go b/internal/leviathan/stego/stego.go deleted file mode 100644 index 03a4ca8..0000000 --- a/internal/leviathan/stego/stego.go +++ /dev/null @@ -1,501 +0,0 @@ -// Package stego provides practical steganography helpers: -// -// - trailer detection (bytes after a declared end) for PNG, JPEG, -// GIF, BMP, WAV and RIFF/WebP containers -// - multi-trailer surfacing: identifies any embedded container -// (PNG/JPEG/ZIP/GIF/PDF/RAR/7z) inside the trailer region -// - LSB extract/inject over an arbitrary byte buffer -// - PNG IDAT, BMP pixel-array and WAV sample-data resolvers so -// callers can point LSB at the correct carrier region -// - textual-metadata extraction for PNG, JPEG, GIF (see metadata.go) -package stego - -import ( - "encoding/binary" - "errors" - - "github.com/andydixon/link/internal/leviathan/document" -) - -// --------------------------------------------------------------------------- -// Trailer detection -// --------------------------------------------------------------------------- - -// EmbeddedHit describes a recognised container signature found inside the -// trailer region (the bytes after a file's declared end). -type EmbeddedHit struct { - Offset int64 `json:"offset"` // absolute offset within the outer file - Kind string `json:"kind"` - Note string `json:"note,omitempty"` -} - -// AnalyseAppended inspects the end of the file for plausible trailers: data -// after the declared end of a known container. The result also lists any -// nested container magic bytes discovered inside the trailer region. -func AnalyseAppended(d *document.Document) (map[string]any, error) { - size := d.Size() - head, _ := d.Range(0, 16) - result := map[string]any{ - "fileSize": size, - "declaredEnd": int64(-1), - "trailerSize": int64(0), - "container": "", - "note": "no recognised container", - "embedded": []EmbeddedHit{}, - } - - end := int64(-1) - container := "" - - switch { - case len(head) >= 8 && string(head[:8]) == "\x89PNG\r\n\x1a\n": - container = "png" - end = findPNGEnd(d) - case len(head) >= 3 && head[0] == 0xff && head[1] == 0xd8 && head[2] == 0xff: - container = "jpeg" - end = findJPEGEnd(d) - case len(head) >= 6 && (string(head[:6]) == "GIF87a" || string(head[:6]) == "GIF89a"): - container = "gif" - end = findGIFEnd(d) - case len(head) >= 2 && head[0] == 'B' && head[1] == 'M': - container = "bmp" - end = findBMPEnd(d) - case len(head) >= 12 && string(head[:4]) == "RIFF": - sub := string(head[8:12]) - if sub == "WAVE" { - container = "wav" - } else if sub == "WEBP" { - container = "webp" - } else { - container = "riff" - } - end = findRIFFEnd(d) - } - - if container != "" { - result["container"] = container - } - - if end > 0 && end <= size { - result["declaredEnd"] = end - if end < size { - trailer := size - end - result["trailerSize"] = trailer - result["note"] = "bytes after " + container + " declared end" - if trailer > 0 { - // Cap scanning the trailer at 16 MiB — otherwise we might - // pull a 2-GiB tail into memory on hostile inputs. - scanLen := trailer - if scanLen > 16*1024*1024 { - scanLen = 16 * 1024 * 1024 - } - tail, _ := d.Range(end, scanLen) - result["embedded"] = scanEmbedded(tail, end) - } - } else { - result["note"] = "no trailing bytes" - } - } - - return result, nil -} - -// ExtractTrailer returns the bytes after a container's declared end. -func ExtractTrailer(d *document.Document) ([]byte, error) { - a, err := AnalyseAppended(d) - if err != nil { - return nil, err - } - end, _ := a["declaredEnd"].(int64) - ts, _ := a["trailerSize"].(int64) - if end < 0 || ts <= 0 { - return nil, errors.New("no trailer found") - } - return d.Range(end, ts) -} - -func findPNGEnd(d *document.Document) int64 { - size := d.Size() - off := int64(8) - for off+12 <= size { - hdr, err := d.Range(off, 8) - if err != nil { - break - } - ln := int64(binary.BigEndian.Uint32(hdr[0:4])) - if ln < 0 || off+12+ln > size { - break - } - typ := string(hdr[4:8]) - off += 12 + ln - if typ == "IEND" { - return off - } - } - return -1 -} - -func findJPEGEnd(d *document.Document) int64 { - size := d.Size() - tailLen := int64(128 * 1024) - if tailLen > size { - tailLen = size - } - tail, _ := d.Range(size-tailLen, tailLen) - for i := len(tail) - 2; i >= 0; i-- { - if tail[i] == 0xff && tail[i+1] == 0xd9 { - return size - tailLen + int64(i+2) - } - } - return -1 -} - -func findGIFEnd(d *document.Document) int64 { - size := d.Size() - // The GIF terminator 0x3B is a single byte that follows the final - // data block of the final image. Walking the container correctly is - // overkill; scan from the tail and accept the last 0x3B that is - // preceded by a block terminator (0x00) or a plausible frame end. - tailLen := int64(32 * 1024) - if tailLen > size { - tailLen = size - } - tail, _ := d.Range(size-tailLen, tailLen) - for i := len(tail) - 1; i >= 0; i-- { - if tail[i] == 0x3b { - return size - tailLen + int64(i+1) - } - } - return -1 -} - -func findBMPEnd(d *document.Document) int64 { - size := d.Size() - hdr, err := d.Range(0, 14) - if err != nil { - return -1 - } - bfSize := int64(binary.LittleEndian.Uint32(hdr[2:6])) - if bfSize > 0 && bfSize <= size { - return bfSize - } - return -1 -} - -func findRIFFEnd(d *document.Document) int64 { - size := d.Size() - hdr, err := d.Range(0, 8) - if err != nil { - return -1 - } - // RIFF chunk size (little-endian) is the size of the rest of the - // file *after* the first 8 bytes, so declared end = 8 + riffSize. - riffSize := int64(binary.LittleEndian.Uint32(hdr[4:8])) - end := 8 + riffSize - if end > 0 && end <= size { - // RIFF pads to even alignment. - if end%2 == 1 && end+1 <= size { - end++ - } - return end - } - return -1 -} - -// scanEmbedded walks a trailer buffer for known container magic bytes. -func scanEmbedded(buf []byte, base int64) []EmbeddedHit { - hits := []EmbeddedHit{} - type sig struct { - bytes []byte - kind string - } - sigs := []sig{ - {[]byte("\x89PNG\r\n\x1a\n"), "png"}, - {[]byte{0xff, 0xd8, 0xff}, "jpeg"}, - {[]byte("GIF87a"), "gif"}, - {[]byte("GIF89a"), "gif"}, - {[]byte("PK\x03\x04"), "zip"}, - {[]byte("PK\x05\x06"), "zip-eocd"}, - {[]byte("Rar!\x1a\x07\x00"), "rar"}, - {[]byte("Rar!\x1a\x07\x01\x00"), "rar5"}, - {[]byte("7z\xbc\xaf\x27\x1c"), "7z"}, - {[]byte("%PDF-"), "pdf"}, - {[]byte("\x1f\x8b\x08"), "gzip"}, - {[]byte{0xfd, '7', 'z', 'X', 'Z', 0x00}, "xz"}, - {[]byte("BZh"), "bzip2"}, - {[]byte{0x28, 0xb5, 0x2f, 0xfd}, "zstd"}, - {[]byte{0x7f, 'E', 'L', 'F'}, "elf"}, - {[]byte("MZ"), "mz/pe"}, - {[]byte("ustar"), "tar"}, - {[]byte("OggS"), "ogg"}, - {[]byte("ID3"), "id3/mp3"}, - {[]byte("fLaC"), "flac"}, - {[]byte("\x1aEߣ"), "matroska/webm"}, - {[]byte("RIFF"), "riff"}, - } - for _, s := range sigs { - if len(s.bytes) == 0 { - continue - } - start := 0 - for { - i := indexOf(buf[start:], s.bytes) - if i < 0 { - break - } - hits = append(hits, EmbeddedHit{ - Offset: base + int64(start+i), - Kind: s.kind, - }) - // A plain "MZ" or "RIFF" hit without more context is noisy; - // the caller can filter on kind. Keep walking past this hit. - start += i + len(s.bytes) - // Bound total hits per signature to keep UI manageable. - if len(hits) > 256 { - break - } - } - if len(hits) > 256 { - break - } - } - return hits -} - -func indexOf(hay, needle []byte) int { - if len(needle) == 0 || len(hay) < len(needle) { - return -1 - } - first := needle[0] - last := len(hay) - len(needle) - for i := 0; i <= last; i++ { - if hay[i] != first { - continue - } - match := true - for j := 1; j < len(needle); j++ { - if hay[i+j] != needle[j] { - match = false - break - } - } - if match { - return i - } - } - return -1 -} - -// --------------------------------------------------------------------------- -// Carrier resolvers (PNG IDAT, BMP pixels, WAV samples) -// --------------------------------------------------------------------------- - -// PNGImageData concatenates all IDAT chunks' payload bytes. Callers can then -// feed the result into LSBExtract/LSBInject. The returned buffer is the raw -// (still-deflated) IDAT stream — it is the conventional LSB carrier for -// tools like zsteg, not the decoded pixel array. -func PNGImageData(d *document.Document) ([]byte, error) { - size := d.Size() - off := int64(8) - buf := []byte{} - for off+12 <= size { - hdr, err := d.Range(off, 8) - if err != nil { - return nil, err - } - ln := int64(binary.BigEndian.Uint32(hdr[0:4])) - typ := string(hdr[4:8]) - if typ == "IDAT" { - chunk, err := d.Range(off+8, ln) - if err != nil { - return nil, err - } - buf = append(buf, chunk...) - } - off += 12 + ln - if typ == "IEND" { - break - } - } - if len(buf) == 0 { - return nil, errors.New("no IDAT data") - } - return buf, nil -} - -// BMPCarrier describes a resolved BMP pixel array that can be used as an -// LSB carrier. -type BMPCarrier struct { - Data []byte `json:"data"` - Offset int64 `json:"offset"` // offset of pixel array in the file - Width int32 `json:"width"` - Height int32 `json:"height"` - BitsPerPixel uint16 `json:"bitsPerPixel"` - RowStride int `json:"rowStride"` -} - -// BMPPixels locates and returns the BMP pixel array. Supports 8/24/32 bpp -// uncompressed BI_RGB images, which are by far the most common carriers. -func BMPPixels(d *document.Document) (*BMPCarrier, error) { - size := d.Size() - if size < 54 { - return nil, errors.New("not a BMP (too small)") - } - hdr, err := d.Range(0, 54) - if err != nil { - return nil, err - } - if hdr[0] != 'B' || hdr[1] != 'M' { - return nil, errors.New("not a BMP") - } - bfOffBits := int64(binary.LittleEndian.Uint32(hdr[10:14])) - dibSize := binary.LittleEndian.Uint32(hdr[14:18]) - // BITMAPINFOHEADER (40), BITMAPV4HEADER (108), BITMAPV5HEADER (124), - // OS/2 BITMAPCOREHEADER (12). Only handle the modern family. - if dibSize < 40 { - return nil, errors.New("BMP: unsupported DIB header (BITMAPCOREHEADER)") - } - w := int32(binary.LittleEndian.Uint32(hdr[18:22])) - h := int32(binary.LittleEndian.Uint32(hdr[22:26])) - bpp := binary.LittleEndian.Uint16(hdr[28:30]) - compression := binary.LittleEndian.Uint32(hdr[30:34]) - if compression != 0 && compression != 3 { // BI_RGB / BI_BITFIELDS - return nil, errors.New("BMP: compressed images not supported") - } - if bpp != 8 && bpp != 24 && bpp != 32 { - return nil, errors.New("BMP: only 8/24/32 bpp supported") - } - absH := h - if absH < 0 { - absH = -absH - } - rowStride := ((int(w)*int(bpp) + 31) / 32) * 4 - total := int64(rowStride) * int64(absH) - if bfOffBits < 0 || bfOffBits+total > size { - return nil, errors.New("BMP: pixel array out of range") - } - data, err := d.Range(bfOffBits, total) - if err != nil { - return nil, err - } - return &BMPCarrier{ - Data: data, Offset: bfOffBits, - Width: w, Height: h, BitsPerPixel: bpp, RowStride: rowStride, - }, nil -} - -// WAVCarrier describes a resolved WAV "data" chunk that can be used as an -// LSB carrier. -type WAVCarrier struct { - Data []byte `json:"data"` - Offset int64 `json:"offset"` // offset of sample data - SampleRate uint32 `json:"sampleRate"` - NumChannels uint16 `json:"numChannels"` - BitsPerSample uint16 `json:"bitsPerSample"` - AudioFormat uint16 `json:"audioFormat"` -} - -// WAVSamples locates the "data" sub-chunk of a RIFF/WAVE file and returns -// its payload. Only PCM (format 1) is usable as a straightforward LSB -// carrier; other formats still return bytes but are flagged in the caller. -func WAVSamples(d *document.Document) (*WAVCarrier, error) { - size := d.Size() - if size < 44 { - return nil, errors.New("not a WAV (too small)") - } - hdr, err := d.Range(0, 12) - if err != nil { - return nil, err - } - if string(hdr[:4]) != "RIFF" || string(hdr[8:12]) != "WAVE" { - return nil, errors.New("not a RIFF/WAVE file") - } - out := &WAVCarrier{} - off := int64(12) - for off+8 <= size { - ch, err := d.Range(off, 8) - if err != nil { - return nil, err - } - id := string(ch[:4]) - cs := int64(binary.LittleEndian.Uint32(ch[4:8])) - if cs < 0 || off+8+cs > size { - break - } - switch id { - case "fmt ": - body, err := d.Range(off+8, cs) - if err == nil && len(body) >= 16 { - out.AudioFormat = binary.LittleEndian.Uint16(body[0:2]) - out.NumChannels = binary.LittleEndian.Uint16(body[2:4]) - out.SampleRate = binary.LittleEndian.Uint32(body[4:8]) - out.BitsPerSample = binary.LittleEndian.Uint16(body[14:16]) - } - case "data": - body, err := d.Range(off+8, cs) - if err != nil { - return nil, err - } - out.Data = body - out.Offset = off + 8 - return out, nil - } - // Pad to even. - step := 8 + cs - if step%2 == 1 { - step++ - } - off += step - } - return nil, errors.New("WAV: data chunk not found") -} - -// --------------------------------------------------------------------------- -// LSB codec -// --------------------------------------------------------------------------- - -// LSBExtract returns a bitstring-as-bytes built from the low bit of each -// byte in the selected range. Useful for exploring LSB payloads over raw -// buffers or specific regions. -func LSBExtract(buf []byte, bitsPerByte int) []byte { - if bitsPerByte <= 0 || bitsPerByte > 8 { - bitsPerByte = 1 - } - total := len(buf) * bitsPerByte - out := make([]byte, total/8) - bitIdx := 0 - for _, b := range buf { - for i := 0; i < bitsPerByte; i++ { - bit := (b >> uint(i)) & 1 - if bit == 1 { - out[bitIdx/8] |= 1 << uint(7-(bitIdx%8)) - } - bitIdx++ - } - } - return out -} - -// LSBInject replaces low bits in buf with bits from payload. Returns the -// modified buffer. When capacity is exceeded, injection stops. -func LSBInject(buf, payload []byte, bitsPerByte int) []byte { - if bitsPerByte <= 0 || bitsPerByte > 8 { - bitsPerByte = 1 - } - out := append([]byte(nil), buf...) - mask := byte(1<> uint(7-(bitIdx%8))) & 1 - if bit != 0 { - chunk |= 1 << uint(b) - } - bitIdx++ - } - out[i] = (out[i] &^ mask) | (chunk & mask) - } - return out -} diff --git a/internal/leviathan/transforms/cipher.go b/internal/leviathan/transforms/cipher.go deleted file mode 100644 index f5cfca7..0000000 --- a/internal/leviathan/transforms/cipher.go +++ /dev/null @@ -1,402 +0,0 @@ -package transforms - -import ( - "math" -) - -// Caesar shifts A-Z/a-z forward by n, leaving other bytes alone. -func Caesar(in []byte, n int) []byte { - n = ((n % 26) + 26) % 26 - out := make([]byte, len(in)) - for i, b := range in { - switch { - case b >= 'A' && b <= 'Z': - out[i] = byte('A' + (int(b-'A')+n)%26) - case b >= 'a' && b <= 'z': - out[i] = byte('a' + (int(b-'a')+n)%26) - default: - out[i] = b - } - } - return out -} - -// Atbash maps A..Z -> Z..A (and lowercase equivalently). -func Atbash(in []byte) []byte { - out := make([]byte, len(in)) - for i, b := range in { - switch { - case b >= 'A' && b <= 'Z': - out[i] = 'A' + 'Z' - b - case b >= 'a' && b <= 'z': - out[i] = 'a' + 'z' - b - default: - out[i] = b - } - } - return out -} - -// Vigenere applies the Vigenère cipher with an ASCII key. encrypt=true -// shifts forward, false shifts backward. Non-letters are preserved and -// do not advance the key. -func Vigenere(in, key []byte, encrypt bool) []byte { - if len(key) == 0 { - return append([]byte(nil), in...) - } - normKey := make([]int, 0, len(key)) - for _, k := range key { - switch { - case k >= 'A' && k <= 'Z': - normKey = append(normKey, int(k-'A')) - case k >= 'a' && k <= 'z': - normKey = append(normKey, int(k-'a')) - } - } - if len(normKey) == 0 { - return append([]byte(nil), in...) - } - out := make([]byte, len(in)) - ki := 0 - for i, b := range in { - var base byte - var letter bool - if b >= 'A' && b <= 'Z' { - base = 'A' - letter = true - } else if b >= 'a' && b <= 'z' { - base = 'a' - letter = true - } - if letter { - shift := normKey[ki%len(normKey)] - if !encrypt { - shift = -shift - } - out[i] = byte(int(base) + ((int(b-base)+shift)%26+26)%26) - ki++ - } else { - out[i] = b - } - } - return out -} - -// English letter frequency (A..Z) in percent. -var englishFreq = [26]float64{ - 8.167, 1.492, 2.782, 4.253, 12.702, 2.228, 2.015, 6.094, 6.966, 0.153, - 0.772, 4.025, 2.406, 6.749, 7.507, 1.929, 0.095, 5.987, 6.327, 9.056, - 2.758, 0.978, 2.360, 0.150, 1.974, 0.074, -} - -// ScoreEnglish returns a heuristic fit to English letter frequencies. -// Higher score = closer to English. ASCII-printable bonus added. -func ScoreEnglish(in []byte) float64 { - if len(in) == 0 { - return 0 - } - var counts [26]int - total := 0 - printable := 0 - for _, b := range in { - if b >= 'A' && b <= 'Z' { - counts[b-'A']++ - total++ - } else if b >= 'a' && b <= 'z' { - counts[b-'a']++ - total++ - } - if (b >= 0x20 && b <= 0x7e) || b == '\n' || b == '\r' || b == '\t' { - printable++ - } - } - printRatio := float64(printable) / float64(len(in)) - if total == 0 { - return printRatio * 10 // give credit for printability - } - // Chi-squared inverse-like score - chi := 0.0 - for i := 0; i < 26; i++ { - obs := float64(counts[i]) / float64(total) * 100 - exp := englishFreq[i] - chi += math.Abs(obs - exp) // L1 distance; simpler than chi^2 for small samples - } - // Lower chi -> better. Convert to a bounded score. - score := (200.0 - chi) + printRatio*50 - if score < 0 { - score = 0 - } - return score -} - -// BruteCaesar returns all 26 shifts with scores, best-first. -func BruteCaesar(in []byte) []map[string]any { - out := make([]map[string]any, 26) - for n := 0; n < 26; n++ { - dec := Caesar(in, n) - out[n] = map[string]any{ - "shift": n, - "score": ScoreEnglish(dec), - "sample": previewText(dec, 120), - "text": string(dec), - } - } - // sort by score desc (simple insertion) - for i := 1; i < len(out); i++ { - for j := i; j > 0 && out[j-1]["score"].(float64) < out[j]["score"].(float64); j-- { - out[j-1], out[j] = out[j], out[j-1] - } - } - return out -} - -// BruteXORByte returns scored 1-byte XOR candidates, best-first. -func BruteXORByte(in []byte) []map[string]any { - out := make([]map[string]any, 256) - for k := 0; k < 256; k++ { - buf := make([]byte, len(in)) - for i, b := range in { - buf[i] = b ^ byte(k) - } - out[k] = map[string]any{ - "key": k, - "keyHex": byteHex(byte(k)), - "score": ScoreEnglish(buf), - "sample": previewText(buf, 120), - "text": string(buf), - } - } - for i := 1; i < len(out); i++ { - for j := i; j > 0 && out[j-1]["score"].(float64) < out[j]["score"].(float64); j-- { - out[j-1], out[j] = out[j], out[j-1] - } - } - return out -} - -// RepeatingXORGuess attempts to recover short repeating XOR keys using -// normalized Hamming distance for key-size ranking and per-column -// single-byte XOR scoring. -func RepeatingXORGuess(in []byte, minKey, maxKey, top int) []map[string]any { - if minKey <= 0 { - minKey = 2 - } - if maxKey < minKey { - maxKey = minKey - } - if maxKey > 64 { - maxKey = 64 - } - if top <= 0 { - top = 5 - } - type keySizeScore struct { - size int - score float64 - } - sizes := []keySizeScore{} - for keySize := minKey; keySize <= maxKey; keySize++ { - if len(in) < keySize*4 { - continue - } - dist := 0.0 - pairs := 0 - for block := 0; block < 4; block++ { - a := block * keySize - b := a + keySize - if b+keySize > len(in) { - break - } - dist += float64(hamming(in[a:a+keySize], in[b:b+keySize])) / float64(keySize) - pairs++ - } - if pairs > 0 { - sizes = append(sizes, keySizeScore{size: keySize, score: dist / float64(pairs)}) - } - } - for i := 1; i < len(sizes); i++ { - for j := i; j > 0 && sizes[j-1].score > sizes[j].score; j-- { - sizes[j-1], sizes[j] = sizes[j], sizes[j-1] - } - } - limit := top - if limit > len(sizes) { - limit = len(sizes) - } - out := make([]map[string]any, 0, limit) - for _, ks := range sizes[:limit] { - key := recoverRepeatingXORKey(in, ks.size) - dec := XORRepeating(in, key) - out = append(out, map[string]any{ - "keySize": ks.size, - "distance": ks.score, - "keyHex": bytesHex(key), - "keyText": previewText(key, len(key)), - "score": ScoreEnglish(dec), - "sample": previewText(dec, 120), - "text": string(dec), - "normalizedScore": ScoreEnglish(dec) - ks.score, - }) - } - for i := 1; i < len(out); i++ { - for j := i; j > 0 && out[j-1]["normalizedScore"].(float64) < out[j]["normalizedScore"].(float64); j-- { - out[j-1], out[j] = out[j], out[j-1] - } - } - return out -} - -func recoverRepeatingXORKey(in []byte, keySize int) []byte { - key := make([]byte, keySize) - for pos := 0; pos < keySize; pos++ { - col := make([]byte, 0, len(in)/keySize+1) - for i := pos; i < len(in); i += keySize { - col = append(col, in[i]) - } - bestScore := -1.0 - bestKey := byte(0) - for k := 0; k < 256; k++ { - buf := make([]byte, len(col)) - for i, b := range col { - buf[i] = b ^ byte(k) - } - score := ScoreEnglish(buf) - if score > bestScore { - bestScore = score - bestKey = byte(k) - } - } - key[pos] = bestKey - } - return key -} - -func XORRepeating(in, key []byte) []byte { - if len(key) == 0 { - return append([]byte(nil), in...) - } - out := make([]byte, len(in)) - for i, b := range in { - out[i] = b ^ key[i%len(key)] - } - return out -} - -func hamming(a, b []byte) int { - n := len(a) - if len(b) < n { - n = len(b) - } - out := 0 - for i := 0; i < n; i++ { - x := a[i] ^ b[i] - for x != 0 { - out++ - x &= x - 1 - } - } - return out -} - -// TryEverything tries a handful of simple decodes and scores them. -func TryEverything(in []byte) []map[string]any { - candidates := []map[string]any{ - {"name": "input", "text": string(in), "score": ScoreEnglish(in)}, - {"name": "rot13", "text": string(Caesar(in, 13)), "score": ScoreEnglish(Caesar(in, 13))}, - {"name": "atbash", "text": string(Atbash(in)), "score": ScoreEnglish(Atbash(in))}, - } - if b, err := Base64Decode(in); err == nil && len(b) > 0 { - candidates = append(candidates, map[string]any{"name": "base64.decode", "text": string(b), "score": ScoreEnglish(b)}) - } - if b, err := HexDecode(in); err == nil && len(b) > 0 { - candidates = append(candidates, map[string]any{"name": "hex.decode", "text": string(b), "score": ScoreEnglish(b)}) - } - if b, err := URLDecode(in); err == nil && len(b) > 0 { - candidates = append(candidates, map[string]any{"name": "url.decode", "text": string(b), "score": ScoreEnglish(b)}) - } - // Top 3 XOR 1-byte candidates - xr := BruteXORByte(in) - for i := 0; i < 3 && i < len(xr); i++ { - candidates = append(candidates, map[string]any{ - "name": "xor1:" + xr[i]["keyHex"].(string), - "text": xr[i]["text"].(string), - "score": xr[i]["score"].(float64), - }) - } - // Top repeating-key XOR candidates. - rx := RepeatingXORGuess(in, 2, 40, 3) - for i := 0; i < 3 && i < len(rx); i++ { - candidates = append(candidates, map[string]any{ - "name": "xor-repeating:" + rx[i]["keyHex"].(string), - "keyHex": rx[i]["keyHex"].(string), - "keyText": rx[i]["keyText"].(string), - "text": rx[i]["text"].(string), - "score": rx[i]["score"].(float64), - }) - } - // Top 2 caesar - cz := BruteCaesar(in) - for i := 0; i < 2 && i < len(cz); i++ { - candidates = append(candidates, map[string]any{ - "name": "caesar:" + byteDec(byte(cz[i]["shift"].(int))), - "text": cz[i]["text"].(string), - "score": cz[i]["score"].(float64), - }) - } - // Sort - for i := 1; i < len(candidates); i++ { - for j := i; j > 0 && candidates[j-1]["score"].(float64) < candidates[j]["score"].(float64); j-- { - candidates[j-1], candidates[j] = candidates[j], candidates[j-1] - } - } - // Samples - for _, c := range candidates { - t, _ := c["text"].(string) - c["sample"] = previewText([]byte(t), 120) - } - return candidates -} - -func previewText(buf []byte, n int) string { - if n > len(buf) { - n = len(buf) - } - out := make([]byte, n) - for i := 0; i < n; i++ { - if buf[i] >= 0x20 && buf[i] <= 0x7e { - out[i] = buf[i] - } else if buf[i] == '\n' { - out[i] = ' ' - } else { - out[i] = '.' - } - } - return string(out) -} - -func byteHex(b byte) string { - const h = "0123456789abcdef" - return string([]byte{h[b>>4], h[b&0x0f]}) -} - -func bytesHex(buf []byte) string { - out := make([]byte, 0, len(buf)*2) - for _, b := range buf { - out = append(out, byteHex(b)...) - } - return string(out) -} - -func byteDec(b byte) string { - if b == 0 { - return "0" - } - out := [3]byte{} - i := 3 - for b > 0 { - i-- - out[i] = '0' + b%10 - b /= 10 - } - return string(out[i:]) -} diff --git a/internal/leviathan/transforms/transforms.go b/internal/leviathan/transforms/transforms.go deleted file mode 100644 index f9344ab..0000000 --- a/internal/leviathan/transforms/transforms.go +++ /dev/null @@ -1,237 +0,0 @@ -// Package transforms exposes chainable transform primitives. Each -// function takes an input []byte and options, and returns an output -// or an error. They are pure and do not mutate input. -package transforms - -import ( - "bytes" - "compress/flate" - "compress/gzip" - "compress/zlib" - "encoding/base64" - "encoding/hex" - "errors" - "io" - "net/url" -) - -const MaxInflateOutput = 64 << 20 - -// HexEncode returns ASCII hex of src. -func HexEncode(src []byte) ([]byte, error) { - return []byte(hex.EncodeToString(src)), nil -} - -// HexDecode parses ASCII hex into bytes. Whitespace is ignored. -func HexDecode(src []byte) ([]byte, error) { - stripped := stripHex(src) - out := make([]byte, len(stripped)/2) - _, err := hex.Decode(out, stripped) - return out, err -} - -func stripHex(s []byte) []byte { - out := make([]byte, 0, len(s)) - for _, b := range s { - if b == ' ' || b == '\n' || b == '\r' || b == '\t' || b == ',' || b == ':' { - continue - } - if b == '0' && len(out) == 0 { - continue - } - out = append(out, b) - } - return out -} - -// Base64Encode returns the standard Base64 of src. -func Base64Encode(src []byte) ([]byte, error) { - return []byte(base64.StdEncoding.EncodeToString(src)), nil -} - -// Base64Decode accepts standard or URL-safe Base64, with or without padding. -func Base64Decode(src []byte) ([]byte, error) { - s := string(bytes.TrimSpace(src)) - attempts := []*base64.Encoding{ - base64.StdEncoding, - base64.URLEncoding, - base64.RawStdEncoding, - base64.RawURLEncoding, - } - for _, enc := range attempts { - if b, err := enc.DecodeString(s); err == nil { - return b, nil - } - } - return nil, errors.New("not valid base64") -} - -// URLEncode applies percent-encoding. -func URLEncode(src []byte) ([]byte, error) { - return []byte(url.QueryEscape(string(src))), nil -} - -// URLDecode reverses percent-encoding. -func URLDecode(src []byte) ([]byte, error) { - v, err := url.QueryUnescape(string(src)) - if err != nil { - return nil, err - } - return []byte(v), nil -} - -// XOR returns src^key, cycling key. -func XOR(src, key []byte) ([]byte, error) { - if len(key) == 0 { - return nil, errors.New("empty key") - } - out := make([]byte, len(src)) - for i := range src { - out[i] = src[i] ^ key[i%len(key)] - } - return out, nil -} - -// InflateGzip, InflateZlib, InflateRaw attempt to decompress. -func InflateGzip(src []byte) ([]byte, error) { - r, err := gzip.NewReader(bytes.NewReader(src)) - if err != nil { - return nil, err - } - defer r.Close() - return readLimitedInflate(r) -} - -func InflateZlib(src []byte) ([]byte, error) { - r, err := zlib.NewReader(bytes.NewReader(src)) - if err != nil { - return nil, err - } - defer r.Close() - return readLimitedInflate(r) -} - -func InflateRaw(src []byte) ([]byte, error) { - r := flate.NewReader(bytes.NewReader(src)) - defer r.Close() - return readLimitedInflate(r) -} - -func readLimitedInflate(r io.Reader) ([]byte, error) { - lr := io.LimitReader(r, MaxInflateOutput+1) - out, err := io.ReadAll(lr) - if err != nil { - return nil, err - } - if len(out) > MaxInflateOutput { - return nil, errors.New("decompressed output exceeds 64 MiB limit") - } - return out, nil -} - -// TryInflate attempts gzip, zlib, and raw deflate in order. -func TryInflate(src []byte) (out []byte, kind string, err error) { - if b, e := InflateGzip(src); e == nil { - return b, "gzip", nil - } - if b, e := InflateZlib(src); e == nil { - return b, "zlib", nil - } - if b, e := InflateRaw(src); e == nil { - return b, "raw", nil - } - return nil, "", errors.New("no decompression succeeded") -} - -// Step is one entry in a transform pipeline. -type Step struct { - Op string // hex.encode, hex.decode, base64.encode, base64.decode, - // xor, inflate, url.encode, url.decode, caesar, rot13, atbash, - // vigenere.enc, vigenere.dec - Arg string // ASCII parameter (e.g. key for XOR/Vigenere, shift for Caesar) -} - -// ApplyPipeline runs steps left-to-right. Intermediate results are -// returned for inspection. -func ApplyPipeline(input []byte, steps []Step) ([][]byte, error) { - results := make([][]byte, 0, len(steps)+1) - cur := append([]byte(nil), input...) - results = append(results, cur) - for _, s := range steps { - out, err := Apply(cur, s) - if err != nil { - return results, err - } - cur = out - results = append(results, cur) - } - return results, nil -} - -// Apply one step. -func Apply(in []byte, s Step) ([]byte, error) { - switch s.Op { - case "hex.encode": - return HexEncode(in) - case "hex.decode": - return HexDecode(in) - case "base64.encode": - return Base64Encode(in) - case "base64.decode": - return Base64Decode(in) - case "url.encode": - return URLEncode(in) - case "url.decode": - return URLDecode(in) - case "xor": - key, err := parseKey(s.Arg) - if err != nil { - return nil, err - } - return XOR(in, key) - case "inflate": - switch s.Arg { - case "gzip": - return InflateGzip(in) - case "zlib": - return InflateZlib(in) - case "raw": - return InflateRaw(in) - default: - out, _, err := TryInflate(in) - return out, err - } - case "caesar": - n := 13 - if s.Arg != "" { - var v int - for _, c := range s.Arg { - if c >= '0' && c <= '9' { - v = v*10 + int(c-'0') - } - } - n = v - } - return Caesar(in, n), nil - case "rot13": - return Caesar(in, 13), nil - case "atbash": - return Atbash(in), nil - case "vigenere.enc": - return Vigenere(in, []byte(s.Arg), true), nil - case "vigenere.dec": - return Vigenere(in, []byte(s.Arg), false), nil - } - return nil, errors.New("unknown op: " + s.Op) -} - -// parseKey accepts either literal string (ASCII) or hex-prefixed "hex:..". -func parseKey(arg string) ([]byte, error) { - if len(arg) > 4 && arg[:4] == "hex:" { - return hex.DecodeString(arg[4:]) - } - if arg == "" { - return nil, errors.New("empty key") - } - return []byte(arg), nil -} diff --git a/internal/localshell/localshell.go b/internal/localshell/localshell.go deleted file mode 100644 index 816d2db..0000000 --- a/internal/localshell/localshell.go +++ /dev/null @@ -1,294 +0,0 @@ -// Package localshell provides a local PTY-based terminal session. -// It spawns a shell process and bridges I/O for the frontend terminal. -package localshell - -import ( - "context" - "errors" - "fmt" - "io" - "os" - "os/exec" - "runtime" - "strconv" - "strings" - "sync" - "syscall" - "time" - - "github.com/andydixon/link/internal/terminalenv" - "github.com/creack/pty" -) - -// Client manages a local shell PTY session. -type Client struct { - mu sync.Mutex - cmd *exec.Cmd - ptmx *os.File - shell string - startDir string - onData func([]byte) - onError func(error) - onClose func() - closed bool - cols uint16 - rows uint16 -} - -// Config holds configuration for a local shell session. -type Config struct { - Shell string // Path to the shell binary (e.g. /bin/zsh) - Cols uint16 - Rows uint16 - StartDir string // Initial working directory; defaults to the user's home directory. - OnData func([]byte) - OnError func(error) - OnClose func() -} - -// DefaultShell returns the system default shell. -func DefaultShell() string { - if runtime.GOOS == "windows" { - // Prefer PowerShell if available, else cmd.exe. - if ps, err := exec.LookPath("powershell.exe"); err == nil { - return ps - } - return "cmd.exe" - } - // Unix: use SHELL env var, fallback to /bin/sh. - if sh := os.Getenv("SHELL"); sh != "" { - return sh - } - return "/bin/sh" -} - -// NewClient creates a new local shell client. -func NewClient(cfg Config) *Client { - shell := cfg.Shell - if shell == "" { - shell = DefaultShell() - } - cols := cfg.Cols - if cols == 0 { - cols = 80 - } - rows := cfg.Rows - if rows == 0 { - rows = 24 - } - startDir := cfg.StartDir - if startDir == "" { - if home, err := os.UserHomeDir(); err == nil { - startDir = home - } - } - return &Client{ - shell: shell, - startDir: startDir, - cols: cols, - rows: rows, - onData: cfg.OnData, - onError: cfg.OnError, - onClose: cfg.OnClose, - } -} - -// Start spawns the shell process and begins reading output. -func (c *Client) Start() error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed { - return fmt.Errorf("shell already closed") - } - if c.ptmx != nil { - return fmt.Errorf("shell already running") - } - - cmd := exec.Command(c.shell) - cmd.Env = terminalenv.LocalShellEnv(os.Environ()) - cmd.Dir = c.startDir - - ptmx, err := pty.StartWithSize(cmd, &pty.Winsize{ - Cols: c.cols, - Rows: c.rows, - }) - if err != nil { - return fmt.Errorf("failed to start shell %q: %w", c.shell, err) - } - - c.cmd = cmd - c.ptmx = ptmx - - // Read loop — forward PTY output to the frontend. - go c.readLoop(ptmx) - - // Wait for process exit. - go func() { - _ = cmd.Wait() - c.mu.Lock() - wasClosed := c.closed - c.closed = true - if c.ptmx == ptmx { - c.ptmx = nil - } - c.mu.Unlock() - - if !wasClosed && c.onClose != nil { - c.onClose() - } - }() - - return nil -} - -// Write sends data to the shell's stdin (PTY). -func (c *Client) Write(data []byte) error { - // Capture the shell's current directory before it handles a command or - // EOF. This lets a replacement shell resume in the same directory after - // an ordinary `exit`, without injecting shell-specific prompt hooks. - if bytesContainCommandBoundary(data) { - c.captureWorkingDirectory() - } - - c.mu.Lock() - ptmx := c.ptmx - c.mu.Unlock() - - if ptmx == nil { - return fmt.Errorf("shell not running") - } - _, err := ptmx.Write(data) - return err -} - -// Resize updates the PTY window size. -func (c *Client) Resize(cols, rows uint16) error { - c.mu.Lock() - if cols > 0 { - c.cols = cols - } - if rows > 0 { - c.rows = rows - } - ptmx := c.ptmx - cols = c.cols - rows = c.rows - c.mu.Unlock() - - if ptmx == nil { - return nil - } - return pty.Setsize(ptmx, &pty.Winsize{Cols: cols, Rows: rows}) -} - -// Close terminates the shell process and PTY. -func (c *Client) Close() { - c.mu.Lock() - if c.closed { - c.mu.Unlock() - return - } - c.closed = true - ptmx := c.ptmx - cmd := c.cmd - c.ptmx = nil - c.mu.Unlock() - - if ptmx != nil { - _ = ptmx.Close() - } - if cmd != nil && cmd.Process != nil { - _ = cmd.Process.Kill() - } -} - -// WorkingDirectory returns the last directory observed for the shell. -func (c *Client) WorkingDirectory() string { - c.mu.Lock() - defer c.mu.Unlock() - return c.startDir -} - -func (c *Client) readLoop(ptmx *os.File) { - defer ptmx.Close() - buf := make([]byte, 32*1024) - for { - n, err := ptmx.Read(buf) - if n > 0 && c.onData != nil { - c.onData(buf[:n]) - } - if err != nil { - c.mu.Lock() - wasClosed := c.closed - c.mu.Unlock() - if !wasClosed && !isNormalPTYClosure(err) && c.onError != nil { - c.onError(err) - } - return - } - } -} - -func bytesContainCommandBoundary(data []byte) bool { - for _, b := range data { - if b == '\r' || b == '\n' || b == 0x04 { - return true - } - } - return false -} - -func isNormalPTYClosure(err error) bool { - return errors.Is(err, io.EOF) || errors.Is(err, os.ErrClosed) || errors.Is(err, syscall.EIO) -} - -func (c *Client) captureWorkingDirectory() { - c.mu.Lock() - cmd := c.cmd - c.mu.Unlock() - if cmd == nil || cmd.Process == nil { - return - } - - dir := processWorkingDirectory(cmd.Process.Pid) - if dir == "" || !isDirectory(dir) { - return - } - - c.mu.Lock() - if c.cmd == cmd { - c.startDir = dir - } - c.mu.Unlock() -} - -func processWorkingDirectory(pid int) string { - switch runtime.GOOS { - case "linux": - dir, err := os.Readlink("/proc/" + strconv.Itoa(pid) + "/cwd") - if err == nil { - return dir - } - case "darwin": - // macOS does not expose /proc. lsof is part of the base system and its - // field output gives us the cwd without parsing a human-readable table. - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() - out, err := exec.CommandContext(ctx, "/usr/sbin/lsof", "-a", "-p", strconv.Itoa(pid), "-d", "cwd", "-Fn").Output() - if err != nil { - return "" - } - for _, line := range strings.Split(string(out), "\n") { - if strings.HasPrefix(line, "n") { - return strings.TrimPrefix(line, "n") - } - } - } - return "" -} - -func isDirectory(path string) bool { - info, err := os.Stat(path) - return err == nil && info.IsDir() -} diff --git a/internal/localshell/localshell_test.go b/internal/localshell/localshell_test.go deleted file mode 100644 index e8c3f5a..0000000 --- a/internal/localshell/localshell_test.go +++ /dev/null @@ -1,146 +0,0 @@ -package localshell - -import ( - "bytes" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "testing" - "time" -) - -func TestClientUsesConfiguredDirectoryAndPreStartSize(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("local PTY test requires a Unix shell") - } - - dir := t.TempDir() - startDir := filepath.Join(dir, "working") - if err := os.Mkdir(startDir, 0o700); err != nil { - t.Fatal(err) - } - script := filepath.Join(dir, "report-terminal.sh") - if err := os.WriteFile(script, []byte("#!/bin/sh\nstty size\npwd\n"), 0o700); err != nil { - t.Fatal(err) - } - - var output bytes.Buffer - var outputMu sync.Mutex - closed := make(chan struct{}) - errors := make(chan error, 1) - client := NewClient(Config{ - Shell: script, - StartDir: startDir, - Cols: 80, - Rows: 24, - OnData: func(data []byte) { - outputMu.Lock() - _, _ = output.Write(data) - outputMu.Unlock() - }, - OnError: func(err error) { - errors <- err - }, - OnClose: func() { - close(closed) - }, - }) - - // A frontend fit can arrive before the process starts. The stored size - // must become the initial PTY geometry rather than being discarded. - if err := client.Resize(132, 43); err != nil { - t.Fatalf("Resize() before Start() error = %v", err) - } - if err := client.Start(); err != nil { - t.Fatalf("Start() error = %v", err) - } - - select { - case err := <-errors: - t.Fatalf("unexpected PTY read error: %v", err) - case <-closed: - case <-time.After(5 * time.Second): - client.Close() - t.Fatal("timed out waiting for local shell to exit") - } - - outputMu.Lock() - got := strings.ReplaceAll(output.String(), "\r", "") - outputMu.Unlock() - if !strings.Contains(got, "43 132\n") { - t.Fatalf("PTY output %q does not contain configured size 43 132", got) - } - if !strings.Contains(got, startDir+"\n") { - t.Fatalf("PTY output %q does not contain start directory %q", got, startDir) - } -} - -func TestBytesContainCommandBoundary(t *testing.T) { - for _, data := range [][]byte{[]byte("pwd\r"), []byte("pwd\n"), {0x04}} { - if !bytesContainCommandBoundary(data) { - t.Fatalf("bytesContainCommandBoundary(%q) = false, want true", data) - } - } - if bytesContainCommandBoundary([]byte("pwd")) { - t.Fatal("bytesContainCommandBoundary without enter/EOF = true, want false") - } -} - -func TestClientCapturesWorkingDirectoryBeforeExit(t *testing.T) { - if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { - t.Skip("process cwd lookup is implemented on macOS and Linux") - } - - dir := t.TempDir() - startDir := filepath.Join(dir, "start") - nextDir := filepath.Join(startDir, "next") - if err := os.MkdirAll(nextDir, 0o700); err != nil { - t.Fatal(err) - } - expectedDir, err := filepath.EvalSymlinks(nextDir) - if err != nil { - t.Fatal(err) - } - - closed := make(chan struct{}) - client := NewClient(Config{ - Shell: "/bin/sh", - StartDir: startDir, - OnClose: func() { - close(closed) - }, - }) - if err := client.Start(); err != nil { - t.Fatalf("Start() error = %v", err) - } - defer client.Close() - - if err := client.Write([]byte("cd next\r")); err != nil { - t.Fatalf("Write(cd) error = %v", err) - } - deadline := time.Now().Add(5 * time.Second) - for client.WorkingDirectory() != expectedDir && time.Now().Before(deadline) { - time.Sleep(20 * time.Millisecond) - // The empty command boundary snapshots the directory after cd has run. - if err := client.Write([]byte("\r")); err != nil { - t.Fatalf("Write(snapshot) error = %v", err) - } - } - if got := client.WorkingDirectory(); got != expectedDir { - t.Fatalf("WorkingDirectory() = %q, want %q", got, expectedDir) - } - - if err := client.Write([]byte("exit\r")); err != nil { - t.Fatalf("Write(exit) error = %v", err) - } - select { - case <-closed: - case <-time.After(5 * time.Second): - t.Fatal("timed out waiting for local shell to exit") - } - if got := client.WorkingDirectory(); got != expectedDir { - t.Fatalf("WorkingDirectory() after exit = %q, want %q", got, expectedDir) - } -} diff --git a/internal/models/defaults.go b/internal/models/defaults.go deleted file mode 100644 index 2e248a2..0000000 --- a/internal/models/defaults.go +++ /dev/null @@ -1,390 +0,0 @@ -package models - -import ( - "time" - - "github.com/google/uuid" -) - -// Default port numbers for each protocol. -const ( - DefaultSSHPort = 22 - DefaultTelnetPort = 23 - DefaultRAWPort = 23 - DefaultGitHubPort = 443 - DefaultVNCPort = 5900 - DefaultIRCPort = 6667 - DefaultIRCTLSPort = 6697 -) - -// Default timeout values (in seconds). -const ( - DefaultConnectTimeout = 30 - DefaultReadTimeout = 0 // no timeout -) - -// Default terminal rendering settings. -const ( - DefaultScrollback = 10000 - DefaultFontSize = 14 - DefaultFontFamily = "JetBrains Mono, Menlo, Monaco, Consolas, monospace" - DefaultCursorStyle = "block" - DefaultTerminalType = "xterm-256color" -) - -// Default SSH keepalive settings. -const ( - DefaultKeepaliveInterval = 30 - DefaultKeepaliveCountMax = 3 -) - -// DefaultAppPreferences returns sensible default application preferences. -func DefaultAppPreferences() AppPreferences { - return AppPreferences{ - Theme: "dark", - TerminalTheme: "charcoal", - DefaultProtocol: string(ProtocolSSH), - ConfirmOnClose: true, - ConfirmOnDisconnect: true, - ShowStatusBar: true, - SidebarWidth: 280, - DefaultFontFamily: DefaultFontFamily, - DefaultFontSize: DefaultFontSize, - DefaultScrollback: DefaultScrollback, - DefaultCursorStyle: DefaultCursorStyle, - UseNativePaste: false, - LocalShell: "", // empty means use system default - LeviathanEnabled: false, - } -} - -// NewProfile creates a new Profile with sensible defaults for the given protocol. -func NewProfile(protocol Protocol) Profile { - now := time.Now() - p := Profile{ - ID: uuid.New().String(), - Protocol: protocol, - Tags: []string{}, - ConnectTimeout: DefaultConnectTimeout, - ReadTimeout: DefaultReadTimeout, - TerminalFontFamily: DefaultFontFamily, - TerminalFontSize: DefaultFontSize, - TerminalScrollback: DefaultScrollback, - TerminalCursorStyle: DefaultCursorStyle, - CreatedAt: now, - UpdatedAt: now, - } - - switch protocol { - case ProtocolSSH: - p.Port = DefaultSSHPort - p.SSH = &SSHConfig{ - AuthMethods: []string{"publickey", "password", "keyboard-interactive"}, - SecretMode: SecretModeSaved, - KeySecretMode: SecretModeSaved, - HostKeyPolicy: HostKeyAsk, - TerminalType: DefaultTerminalType, - KeepaliveInterval: DefaultKeepaliveInterval, - KeepaliveCountMax: DefaultKeepaliveCountMax, - JumpHosts: []JumpHost{}, - PortForwards: []PortForward{}, - EnvVars: map[string]string{}, - } - case ProtocolTelnet: - p.Port = DefaultTelnetPort - p.Telnet = &TelnetConfig{ - LocalEcho: false, - LineEnding: LineEndingCRLF, - TerminalType: DefaultTerminalType, - Encoding: "utf-8", - } - case ProtocolRAW: - p.Port = DefaultRAWPort - p.RAW = &RAWConfig{ - LineEnding: LineEndingCRLF, - Encoding: "utf-8", - PayloadPresets: []PayloadPreset{}, - } - case ProtocolGitHub: - p.Port = DefaultGitHubPort - p.Host = "github.com" - p.GitHub = &GitHubConfig{ - BaseURL: "https://api.github.com", - SecretMode: SecretModeSaved, - RefreshIntervalSecs: 300, - } - case ProtocolVNC: - p.Port = DefaultVNCPort - p.VNC = &VNCConfig{ - VNCPort: DefaultVNCPort, - SecretMode: SecretModeSaved, - ScaleToFit: true, - SharedSession: true, - Quality: 6, - SSHPort: DefaultSSHPort, - SSHAuthMethod: "publickey", - SSHSecretMode: SecretModeSaved, - } - case ProtocolIRC: - p.Port = DefaultIRCPort - p.IRC = &IRCConfig{ - Nick: "link", - RealName: "Alchementary Link", - ServerPasswordMode: SecretModeAskEveryTime, - NickServMode: SecretModeSaved, - AutoJoinRooms: []string{}, - SSHPort: DefaultSSHPort, - SSHAuthMethod: "publickey", - SSHSecretMode: SecretModeSaved, - AutoReconnect: true, - } - } - - return p -} - -// NormalizeProfileDefaults fills missing default values on profiles loaded -// from older exports, third-party imports, or sparse frontend payloads. -func NormalizeProfileDefaults(p *Profile) { - if p == nil { - return - } - if p.Protocol == "" { - p.Protocol = ProtocolSSH - } - - defaults := NewProfile(p.Protocol) - if p.ID == "" { - p.ID = defaults.ID - } - if p.Tags == nil { - p.Tags = []string{} - } - if p.ConnectTimeout == 0 { - p.ConnectTimeout = DefaultConnectTimeout - } - if p.TerminalFontFamily == "" { - p.TerminalFontFamily = DefaultFontFamily - } - if p.TerminalFontSize == 0 { - p.TerminalFontSize = DefaultFontSize - } - if p.TerminalScrollback == 0 { - p.TerminalScrollback = DefaultScrollback - } - if p.TerminalCursorStyle == "" { - p.TerminalCursorStyle = DefaultCursorStyle - } - if p.CreatedAt.IsZero() { - p.CreatedAt = defaults.CreatedAt - } - if p.UpdatedAt.IsZero() { - p.UpdatedAt = defaults.UpdatedAt - } - if p.Port == 0 { - p.Port = defaults.Port - } - if p.DisplayName == "" { - p.DisplayName = defaultProfileName(p) - } - - switch p.Protocol { - case ProtocolSSH: - normalizeSSHConfig(p, defaults.SSH) - case ProtocolTelnet: - normalizeTelnetConfig(p, defaults.Telnet) - case ProtocolRAW: - normalizeRAWConfig(p, defaults.RAW) - case ProtocolGitHub: - if p.Host == "" { - p.Host = defaults.Host - } - normalizeGitHubConfig(p, defaults.GitHub) - case ProtocolVNC: - normalizeVNCConfig(p, defaults.VNC) - case ProtocolIRC: - normalizeIRCConfig(p, defaults.IRC) - } -} - -func defaultProfileName(p *Profile) string { - if p.Host != "" { - return p.Host - } - if p.VNC != nil && p.VNC.VNCHost != "" { - return p.VNC.VNCHost - } - switch p.Protocol { - case ProtocolGitHub: - return "GitHub" - case ProtocolVNC: - return "VNC" - case ProtocolIRC: - return "IRC" - case ProtocolRAW: - return "RAW TCP" - case ProtocolTelnet: - return "Telnet" - default: - return "SSH" - } -} - -func normalizeSecretMode(mode SecretMode, fallback SecretMode) SecretMode { - switch mode { - case SecretModeSaved, SecretModeSessionOnly, SecretModeAskEveryTime: - return mode - case "prompt": - return SecretModeAskEveryTime - case "": - return fallback - default: - return mode - } -} - -func normalizeSSHConfig(p *Profile, defaults *SSHConfig) { - if p.SSH == nil { - cfg := *defaults - p.SSH = &cfg - return - } - cfg := p.SSH - if len(cfg.AuthMethods) == 0 { - cfg.AuthMethods = append([]string{}, defaults.AuthMethods...) - } - cfg.SecretMode = normalizeSecretMode(cfg.SecretMode, defaults.SecretMode) - cfg.KeySecretMode = normalizeSecretMode(cfg.KeySecretMode, defaults.KeySecretMode) - if cfg.HostKeyPolicy == "" { - cfg.HostKeyPolicy = defaults.HostKeyPolicy - } - if cfg.TerminalType == "" { - cfg.TerminalType = defaults.TerminalType - } - if cfg.KeepaliveInterval == 0 { - cfg.KeepaliveInterval = defaults.KeepaliveInterval - } - if cfg.KeepaliveCountMax == 0 { - cfg.KeepaliveCountMax = defaults.KeepaliveCountMax - } - if cfg.JumpHosts == nil { - cfg.JumpHosts = []JumpHost{} - } - if cfg.PortForwards == nil { - cfg.PortForwards = []PortForward{} - } - if cfg.EnvVars == nil { - cfg.EnvVars = map[string]string{} - } -} - -func normalizeTelnetConfig(p *Profile, defaults *TelnetConfig) { - if p.Telnet == nil { - cfg := *defaults - p.Telnet = &cfg - return - } - cfg := p.Telnet - if cfg.LineEnding == "" { - cfg.LineEnding = defaults.LineEnding - } - if cfg.TerminalType == "" { - cfg.TerminalType = defaults.TerminalType - } - if cfg.Encoding == "" { - cfg.Encoding = defaults.Encoding - } -} - -func normalizeRAWConfig(p *Profile, defaults *RAWConfig) { - if p.RAW == nil { - cfg := *defaults - p.RAW = &cfg - return - } - cfg := p.RAW - if cfg.LineEnding == "" { - cfg.LineEnding = defaults.LineEnding - } - if cfg.Encoding == "" { - cfg.Encoding = defaults.Encoding - } - if cfg.PayloadPresets == nil { - cfg.PayloadPresets = []PayloadPreset{} - } - for i := range cfg.PayloadPresets { - if cfg.PayloadPresets[i].ID == "" { - cfg.PayloadPresets[i].ID = uuid.New().String() - } - if cfg.PayloadPresets[i].Format == "" { - cfg.PayloadPresets[i].Format = "text" - } - } -} - -func normalizeGitHubConfig(p *Profile, defaults *GitHubConfig) { - if p.GitHub == nil { - cfg := *defaults - p.GitHub = &cfg - return - } - cfg := p.GitHub - if cfg.BaseURL == "" { - cfg.BaseURL = defaults.BaseURL - } - cfg.SecretMode = normalizeSecretMode(cfg.SecretMode, defaults.SecretMode) - if cfg.RefreshIntervalSecs == 0 { - cfg.RefreshIntervalSecs = defaults.RefreshIntervalSecs - } -} - -func normalizeVNCConfig(p *Profile, defaults *VNCConfig) { - if p.VNC == nil { - cfg := *defaults - p.VNC = &cfg - return - } - cfg := p.VNC - if cfg.VNCPort == 0 { - cfg.VNCPort = defaults.VNCPort - } - cfg.SecretMode = normalizeSecretMode(cfg.SecretMode, defaults.SecretMode) - if cfg.Quality < 0 { - cfg.Quality = defaults.Quality - } - if cfg.SSHPort == 0 { - cfg.SSHPort = defaults.SSHPort - } - if cfg.SSHAuthMethod == "" { - cfg.SSHAuthMethod = defaults.SSHAuthMethod - } - cfg.SSHSecretMode = normalizeSecretMode(cfg.SSHSecretMode, defaults.SSHSecretMode) -} - -func normalizeIRCConfig(p *Profile, defaults *IRCConfig) { - if p.IRC == nil { - cfg := *defaults - cfg.AutoJoinRooms = append([]string{}, defaults.AutoJoinRooms...) - p.IRC = &cfg - return - } - cfg := p.IRC - if cfg.Nick == "" { - cfg.Nick = defaults.Nick - } - if cfg.RealName == "" { - cfg.RealName = defaults.RealName - } - cfg.ServerPasswordMode = normalizeSecretMode(cfg.ServerPasswordMode, defaults.ServerPasswordMode) - cfg.NickServMode = normalizeSecretMode(cfg.NickServMode, defaults.NickServMode) - if cfg.AutoJoinRooms == nil { - cfg.AutoJoinRooms = []string{} - } - if cfg.SSHPort == 0 { - cfg.SSHPort = defaults.SSHPort - } - if cfg.SSHAuthMethod == "" { - cfg.SSHAuthMethod = defaults.SSHAuthMethod - } - cfg.SSHSecretMode = normalizeSecretMode(cfg.SSHSecretMode, defaults.SSHSecretMode) -} diff --git a/internal/models/defaults_test.go b/internal/models/defaults_test.go deleted file mode 100644 index aef6ae2..0000000 --- a/internal/models/defaults_test.go +++ /dev/null @@ -1,10 +0,0 @@ -package models - -import "testing" - -func TestDefaultAppPreferencesHideLeviathan(t *testing.T) { - prefs := DefaultAppPreferences() - if prefs.LeviathanEnabled { - t.Fatal("LeviathanEnabled = true, want false by default") - } -} diff --git a/internal/models/models.go b/internal/models/models.go deleted file mode 100644 index a33827f..0000000 --- a/internal/models/models.go +++ /dev/null @@ -1,372 +0,0 @@ -// Package models defines the core data structures used throughout -// the Alchementary Link application. -package models - -import "time" - -// Protocol represents the connection protocol for a session. -type Protocol string - -const ( - ProtocolSSH Protocol = "ssh" - ProtocolTelnet Protocol = "telnet" - ProtocolRAW Protocol = "raw" - ProtocolGitHub Protocol = "github" - ProtocolVNC Protocol = "vnc" - ProtocolIRC Protocol = "irc" - ProtocolLocal Protocol = "local" -) - -// HostKeyPolicy determines how unknown or changed SSH host keys are handled. -type HostKeyPolicy string - -const ( - HostKeyStrict HostKeyPolicy = "strict" - HostKeyAsk HostKeyPolicy = "ask" - HostKeyAcceptNew HostKeyPolicy = "accept-new" - HostKeyInsecure HostKeyPolicy = "insecure" -) - -// SecretMode controls how credentials are persisted. -type SecretMode string - -const ( - SecretModeSaved SecretMode = "saved" - SecretModeSessionOnly SecretMode = "session-only" - SecretModeAskEveryTime SecretMode = "ask-every-time" -) - -// LineEnding specifies the line-ending convention for Telnet and RAW connections. -type LineEnding string - -const ( - LineEndingLF LineEnding = "lf" - LineEndingCR LineEnding = "cr" - LineEndingCRLF LineEnding = "crlf" - LineEndingNone LineEnding = "none" -) - -// Profile holds the full configuration for a connection profile. -type Profile struct { - ID string `json:"id"` - DisplayName string `json:"displayName"` - Description string `json:"description"` - Protocol Protocol `json:"protocol"` - Host string `json:"host"` - Port int `json:"port"` - Username string `json:"username"` - Tags []string `json:"tags"` - Folder string `json:"folder"` - Environment string `json:"environment"` - Notes string `json:"notes"` - Favourite bool `json:"favourite"` - LastConnected *time.Time `json:"lastConnected" ts_type:"string"` - ConnectTimeout int `json:"connectTimeout"` // seconds - ReadTimeout int `json:"readTimeout"` // seconds - CreatedAt time.Time `json:"createdAt" ts_type:"string"` - UpdatedAt time.Time `json:"updatedAt" ts_type:"string"` - - // Terminal preferences - TerminalFontFamily string `json:"terminalFontFamily"` - TerminalFontSize int `json:"terminalFontSize"` - TerminalScrollback int `json:"terminalScrollback"` - TerminalCursorStyle string `json:"terminalCursorStyle"` - - // SSH-specific configuration - SSH *SSHConfig `json:"ssh,omitempty"` - - // Telnet-specific configuration - Telnet *TelnetConfig `json:"telnet,omitempty"` - - // RAW-specific configuration - RAW *RAWConfig `json:"raw,omitempty"` - - // GitHub-specific configuration - GitHub *GitHubConfig `json:"github,omitempty"` - - // VNC-specific configuration - VNC *VNCConfig `json:"vnc,omitempty"` - - // IRC-specific configuration - IRC *IRCConfig `json:"irc,omitempty"` -} - -// SSHConfig holds SSH-specific settings for a connection profile. -type SSHConfig struct { - AuthMethods []string `json:"authMethods"` // password, publickey, keyboard-interactive - SecretMode SecretMode `json:"secretMode"` - PrivateKeyPath string `json:"privateKeyPath"` - KeySecretMode SecretMode `json:"keySecretMode"` - HostKeyPolicy HostKeyPolicy `json:"hostKeyPolicy"` - TerminalType string `json:"terminalType"` - KeepaliveInterval int `json:"keepaliveInterval"` - KeepaliveCountMax int `json:"keepaliveCountMax"` - Compression bool `json:"compression"` - AgentAuth bool `json:"agentAuth"` // use SSH agent for authentication - AgentForwarding bool `json:"agentForwarding"` // forward agent to remote host - AutoReconnect bool `json:"autoReconnect"` // reconnect on unexpected disconnect - MaxReconnectTries int `json:"maxReconnectTries"` // 0 = use default (5) - LoginScripts []LoginScriptStep `json:"loginScripts"` // expect-style auto-login sequence - TOTPSecret string `json:"totpSecret"` // TOTP secret key for 2FA auto-fill (stored encrypted) - ProxyType string `json:"proxyType"` // "none", "socks5", "http" - ProxyHost string `json:"proxyHost"` - ProxyPort int `json:"proxyPort"` - ProxyUsername string `json:"proxyUsername"` - ProxyPassword string `json:"proxyPassword"` // stored in vault for production; inline for simplicity - JumpHosts []JumpHost `json:"jumpHosts"` - PortForwards []PortForward `json:"portForwards"` - StartupCommand string `json:"startupCommand"` - EnvVars map[string]string `json:"envVars"` -} - -// LoginScriptStep represents a single step in an expect-style auto-login sequence. -type LoginScriptStep struct { - Type string `json:"type"` // "wait" or "send" - Value string `json:"value"` // text to wait for / text to send - Timeout int `json:"timeout"` // max seconds to wait (for "wait" steps; 0 = use default 10s) -} - -// JumpHost describes a proxy/bastion host used for SSH multi-hop connections. -type JumpHost struct { - Host string `json:"host"` - Port int `json:"port"` - Username string `json:"username"` - AuthMethod string `json:"authMethod"` - SecretMode SecretMode `json:"secretMode"` - PrivateKeyPath string `json:"privateKeyPath"` -} - -// PortForwardDirection indicates the tunnel direction. -type PortForwardDirection string - -const ( - PortForwardLocal PortForwardDirection = "local" - PortForwardRemote PortForwardDirection = "remote" -) - -// PortForward defines a single SSH port-forwarding rule. -type PortForward struct { - ID string `json:"id"` - Direction PortForwardDirection `json:"direction"` - BindHost string `json:"bindHost"` // local address to listen on (local) or remote bind (remote) - BindPort int `json:"bindPort"` - DestHost string `json:"destHost"` // target address to forward to - DestPort int `json:"destPort"` - Enabled bool `json:"enabled"` // whether to activate on connect - Label string `json:"label"` // optional descriptive label -} - -// TelnetConfig holds Telnet-specific settings for a connection profile. -type TelnetConfig struct { - LocalEcho bool `json:"localEcho"` - LineEnding LineEnding `json:"lineEnding"` - TerminalType string `json:"terminalType"` - Encoding string `json:"encoding"` - NegotiationDebug bool `json:"negotiationDebug"` - LineMode bool `json:"lineMode"` - AutoReconnect bool `json:"autoReconnect"` // reconnect on unexpected disconnect - MaxReconnectTries int `json:"maxReconnectTries"` // 0 = use default (5) - LoginScripts []LoginScriptStep `json:"loginScripts"` // expect-style auto-login sequence -} - -// RAWConfig holds RAW TCP-specific settings for a connection profile. -type RAWConfig struct { - AutoReconnect bool `json:"autoReconnect"` - LineEnding LineEnding `json:"lineEnding"` - Encoding string `json:"encoding"` - ShowTimestamps bool `json:"showTimestamps"` - ShowDirection bool `json:"showDirection"` - PayloadPresets []PayloadPreset `json:"payloadPresets"` -} - -// PayloadPreset stores a reusable payload for RAW TCP connections. -type PayloadPreset struct { - ID string `json:"id"` - Name string `json:"name"` - Payload string `json:"payload"` // base64-encoded for binary safety - Format string `json:"format"` // text, hex, escaped -} - -// GitHubConfig holds GitHub API-specific settings for a connection profile. -type GitHubConfig struct { - // BaseURL is the GitHub API base (e.g. "https://api.github.com" for - // github.com, or "https://github.example.com/api/v3" for GHE). - BaseURL string `json:"baseURL"` - - // SecretMode controls how the personal access token is stored. - SecretMode SecretMode `json:"secretMode"` - - // RefreshIntervalSecs controls the auto-refresh period in seconds. - // Defaults to 300 (5 minutes). - RefreshIntervalSecs int `json:"refreshIntervalSecs"` -} - -// VNCConfig holds VNC-specific settings for a connection profile. -type VNCConfig struct { - // VNCHost is the VNC server address (defaults to profile.Host if empty). - VNCHost string `json:"vncHost"` - - // VNCPort is the VNC server port (5900 + display number, default 5900). - VNCPort int `json:"vncPort"` - - // SecretMode controls how the VNC password is stored. - SecretMode SecretMode `json:"secretMode"` - - // ViewOnly disables sending keyboard and mouse events to the server. - ViewOnly bool `json:"viewOnly"` - - // ScaleToFit scales the remote framebuffer to fit the panel. - ScaleToFit bool `json:"scaleToFit"` - - // Quality controls JPEG compression quality (1-9, 0 = default). - Quality int `json:"quality"` - - // SharedSession allows other VNC viewers to remain connected. - SharedSession bool `json:"sharedSession"` - - // SSH tunnel settings — if enabled, VNC traffic is tunnelled through an SSH connection. - SSHTunnel bool `json:"sshTunnel"` - SSHHost string `json:"sshHost"` - SSHPort int `json:"sshPort"` - SSHUsername string `json:"sshUsername"` - SSHAuthMethod string `json:"sshAuthMethod"` // password, publickey - SSHSecretMode SecretMode `json:"sshSecretMode"` - SSHPrivateKey string `json:"sshPrivateKeyPath"` - SSHAgentAuth bool `json:"sshAgentAuth"` -} - -// IRCConfig holds IRC-specific settings for a connection profile. -type IRCConfig struct { - // Nick is the primary nickname used when connecting to the IRC network. - Nick string `json:"nick"` - - // RealName is the user-facing realname/gecos sent during registration. - RealName string `json:"realName"` - - // UseTLS enables TLS for the IRC server connection. - UseTLS bool `json:"useTLS"` - - // ServerPasswordRequired controls whether to prompt for an IRC server PASS secret. - ServerPasswordRequired bool `json:"serverPasswordRequired"` - ServerPasswordMode SecretMode `json:"serverPasswordMode"` - - // NickServ settings. The password is resolved through the secret store. - NickServIdentify bool `json:"nickServIdentify"` - NickServAccount string `json:"nickServAccount"` - NickServMode SecretMode `json:"nickServMode"` - - // AutoJoinRooms are joined automatically after registration. - AutoJoinRooms []string `json:"autoJoinRooms"` - - // SSH tunnel settings — if enabled, IRC traffic is tunnelled through SSH. - SSHTunnel bool `json:"sshTunnel"` - SSHHost string `json:"sshHost"` - SSHPort int `json:"sshPort"` - SSHUsername string `json:"sshUsername"` - SSHAuthMethod string `json:"sshAuthMethod"` // password, publickey - SSHSecretMode SecretMode `json:"sshSecretMode"` - SSHPrivateKey string `json:"sshPrivateKeyPath"` - SSHAgentAuth bool `json:"sshAgentAuth"` - - // Reconnect policy for unexpected disconnects. - AutoReconnect bool `json:"autoReconnect"` - MaxReconnectTries int `json:"maxReconnectTries"` // 0 = use default (5) -} - -// GitHubRepo is a single repository returned to the frontend for display. -type GitHubRepo struct { - FullName string `json:"fullName"` - Name string `json:"name"` - Owner string `json:"owner"` - Description string `json:"description"` - HTMLURL string `json:"htmlURL"` - DefaultBranch string `json:"defaultBranch"` - LatestCommitter string `json:"latestCommitter"` - LatestCommitAt string `json:"latestCommitAt"` - Private bool `json:"private"` - Fork bool `json:"fork"` - Archived bool `json:"archived"` - Language string `json:"language"` - Stars int `json:"stars"` - PushedAt string `json:"pushedAt"` // ISO 8601 - UpdatedAt string `json:"updatedAt"` -} - -// SessionState represents the lifecycle state of a connection session. -type SessionState string - -const ( - SessionConnecting SessionState = "connecting" - SessionConnected SessionState = "connected" - SessionDisconnected SessionState = "disconnected" - SessionReconnecting SessionState = "reconnecting" - SessionError SessionState = "error" -) - -// SessionInfo provides metadata about an active or recent session. -type SessionInfo struct { - ID string `json:"id"` - ProfileID string `json:"profileId"` - ProfileName string `json:"profileName"` - Protocol Protocol `json:"protocol"` - Host string `json:"host"` - Port int `json:"port"` - State SessionState `json:"state"` - TabName string `json:"tabName"` - Pinned bool `json:"pinned"` - ConnectedAt time.Time `json:"connectedAt" ts_type:"string"` - Error string `json:"error,omitempty"` -} - -// KnownHost records a previously-seen SSH host key fingerprint. -type KnownHost struct { - ID string `json:"id"` - Host string `json:"host"` - Port int `json:"port"` - KeyType string `json:"keyType"` - Fingerprint string `json:"fingerprint"` - PublicKey string `json:"publicKey"` - FirstSeen time.Time `json:"firstSeen" ts_type:"string"` - LastSeen time.Time `json:"lastSeen" ts_type:"string"` - Trusted bool `json:"trusted"` -} - -// AppPreferences stores user-level application settings. -type AppPreferences struct { - Theme string `json:"theme"` - TerminalTheme string `json:"terminalTheme"` // colour scheme name (e.g. "charcoal", "solarized-dark") - DefaultProtocol string `json:"defaultProtocol"` - ConfirmOnClose bool `json:"confirmOnClose"` - ConfirmOnDisconnect bool `json:"confirmOnDisconnect"` - ShowStatusBar bool `json:"showStatusBar"` - SidebarWidth int `json:"sidebarWidth"` - DefaultFontFamily string `json:"defaultFontFamily"` - DefaultFontSize int `json:"defaultFontSize"` - DefaultScrollback int `json:"defaultScrollback"` - DefaultCursorStyle string `json:"defaultCursorStyle"` - UseNativePaste bool `json:"useNativePaste"` - LocalShell string `json:"localShell"` // path to shell binary for local terminal - LeviathanEnabled bool `json:"leviathanEnabled"` -} - -// AuditEntry records an auditable action within the application. -type AuditEntry struct { - ID string `json:"id"` - Timestamp time.Time `json:"timestamp" ts_type:"string"` - Action string `json:"action"` - ProfileID string `json:"profileId,omitempty"` - SessionID string `json:"sessionId,omitempty"` - Details string `json:"details"` -} - -// Snippet stores a reusable command for the snippet library. -type Snippet struct { - ID string `json:"id"` - Name string `json:"name"` - Command string `json:"command"` - Description string `json:"description"` - Folder string `json:"folder"` - Tags []string `json:"tags"` - CreatedAt time.Time `json:"createdAt" ts_type:"string"` - UpdatedAt time.Time `json:"updatedAt" ts_type:"string"` -} diff --git a/internal/preferences/preferences.go b/internal/preferences/preferences.go deleted file mode 100644 index 0ead2f2..0000000 --- a/internal/preferences/preferences.go +++ /dev/null @@ -1,107 +0,0 @@ -// Package preferences manages application preferences with in-memory caching -// and persistence to a backing store. -package preferences - -import ( - "fmt" - "sync" - - "github.com/andydixon/link/internal/models" -) - -// PreferenceStore is the persistence interface used by the Manager. -type PreferenceStore interface { - GetPreferences() (*models.AppPreferences, error) - SavePreferences(prefs *models.AppPreferences) error -} - -// Manager handles application preferences with defaults and caching. -// It is safe for concurrent use. -type Manager struct { - store PreferenceStore - cache *models.AppPreferences - mu sync.RWMutex -} - -// NewManager creates a preferences Manager backed by the given store. -// Call Load to populate the cache from disc. -func NewManager(store PreferenceStore) *Manager { - return &Manager{store: store} -} - -// Load reads preferences from the backing store into memory. If no -// preferences have been saved yet, sensible defaults are applied. -func (m *Manager) Load() error { - m.mu.Lock() - defer m.mu.Unlock() - - prefs, err := m.store.GetPreferences() - if err != nil { - return fmt.Errorf("preferences: failed to load: %w", err) - } - - if prefs == nil { - defaults := models.DefaultAppPreferences() - m.cache = &defaults - } else { - m.cache = prefs - m.applyDefaults() - } - return nil -} - -// Get returns the current preferences. If Load has not been called, defaults -// are returned. -func (m *Manager) Get() *models.AppPreferences { - m.mu.RLock() - defer m.mu.RUnlock() - - if m.cache == nil { - defaults := models.DefaultAppPreferences() - return &defaults - } - // Return a copy to prevent callers mutating the cache. - cp := *m.cache - return &cp -} - -// Save persists the given preferences to the backing store and updates the -// in-memory cache. -func (m *Manager) Save(prefs *models.AppPreferences) error { - m.mu.Lock() - defer m.mu.Unlock() - - if err := m.store.SavePreferences(prefs); err != nil { - return fmt.Errorf("preferences: failed to save: %w", err) - } - cp := *prefs - m.cache = &cp - return nil -} - -// applyDefaults fills in zero-valued fields from the default preferences. -// Caller must hold m.mu. -func (m *Manager) applyDefaults() { - defaults := models.DefaultAppPreferences() - if m.cache.Theme == "" { - m.cache.Theme = defaults.Theme - } - if m.cache.DefaultProtocol == "" { - m.cache.DefaultProtocol = defaults.DefaultProtocol - } - if m.cache.SidebarWidth == 0 { - m.cache.SidebarWidth = defaults.SidebarWidth - } - if m.cache.DefaultFontFamily == "" { - m.cache.DefaultFontFamily = defaults.DefaultFontFamily - } - if m.cache.DefaultFontSize == 0 { - m.cache.DefaultFontSize = defaults.DefaultFontSize - } - if m.cache.DefaultScrollback == 0 { - m.cache.DefaultScrollback = defaults.DefaultScrollback - } - if m.cache.DefaultCursorStyle == "" { - m.cache.DefaultCursorStyle = defaults.DefaultCursorStyle - } -} diff --git a/internal/rawclient/encoding.go b/internal/rawclient/encoding.go deleted file mode 100644 index a7acc2b..0000000 --- a/internal/rawclient/encoding.go +++ /dev/null @@ -1,229 +0,0 @@ -package rawclient - -import ( - "encoding/hex" - "fmt" - "strconv" - "strings" - "unicode" - "unicode/utf8" - - "golang.org/x/text/encoding/charmap" -) - -// ParseHexString converts a hex string (with optional spaces and 0x prefixes) -// to raw bytes. -// -// Accepted formats: -// -// "48 65 6c 6c 6f" -// "48656c6c6f" -// "0x48 0x65 0x6c 0x6c 0x6f" -func ParseHexString(s string) ([]byte, error) { - s = strings.TrimSpace(s) - if s == "" { - return nil, fmt.Errorf("empty hex string") - } - - // Strip 0x/0X prefixes. - s = strings.ReplaceAll(s, "0x", "") - s = strings.ReplaceAll(s, "0X", "") - - // Collect only hex digit characters. - var cleaned strings.Builder - for _, r := range s { - if unicode.IsSpace(r) { - continue - } - if !isHexDigit(r) { - return nil, fmt.Errorf("invalid character in hex string: %q", r) - } - cleaned.WriteRune(r) - } - - if cleaned.Len()%2 != 0 { - return nil, fmt.Errorf("hex string has odd number of digits") - } - - return hex.DecodeString(cleaned.String()) -} - -// FormatHex formats bytes as a space-separated hex string. -// Example output: "48 65 6c 6c 6f" -func FormatHex(data []byte) string { - if len(data) == 0 { - return "" - } - parts := make([]string, len(data)) - for i, b := range data { - parts[i] = fmt.Sprintf("%02x", b) - } - return strings.Join(parts, " ") -} - -// FormatHexDump formats bytes as a traditional hex dump with offset, hex, -// and ASCII columns. Each line shows 16 bytes. -// -// Example output: -// -// 00000000 48 65 6c 6c 6f 20 57 6f 72 6c 64 21 |Hello World! | -func FormatHexDump(data []byte) string { - if len(data) == 0 { - return "" - } - - var sb strings.Builder - for offset := 0; offset < len(data); offset += 16 { - fmt.Fprintf(&sb, "%08x ", offset) - - end := offset + 16 - if end > len(data) { - end = len(data) - } - chunk := data[offset:end] - - // Hex columns (two groups of eight bytes). - for i := 0; i < 16; i++ { - if i == 8 { - sb.WriteByte(' ') - } - if i < len(chunk) { - fmt.Fprintf(&sb, "%02x ", chunk[i]) - } else { - sb.WriteString(" ") - } - } - - // ASCII column. - sb.WriteString(" |") - for _, b := range chunk { - if b >= 0x20 && b <= 0x7e { - sb.WriteByte(b) - } else { - sb.WriteByte('.') - } - } - for i := len(chunk); i < 16; i++ { - sb.WriteByte(' ') - } - sb.WriteString("|\n") - } - return sb.String() -} - -// ParseEscaped processes C-style escape sequences in a string. -// Supported sequences: \r \n \t \\ \0 \xNN -func ParseEscaped(s string) ([]byte, error) { - var result []byte - i := 0 - for i < len(s) { - if s[i] != '\\' { - r, size := utf8.DecodeRuneInString(s[i:]) - if r == utf8.RuneError && size <= 1 { - result = append(result, s[i]) - i++ - } else { - result = append(result, s[i:i+size]...) - i += size - } - continue - } - - if i+1 >= len(s) { - return nil, fmt.Errorf("trailing backslash at position %d", i) - } - - next := s[i+1] - switch next { - case 'r': - result = append(result, '\r') - i += 2 - case 'n': - result = append(result, '\n') - i += 2 - case 't': - result = append(result, '\t') - i += 2 - case '\\': - result = append(result, '\\') - i += 2 - case '0': - result = append(result, 0) - i += 2 - case 'x', 'X': - if i+3 >= len(s) { - return nil, fmt.Errorf("incomplete \\x escape at position %d", i) - } - hexStr := s[i+2 : i+4] - b, err := strconv.ParseUint(hexStr, 16, 8) - if err != nil { - return nil, fmt.Errorf("invalid \\x escape at position %d: %s", i, hexStr) - } - result = append(result, byte(b)) - i += 4 - default: - return nil, fmt.Errorf("unknown escape sequence \\%c at position %d", next, i) - } - } - return result, nil -} - -// ToDisplayEncoding converts raw bytes to a display string using the -// specified encoding. Supported encodings: "utf-8" (default), "ascii", -// "iso-8859-1" (also "latin1"). -func ToDisplayEncoding(data []byte, encoding string) string { - switch strings.ToLower(strings.TrimSpace(encoding)) { - case "ascii": - return toASCII(data) - case "iso-8859-1", "latin1", "latin-1": - return toLatin1(data) - default: - return toUTF8(data) - } -} - -// toASCII replaces non-ASCII bytes with '.', preserving common control chars. -func toASCII(data []byte) string { - var sb strings.Builder - sb.Grow(len(data)) - for _, b := range data { - if b >= 0x20 && b <= 0x7e { - sb.WriteByte(b) - } else if b == '\n' || b == '\r' || b == '\t' { - sb.WriteByte(b) - } else { - sb.WriteByte('.') - } - } - return sb.String() -} - -// toLatin1 decodes bytes as ISO 8859-1. -func toLatin1(data []byte) string { - decoded, err := charmap.ISO8859_1.NewDecoder().Bytes(data) - if err != nil { - return string(data) - } - return string(decoded) -} - -// toUTF8 ensures the output is valid UTF-8, replacing invalid bytes with -// the Unicode replacement character. -func toUTF8(data []byte) string { - if utf8.Valid(data) { - return string(data) - } - var sb strings.Builder - sb.Grow(len(data)) - for len(data) > 0 { - r, size := utf8.DecodeRune(data) - sb.WriteRune(r) - data = data[size:] - } - return sb.String() -} - -// isHexDigit reports whether r is a valid hexadecimal digit. -func isHexDigit(r rune) bool { - return (r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F') -} diff --git a/internal/rawclient/encoding_test.go b/internal/rawclient/encoding_test.go deleted file mode 100644 index 949ef20..0000000 --- a/internal/rawclient/encoding_test.go +++ /dev/null @@ -1,226 +0,0 @@ -package rawclient - -import ( - "bytes" - "strings" - "testing" -) - -// --------------------------------------------------------------------------- -// ParseHexString -// --------------------------------------------------------------------------- - -func TestParseHexString(t *testing.T) { - tests := []struct { - name string - input string - want []byte - wantErr bool - }{ - { - name: "contiguous hex for Hello", - input: "48656c6c6f", - want: []byte("Hello"), - }, - { - name: "space-separated hex", - input: "48 65 6c 6c 6f", - want: []byte("Hello"), - }, - { - name: "0x prefix", - input: "0x48 0x65 0x6c 0x6c 0x6f", - want: []byte("Hello"), - }, - { - name: "empty string", - input: "", - wantErr: true, - }, - { - name: "invalid characters", - input: "ZZZZ", - wantErr: true, - }, - { - name: "null bytes", - input: "0000", - want: []byte{0x00, 0x00}, - }, - { - name: "0xff bytes", - input: "ff ff", - want: []byte{0xff, 0xff}, - }, - { - name: "odd number of digits", - input: "abc", - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ParseHexString(tt.input) - if (err != nil) != tt.wantErr { - t.Errorf("ParseHexString(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) - return - } - if !tt.wantErr && !bytes.Equal(got, tt.want) { - t.Errorf("ParseHexString(%q) = %v, want %v", tt.input, got, tt.want) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ParseEscaped -// --------------------------------------------------------------------------- - -func TestParseEscaped(t *testing.T) { - tests := []struct { - name string - input string - want []byte - wantErr bool - }{ - {name: "plain text", input: "hello", want: []byte("hello")}, - {name: "newline escape", input: `\n`, want: []byte{'\n'}}, - {name: "carriage return + newline", input: `\r\n`, want: []byte{'\r', '\n'}}, - {name: "tab escape", input: `\t`, want: []byte{'\t'}}, - {name: "null byte", input: `\0`, want: []byte{0x00}}, - {name: "hex escape 0xff", input: `\xff`, want: []byte{0xff}}, - {name: "escaped backslash", input: `\\`, want: []byte{'\\'}}, - {name: "mixed AT command", input: `AT\r\n`, want: []byte{'A', 'T', '\r', '\n'}}, - {name: "hex escape 0x00", input: `\x00`, want: []byte{0x00}}, - {name: "trailing backslash", input: `hello\`, wantErr: true}, - {name: "unknown escape", input: `\q`, wantErr: true}, - {name: "incomplete hex escape", input: `\x0`, wantErr: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := ParseEscaped(tt.input) - if (err != nil) != tt.wantErr { - t.Errorf("ParseEscaped(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr) - return - } - if !tt.wantErr && !bytes.Equal(got, tt.want) { - t.Errorf("ParseEscaped(%q) = %v, want %v", tt.input, got, tt.want) - } - }) - } -} - -// --------------------------------------------------------------------------- -// FormatHexDump -// --------------------------------------------------------------------------- - -func TestFormatHexDump(t *testing.T) { - t.Run("empty data", func(t *testing.T) { - got := FormatHexDump(nil) - if got != "" { - t.Errorf("FormatHexDump(nil) = %q, want empty", got) - } - }) - - t.Run("small data", func(t *testing.T) { - got := FormatHexDump([]byte("Hi")) - if !strings.Contains(got, "48 69") { - t.Errorf("expected hex for 'Hi', got:\n%s", got) - } - // Should contain the ASCII display column. - if !strings.Contains(got, "|Hi") { - t.Errorf("expected ASCII column with 'Hi', got:\n%s", got) - } - }) - - t.Run("16+ bytes spans multiple lines", func(t *testing.T) { - data := []byte("ABCDEFGHIJKLMNOPQRST") // 20 bytes - got := FormatHexDump(data) - lines := strings.Split(strings.TrimRight(got, "\n"), "\n") - if len(lines) < 2 { - t.Errorf("expected at least 2 lines for 20 bytes, got %d", len(lines)) - } - // First line should start with offset 00000000. - if !strings.HasPrefix(lines[0], "00000000") { - t.Errorf("first line should start with 00000000, got %q", lines[0]) - } - // Second line should start with offset 00000010. - if !strings.HasPrefix(lines[1], "00000010") { - t.Errorf("second line should start with 00000010, got %q", lines[1]) - } - }) -} - -// --------------------------------------------------------------------------- -// FormatHex -// --------------------------------------------------------------------------- - -func TestFormatHex(t *testing.T) { - tests := []struct { - name string - input []byte - want string - }{ - {name: "empty", input: nil, want: ""}, - {name: "single byte", input: []byte{0x42}, want: "42"}, - {name: "Hello", input: []byte("Hello"), want: "48 65 6c 6c 6f"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := FormatHex(tt.input) - if got != tt.want { - t.Errorf("FormatHex() = %q, want %q", got, tt.want) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ToDisplayEncoding -// --------------------------------------------------------------------------- - -func TestToDisplayEncoding(t *testing.T) { - tests := []struct { - name string - data []byte - encoding string - contains string - }{ - { - name: "utf-8 passthrough", - data: []byte("Hello, World!"), - encoding: "utf-8", - contains: "Hello, World!", - }, - { - name: "ascii replaces non-printable", - data: []byte{0x80, 0x41}, - encoding: "ascii", - contains: ".A", - }, - { - name: "latin-1 decodes high bytes", - data: []byte{0xe9}, // é in ISO 8859-1 - encoding: "latin-1", - contains: "é", - }, - { - name: "iso-8859-1 alias", - data: []byte{0xe9}, - encoding: "iso-8859-1", - contains: "é", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := ToDisplayEncoding(tt.data, tt.encoding) - if !strings.Contains(got, tt.contains) { - t.Errorf("ToDisplayEncoding() = %q, expected to contain %q", got, tt.contains) - } - }) - } -} diff --git a/internal/rawclient/history_test.go b/internal/rawclient/history_test.go deleted file mode 100644 index 533d685..0000000 --- a/internal/rawclient/history_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package rawclient - -import ( - "bytes" - "testing" - - "github.com/andydixon/link/internal/models" -) - -func TestHistoryIsCappedByEntryCount(t *testing.T) { - client := NewClient(&models.Profile{}) - - for i := 0; i < maxHistoryEntries+25; i++ { - client.appendHistory(HistoryEntry{Direction: "recv", Data: []byte{byte(i)}}) - } - - history := client.GetHistory() - if len(history) != maxHistoryEntries { - t.Fatalf("history length = %d, want %d", len(history), maxHistoryEntries) - } -} - -func TestHistoryIsCappedByBytes(t *testing.T) { - client := NewClient(&models.Profile{}) - chunk := bytes.Repeat([]byte("a"), 1024) - - for i := 0; i < (maxHistoryBytes/len(chunk))+10; i++ { - client.appendHistory(HistoryEntry{Direction: "recv", Data: chunk}) - } - - total := 0 - for _, entry := range client.GetHistory() { - total += len(entry.Data) - } - if total > maxHistoryBytes { - t.Fatalf("history bytes = %d, want <= %d", total, maxHistoryBytes) - } -} - -func TestGetHistoryReturnsDeepCopy(t *testing.T) { - client := NewClient(&models.Profile{}) - client.appendHistory(HistoryEntry{Direction: "recv", Data: []byte("secret")}) - - history := client.GetHistory() - history[0].Data[0] = 'X' - - again := client.GetHistory() - if string(again[0].Data) != "secret" { - t.Fatalf("history was mutated through returned slice: %q", string(again[0].Data)) - } -} diff --git a/internal/rawclient/rawclient.go b/internal/rawclient/rawclient.go deleted file mode 100644 index 0c1427c..0000000 --- a/internal/rawclient/rawclient.go +++ /dev/null @@ -1,332 +0,0 @@ -package rawclient - -import ( - "context" - "encoding/json" - "fmt" - "net" - "os" - "strings" - "sync" - "time" - - "github.com/andydixon/link/internal/models" -) - -const ( - maxHistoryEntries = 10000 - maxHistoryBytes = 10 * 1024 * 1024 -) - -// Client manages a raw TCP connection. -// It provides binary-safe data handling with support for text and hex views, -// timestamps, direction markers, and payload presets. -type Client struct { - mu sync.Mutex - config *models.Profile - conn net.Conn - connected bool - onData func(data []byte, direction string, timestamp time.Time) - onError func(err error) - onClose func() - ctx context.Context - cancel context.CancelFunc - history []HistoryEntry - historyBytes int - historyMu sync.RWMutex -} - -// HistoryEntry records a single sent or received data frame with its timestamp. -type HistoryEntry struct { - Timestamp time.Time `json:"timestamp"` - Direction string `json:"direction"` // "send" or "recv" - Data []byte `json:"data"` -} - -// NewClient creates a new raw TCP client bound to the given profile. -func NewClient(profile *models.Profile) *Client { - ctx, cancel := context.WithCancel(context.Background()) - return &Client{ - config: profile, - ctx: ctx, - cancel: cancel, - } -} - -// SetCallbacks sets the data, error, and close callbacks. Must be called -// before Connect. -func (c *Client) SetCallbacks(onData func([]byte, string, time.Time), onError func(error), onClose func()) { - c.mu.Lock() - defer c.mu.Unlock() - c.onData = onData - c.onError = onError - c.onClose = onClose -} - -// rawConf returns the RAW-specific configuration, or a zero value if nil. -func (c *Client) rawConf() models.RAWConfig { - if c.config.RAW != nil { - return *c.config.RAW - } - return models.RAWConfig{} -} - -// Connect establishes the raw TCP connection and starts the read loop. -func (c *Client) Connect() error { - c.mu.Lock() - if c.connected { - c.mu.Unlock() - return fmt.Errorf("already connected") - } - c.mu.Unlock() - - timeout := time.Duration(c.config.ConnectTimeout) * time.Second - if timeout == 0 { - timeout = 30 * time.Second - } - - port := c.config.Port - if port == 0 { - return fmt.Errorf("port must be specified for raw TCP connections") - } - addr := net.JoinHostPort(c.config.Host, fmt.Sprintf("%d", port)) - - conn, err := net.DialTimeout("tcp", addr, timeout) - if err != nil { - return fmt.Errorf("raw TCP connection to %s failed: %w", addr, err) - } - - c.mu.Lock() - c.conn = conn - c.connected = true - c.mu.Unlock() - - go c.readLoop() - return nil -} - -// Write sends raw bytes to the connection and records the frame in history. -func (c *Client) Write(data []byte) error { - c.mu.Lock() - if !c.connected || c.conn == nil { - c.mu.Unlock() - return fmt.Errorf("not connected") - } - conn := c.conn - c.mu.Unlock() - - _, err := conn.Write(data) - if err != nil { - return err - } - - now := time.Now() - c.appendHistory(HistoryEntry{ - Timestamp: now, - Direction: "send", - Data: copyBytes(data), - }) - - c.mu.Lock() - cb := c.onData - c.mu.Unlock() - if cb != nil { - cb(copyBytes(data), "send", now) - } - - return nil -} - -// WriteText sends text with the configured line ending appended. -func (c *Client) WriteText(text string) error { - data := c.applyLineEnding(text) - return c.Write(data) -} - -// WriteHex sends hex-encoded bytes (e.g. "48656c6c6f" or "48 65 6c 6c 6f"). -func (c *Client) WriteHex(hexStr string) error { - data, err := ParseHexString(hexStr) - if err != nil { - return fmt.Errorf("invalid hex input: %w", err) - } - return c.Write(data) -} - -// WriteEscaped sends text with C-style escape sequences processed -// (\r, \n, \t, \x00, etc.). -func (c *Client) WriteEscaped(text string) error { - data, err := ParseEscaped(text) - if err != nil { - return fmt.Errorf("escape processing failed: %w", err) - } - return c.Write(data) -} - -// Close shuts down the raw TCP connection and invokes the onClose callback. -func (c *Client) Close() error { - c.mu.Lock() - if !c.connected { - c.mu.Unlock() - return nil - } - c.connected = false - conn := c.conn - onClose := c.onClose - c.mu.Unlock() - - c.cancel() - - var err error - if conn != nil { - err = conn.Close() - } - if onClose != nil { - onClose() - } - return err -} - -// IsConnected returns the current connection state. -func (c *Client) IsConnected() bool { - c.mu.Lock() - defer c.mu.Unlock() - return c.connected -} - -// GetHistory returns a copy of the session data history. -func (c *Client) GetHistory() []HistoryEntry { - c.historyMu.RLock() - defer c.historyMu.RUnlock() - - out := make([]HistoryEntry, len(c.history)) - for i, entry := range c.history { - out[i] = entry - out[i].Data = copyBytes(entry.Data) - } - return out -} - -// ClearHistory clears the session history buffer. -func (c *Client) ClearHistory() { - c.historyMu.Lock() - defer c.historyMu.Unlock() - c.history = nil - c.historyBytes = 0 -} - -// SaveCapture writes the session history to a file in JSON format. -func (c *Client) SaveCapture(path string) error { - entries := c.GetHistory() - data, err := json.MarshalIndent(entries, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal capture data: %w", err) - } - return os.WriteFile(path, data, 0600) -} - -// readLoop reads from the connection and dispatches received data to the -// onData callback. -func (c *Client) readLoop() { - buf := make([]byte, 4096) - - for { - select { - case <-c.ctx.Done(): - return - default: - } - - c.mu.Lock() - conn := c.conn - connected := c.connected - c.mu.Unlock() - - if !connected || conn == nil { - return - } - - n, err := conn.Read(buf) - if n > 0 { - now := time.Now() - data := copyBytes(buf[:n]) - - c.appendHistory(HistoryEntry{ - Timestamp: now, - Direction: "recv", - Data: data, - }) - - c.mu.Lock() - cb := c.onData - c.mu.Unlock() - if cb != nil { - cb(data, "recv", now) - } - } - - if err != nil { - c.mu.Lock() - cb := c.onError - c.mu.Unlock() - if cb != nil { - cb(fmt.Errorf("read error: %w", err)) - } - c.Close() - return - } - } -} - -// appendHistory records a history entry in a thread-safe manner. -func (c *Client) appendHistory(entry HistoryEntry) { - c.historyMu.Lock() - defer c.historyMu.Unlock() - - if len(entry.Data) > maxHistoryBytes { - entry.Data = copyBytes(entry.Data[len(entry.Data)-maxHistoryBytes:]) - } - c.history = append(c.history, entry) - c.historyBytes += len(entry.Data) - for len(c.history) > maxHistoryEntries || c.historyBytes > maxHistoryBytes { - if len(c.history) == 0 { - c.historyBytes = 0 - return - } - c.historyBytes -= len(c.history[0].Data) - c.history[0].Data = nil - c.history = c.history[1:] - } -} - -// applyLineEnding appends the configured line ending to text. -func (c *Client) applyLineEnding(text string) []byte { - rc := c.rawConf() - le := rc.LineEnding - if le == "" { - le = models.LineEndingCRLF - } - - var suffix string - switch le { - case models.LineEndingCR: - suffix = "\r" - case models.LineEndingLF: - suffix = "\n" - case models.LineEndingCRLF: - suffix = "\r\n" - case models.LineEndingNone: - // No suffix. - } - - if suffix != "" && !strings.HasSuffix(text, suffix) { - text += suffix - } - return []byte(text) -} - -// copyBytes returns an independent copy of a byte slice. -func copyBytes(data []byte) []byte { - out := make([]byte, len(data)) - copy(out, data) - return out -} diff --git a/internal/recording/recorder.go b/internal/recording/recorder.go deleted file mode 100644 index b54a2cf..0000000 --- a/internal/recording/recorder.go +++ /dev/null @@ -1,538 +0,0 @@ -// Package recording provides session recording in asciicast v2 format. -// Recordings capture terminal I/O with timestamps, enabling playback of -// complete sessions for auditing, training, or troubleshooting. -// -// File format: https://docs.asciinema.org/manual/asciicast/v2/ -// -// The header line is a JSON object with metadata (version, dimensions, etc.) -// followed by one JSON array per event: [elapsed_secs, event_type, data]. -package recording - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "os" - "path/filepath" - "sort" - "strings" - "sync" - "time" -) - -// EventType distinguishes input from output events. -type EventType string - -const ( - // EventOutput represents data sent from the remote host to the terminal. - EventOutput EventType = "o" - // EventInput represents data sent from the user to the remote host. - EventInput EventType = "i" - - // MaxRecordingBytes bounds a single active recording file. - MaxRecordingBytes = int64(250 * 1024 * 1024) - // MaxPlaybackBytes bounds the file size loaded for playback. - MaxPlaybackBytes = int64(100 * 1024 * 1024) - // MaxPlaybackLineBytes bounds one asciicast line during streaming playback - // parsing. Terminal events should stay well below this in normal use. - MaxPlaybackLineBytes = 8 * 1024 * 1024 -) - -// Header is the asciicast v2 file header (first line). -type Header struct { - Version int `json:"version"` - Width int `json:"width"` - Height int `json:"height"` - Timestamp int64 `json:"timestamp"` - Title string `json:"title,omitempty"` - Env map[string]string `json:"env,omitempty"` -} - -// Event is a single recorded I/O event: [elapsed_seconds, type, data]. -type Event struct { - Elapsed float64 `json:"-"` - Type EventType `json:"-"` - Data string `json:"-"` -} - -// MarshalJSON serialises an event as the asciicast v2 array format. -func (e Event) MarshalJSON() ([]byte, error) { - return json.Marshal([]interface{}{e.Elapsed, string(e.Type), e.Data}) -} - -// RecordingMeta describes a saved recording without loading the full data. -type RecordingMeta struct { - ID string `json:"id"` - SessionID string `json:"sessionId"` - Title string `json:"title"` - Width int `json:"width"` - Height int `json:"height"` - Duration float64 `json:"duration"` // seconds - Size int64 `json:"size"` // file size in bytes - CreatedAt time.Time `json:"createdAt"` - FilePath string `json:"filePath"` -} - -// Recorder captures terminal I/O for a single session. -type Recorder struct { - mu sync.Mutex - file *os.File - startTime time.Time - header Header - eventCount int - closed bool - filePath string - bytesWritten int64 - maxBytes int64 -} - -// NewRecorder creates a new recording file and writes the header. -func NewRecorder(dir, id, title string, cols, rows int) (*Recorder, error) { - return newRecorderWithLimit(dir, id, title, cols, rows, MaxRecordingBytes) -} - -func newRecorderWithLimit(dir, id, title string, cols, rows int, maxBytes int64) (*Recorder, error) { - if err := os.MkdirAll(dir, 0700); err != nil { - return nil, fmt.Errorf("recording: failed to create directory: %w", err) - } - - filename := fmt.Sprintf("%s.cast", id) - path := filepath.Join(dir, filename) - - f, err := os.Create(path) - if err != nil { - return nil, fmt.Errorf("recording: failed to create file: %w", err) - } - - now := time.Now() - hdr := Header{ - Version: 2, - Width: cols, - Height: rows, - Timestamp: now.Unix(), - Title: title, - Env: map[string]string{ - "TERM": "xterm-256color", - }, - } - - headerLine, err := marshalJSONLine(hdr) - if err != nil { - f.Close() - os.Remove(path) - return nil, fmt.Errorf("recording: failed to encode header: %w", err) - } - n, err := f.Write(headerLine) - if err != nil { - f.Close() - os.Remove(path) - return nil, fmt.Errorf("recording: failed to write header: %w", err) - } - - return &Recorder{ - file: f, - startTime: now, - header: hdr, - filePath: path, - bytesWritten: int64(n), - maxBytes: maxBytes, - }, nil -} - -// WriteOutput records terminal output (data from remote host). -func (r *Recorder) WriteOutput(data []byte) { - r.writeEvent(EventOutput, data) -} - -// WriteInput records terminal input (data from user). -func (r *Recorder) WriteInput(data []byte) { - r.writeEvent(EventInput, data) -} - -// writeEvent appends a timestamped event to the recording file. -func (r *Recorder) writeEvent(eventType EventType, data []byte) { - r.mu.Lock() - defer r.mu.Unlock() - - if r.closed || r.file == nil { - return - } - - elapsed := time.Since(r.startTime).Seconds() - evt := Event{ - Elapsed: elapsed, - Type: eventType, - Data: string(data), - } - - line, err := marshalJSONLine(evt) - if err != nil { - return - } - if r.maxBytes > 0 && r.bytesWritten+int64(len(line)) > r.maxBytes { - r.closed = true - _ = r.file.Sync() - _ = r.file.Close() - r.file = nil - return - } - - n, err := r.file.Write(line) - if err != nil { - return - } - r.bytesWritten += int64(n) - r.eventCount++ -} - -// UpdateSize records a terminal resize (not part of asciicast v2 events, -// but useful for future playback fidelity). Currently a no-op placeholder. -func (r *Recorder) UpdateSize(cols, rows int) { - // asciicast v2 does not have a standard resize event. Future extension - // could use a custom event type like "r" with JSON dimensions. -} - -// Close flushes and closes the recording file. -func (r *Recorder) Close() error { - r.mu.Lock() - defer r.mu.Unlock() - - if r.closed { - return nil - } - r.closed = true - - if r.file != nil { - if err := r.file.Sync(); err != nil { - r.file.Close() - return fmt.Errorf("recording: sync failed: %w", err) - } - err := r.file.Close() - r.file = nil - return err - } - return nil -} - -// Duration returns the elapsed time since recording started. -func (r *Recorder) Duration() float64 { - r.mu.Lock() - defer r.mu.Unlock() - return time.Since(r.startTime).Seconds() -} - -// FilePath returns the absolute path to the recording file. -func (r *Recorder) FilePath() string { - return r.filePath -} - -// EventCount returns the number of events recorded so far. -func (r *Recorder) EventCount() int { - r.mu.Lock() - defer r.mu.Unlock() - return r.eventCount -} - -// --------------------------------------------------------------------------- -// Recording store — manages listing, reading, and deleting recordings. -// --------------------------------------------------------------------------- - -// Store manages the recording directory. -type Store struct { - dir string -} - -// NewStore creates a recording store at the given directory. -func NewStore(dir string) *Store { - return &Store{dir: dir} -} - -// Dir returns the recordings directory path. -func (s *Store) Dir() string { - return s.dir -} - -// List returns metadata for all recordings in the store. -func (s *Store) List() ([]RecordingMeta, error) { - if err := os.MkdirAll(s.dir, 0700); err != nil { - return nil, err - } - - entries, err := os.ReadDir(s.dir) - if err != nil { - return nil, fmt.Errorf("recording: failed to read directory: %w", err) - } - - var results []RecordingMeta - for _, entry := range entries { - if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".cast") { - continue - } - - meta, err := s.readMeta(entry.Name()) - if err != nil { - continue // skip unreadable files - } - results = append(results, *meta) - } - - // Sort by creation time, newest first. - sort.Slice(results, func(i, j int) bool { - return results[i].CreatedAt.After(results[j].CreatedAt) - }) - - return results, nil -} - -// Get reads the full recording (header + events) from a file. -func (s *Store) Get(id string) (*Header, []Event, error) { - return s.getWithLimit(id, MaxPlaybackBytes) -} - -func (s *Store) getWithLimit(id string, maxBytes int64) (*Header, []Event, error) { - path, err := s.recordingPath(id) - if err != nil { - return nil, nil, err - } - info, err := os.Stat(path) - if err != nil { - return nil, nil, fmt.Errorf("recording: failed to stat file: %w", err) - } - if maxBytes > 0 && info.Size() > maxBytes { - return nil, nil, fmt.Errorf("recording: file is too large for playback (max %d bytes)", maxBytes) - } - file, err := os.Open(path) - if err != nil { - return nil, nil, fmt.Errorf("recording: failed to open file: %w", err) - } - defer file.Close() - - // Parse header (first line). - scanner := bufio.NewScanner(file) - scanner.Buffer(make([]byte, 0, 64*1024), MaxPlaybackLineBytes) - if !scanner.Scan() { - if err := scanner.Err(); err != nil { - return nil, nil, fmt.Errorf("recording: failed to scan header: %w", err) - } - return nil, nil, fmt.Errorf("recording: empty file") - } - var hdr Header - if err := json.Unmarshal([]byte(strings.TrimSpace(scanner.Text())), &hdr); err != nil { - return nil, nil, fmt.Errorf("recording: invalid header: %w", err) - } - - // Parse events (remaining lines). - events := make([]Event, 0) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if line == "" { - continue - } - var raw []json.RawMessage - if err := json.Unmarshal([]byte(line), &raw); err != nil { - continue - } - if len(raw) != 3 { - continue - } - - var elapsed float64 - var evtType, evtData string - if err := json.Unmarshal(raw[0], &elapsed); err != nil { - continue - } - if err := json.Unmarshal(raw[1], &evtType); err != nil { - continue - } - if err := json.Unmarshal(raw[2], &evtData); err != nil { - continue - } - - events = append(events, Event{ - Elapsed: elapsed, - Type: EventType(evtType), - Data: evtData, - }) - } - if err := scanner.Err(); err != nil { - return nil, nil, fmt.Errorf("recording: failed to scan events: %w", err) - } - - return &hdr, events, nil -} - -// Delete removes a recording file. -func (s *Store) Delete(id string) error { - path, err := s.recordingPath(id) - if err != nil { - return err - } - if err := os.Remove(path); err != nil { - return fmt.Errorf("recording: failed to delete: %w", err) - } - return nil -} - -// StorageInfo summarizes recording storage usage. -type StorageInfo struct { - Count int `json:"count"` - TotalBytes int64 `json:"totalBytes"` - OldestCreatedAt *time.Time `json:"oldestCreatedAt,omitempty"` - NewestCreatedAt *time.Time `json:"newestCreatedAt,omitempty"` - MaxRecordingBytes int64 `json:"maxRecordingBytes"` - MaxPlaybackBytes int64 `json:"maxPlaybackBytes"` -} - -// CleanupResult describes files removed by retention cleanup. -type CleanupResult struct { - Deleted int `json:"deleted"` - FreedBytes int64 `json:"freedBytes"` -} - -// StorageInfo returns aggregate metadata for recordings. -func (s *Store) StorageInfo() (StorageInfo, error) { - metas, err := s.List() - if err != nil { - return StorageInfo{}, err - } - info := StorageInfo{ - Count: len(metas), - MaxRecordingBytes: MaxRecordingBytes, - MaxPlaybackBytes: MaxPlaybackBytes, - } - for i := range metas { - meta := metas[i] - info.TotalBytes += meta.Size - if info.OldestCreatedAt == nil || meta.CreatedAt.Before(*info.OldestCreatedAt) { - t := meta.CreatedAt - info.OldestCreatedAt = &t - } - if info.NewestCreatedAt == nil || meta.CreatedAt.After(*info.NewestCreatedAt) { - t := meta.CreatedAt - info.NewestCreatedAt = &t - } - } - return info, nil -} - -// Cleanup removes recordings older than maxAgeDays and/or enough oldest files -// to bring total storage below maxTotalBytes. Non-positive limits are ignored. -func (s *Store) Cleanup(maxAgeDays int, maxTotalBytes int64) (CleanupResult, error) { - metas, err := s.List() - if err != nil { - return CleanupResult{}, err - } - - result := CleanupResult{} - deleted := map[string]bool{} - total := int64(0) - for _, meta := range metas { - total += meta.Size - } - - deleteMeta := func(meta RecordingMeta) { - if deleted[meta.ID] { - return - } - if err := s.Delete(meta.ID); err == nil { - deleted[meta.ID] = true - result.Deleted++ - result.FreedBytes += meta.Size - total -= meta.Size - } - } - - if maxAgeDays > 0 { - cutoff := time.Now().Add(-time.Duration(maxAgeDays) * 24 * time.Hour) - for _, meta := range metas { - if meta.CreatedAt.Before(cutoff) { - deleteMeta(meta) - } - } - } - - if maxTotalBytes > 0 && total > maxTotalBytes { - sort.Slice(metas, func(i, j int) bool { - return metas[i].CreatedAt.Before(metas[j].CreatedAt) - }) - for _, meta := range metas { - if total <= maxTotalBytes { - break - } - deleteMeta(meta) - } - } - - return result, nil -} - -func (s *Store) recordingPath(id string) (string, error) { - id = strings.TrimSpace(id) - if id == "" || id == "." || id == ".." || strings.ContainsAny(id, `/\`) { - return "", fmt.Errorf("recording: invalid recording ID") - } - return filepath.Join(s.dir, id+".cast"), nil -} - -func marshalJSONLine(v interface{}) ([]byte, error) { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetEscapeHTML(false) - if err := enc.Encode(v); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -// readMeta reads just the header from a .cast file to produce metadata. -func (s *Store) readMeta(filename string) (*RecordingMeta, error) { - path := filepath.Join(s.dir, filename) - f, err := os.Open(path) - if err != nil { - return nil, err - } - defer f.Close() - - info, err := f.Stat() - if err != nil { - return nil, err - } - - var hdr Header - dec := json.NewDecoder(f) - if err := dec.Decode(&hdr); err != nil { - return nil, err - } - - // Calculate duration by scanning for last event timestamp. - var duration float64 - for dec.More() { - var raw json.RawMessage - if err := dec.Decode(&raw); err != nil { - break - } - var arr []json.RawMessage - if err := json.Unmarshal(raw, &arr); err != nil || len(arr) < 1 { - continue - } - var elapsed float64 - if err := json.Unmarshal(arr[0], &elapsed); err == nil { - duration = elapsed - } - } - - id := strings.TrimSuffix(filename, ".cast") - - return &RecordingMeta{ - ID: id, - SessionID: id, // recording ID matches session ID by default - Title: hdr.Title, - Width: hdr.Width, - Height: hdr.Height, - Duration: duration, - Size: info.Size(), - CreatedAt: time.Unix(hdr.Timestamp, 0), - FilePath: path, - }, nil -} diff --git a/internal/recording/recorder_test.go b/internal/recording/recorder_test.go deleted file mode 100644 index ee95798..0000000 --- a/internal/recording/recorder_test.go +++ /dev/null @@ -1,283 +0,0 @@ -package recording - -import ( - "encoding/json" - "os" - "path/filepath" - "strings" - "testing" - "time" -) - -func TestRecorderWriteAndClose(t *testing.T) { - dir := t.TempDir() - id := "test-session-001" - - rec, err := NewRecorder(dir, id, "Test Recording", 80, 24) - if err != nil { - t.Fatalf("NewRecorder failed: %v", err) - } - - // Write some output events. - rec.WriteOutput([]byte("Welcome to the server\r\n")) - rec.WriteOutput([]byte("$ ")) - - // Write some input events. - rec.WriteInput([]byte("ls -la\r\n")) - - // Write more output. - rec.WriteOutput([]byte("total 42\r\n")) - - if rec.EventCount() != 4 { - t.Errorf("expected 4 events, got %d", rec.EventCount()) - } - - if err := rec.Close(); err != nil { - t.Fatalf("Close failed: %v", err) - } - - // Verify the file exists and has correct format. - path := filepath.Join(dir, id+".cast") - data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("failed to read recording: %v", err) - } - - lines := strings.Split(strings.TrimSpace(string(data)), "\n") - if len(lines) < 5 { // 1 header + 4 events - t.Fatalf("expected at least 5 lines, got %d", len(lines)) - } - - // Verify header. - var hdr Header - if err := json.Unmarshal([]byte(lines[0]), &hdr); err != nil { - t.Fatalf("failed to parse header: %v", err) - } - if hdr.Version != 2 { - t.Errorf("expected version 2, got %d", hdr.Version) - } - if hdr.Width != 80 || hdr.Height != 24 { - t.Errorf("expected 80x24, got %dx%d", hdr.Width, hdr.Height) - } - - // Verify first event is output. - var evt []json.RawMessage - if err := json.Unmarshal([]byte(lines[1]), &evt); err != nil { - t.Fatalf("failed to parse first event: %v", err) - } - if len(evt) != 3 { - t.Fatalf("expected 3-element array, got %d", len(evt)) - } - var evtType string - json.Unmarshal(evt[1], &evtType) - if evtType != "o" { - t.Errorf("expected event type 'o', got %q", evtType) - } -} - -func TestStoreListAndGet(t *testing.T) { - dir := t.TempDir() - - // Create a recording. - rec, err := NewRecorder(dir, "session-abc", "My Session", 120, 40) - if err != nil { - t.Fatalf("NewRecorder failed: %v", err) - } - rec.WriteOutput([]byte("hello world\r\n")) - time.Sleep(10 * time.Millisecond) - rec.WriteOutput([]byte("done\r\n")) - rec.Close() - - // Use the store to list. - store := NewStore(dir) - metas, err := store.List() - if err != nil { - t.Fatalf("List failed: %v", err) - } - if len(metas) != 1 { - t.Fatalf("expected 1 recording, got %d", len(metas)) - } - if metas[0].ID != "session-abc" { - t.Errorf("expected ID 'session-abc', got %q", metas[0].ID) - } - if metas[0].Width != 120 || metas[0].Height != 40 { - t.Errorf("unexpected dimensions: %dx%d", metas[0].Width, metas[0].Height) - } - - // Read full recording. - hdr, events, err := store.Get("session-abc") - if err != nil { - t.Fatalf("Get failed: %v", err) - } - if hdr.Title != "My Session" { - t.Errorf("expected title 'My Session', got %q", hdr.Title) - } - if len(events) != 2 { - t.Errorf("expected 2 events, got %d", len(events)) - } -} - -func TestStoreDelete(t *testing.T) { - dir := t.TempDir() - - rec, err := NewRecorder(dir, "to-delete", "Delete Me", 80, 24) - if err != nil { - t.Fatalf("NewRecorder failed: %v", err) - } - rec.WriteOutput([]byte("test\n")) - rec.Close() - - store := NewStore(dir) - - // Verify it exists. - metas, _ := store.List() - if len(metas) != 1 { - t.Fatalf("expected 1 recording, got %d", len(metas)) - } - - // Delete. - if err := store.Delete("to-delete"); err != nil { - t.Fatalf("Delete failed: %v", err) - } - - // Verify it's gone. - metas, _ = store.List() - if len(metas) != 0 { - t.Errorf("expected 0 recordings, got %d", len(metas)) - } -} - -func TestDoubleClose(t *testing.T) { - dir := t.TempDir() - rec, err := NewRecorder(dir, "double-close", "Test", 80, 24) - if err != nil { - t.Fatalf("NewRecorder failed: %v", err) - } - rec.WriteOutput([]byte("test")) - - if err := rec.Close(); err != nil { - t.Fatalf("first Close failed: %v", err) - } - // Second close should be a no-op. - if err := rec.Close(); err != nil { - t.Fatalf("second Close failed: %v", err) - } -} - -func TestRecorderStopsAtSizeLimit(t *testing.T) { - dir := t.TempDir() - rec, err := newRecorderWithLimit(dir, "limited", "Limited", 80, 24, 512) - if err != nil { - t.Fatalf("newRecorderWithLimit failed: %v", err) - } - - for i := 0; i < 50; i++ { - rec.WriteOutput([]byte(strings.Repeat("x", 80))) - } - if rec.EventCount() >= 50 { - t.Fatalf("expected recording to stop before all events were written") - } - if err := rec.Close(); err != nil { - t.Fatalf("Close failed: %v", err) - } - info, err := os.Stat(filepath.Join(dir, "limited.cast")) - if err != nil { - t.Fatalf("stat failed: %v", err) - } - if info.Size() > 512 { - t.Fatalf("recording size = %d, want <= 512", info.Size()) - } -} - -func TestStoreGetRejectsOversizedPlayback(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "huge.cast") - header := `{"version":2,"width":80,"height":24,"timestamp":1}` + "\n" - if err := os.WriteFile(path, []byte(header+strings.Repeat("x", 256)), 0600); err != nil { - t.Fatalf("write failed: %v", err) - } - - store := NewStore(dir) - _, _, err := store.getWithLimit("huge", 128) - if err == nil { - t.Fatal("expected oversized playback to be rejected") - } - if !strings.Contains(err.Error(), "too large") { - t.Fatalf("error = %q, want too large", err.Error()) - } -} - -func TestStoreCleanupByAgeAndSize(t *testing.T) { - dir := t.TempDir() - makeRecording := func(id string, created time.Time, payload string) { - rec, err := NewRecorder(dir, id, id, 80, 24) - if err != nil { - t.Fatalf("NewRecorder(%s) failed: %v", id, err) - } - rec.WriteOutput([]byte(payload)) - if err := rec.Close(); err != nil { - t.Fatalf("Close(%s) failed: %v", id, err) - } - path := filepath.Join(dir, id+".cast") - data, err := os.ReadFile(path) - if err != nil { - t.Fatalf("ReadFile(%s) failed: %v", id, err) - } - parts := strings.SplitN(string(data), "\n", 2) - var hdr Header - if err := json.Unmarshal([]byte(parts[0]), &hdr); err != nil { - t.Fatalf("header parse(%s) failed: %v", id, err) - } - hdr.Timestamp = created.Unix() - line, err := marshalJSONLine(hdr) - if err != nil { - t.Fatalf("header encode(%s) failed: %v", id, err) - } - rest := "" - if len(parts) == 2 { - rest = parts[1] - } - if err := os.WriteFile(path, append(line, []byte(rest)...), 0600); err != nil { - t.Fatalf("WriteFile(%s) failed: %v", id, err) - } - if err := os.Chtimes(path, created, created); err != nil { - t.Fatalf("Chtimes(%s) failed: %v", id, err) - } - } - - old := time.Now().Add(-45 * 24 * time.Hour) - recent := time.Now().Add(-2 * time.Hour) - makeRecording("old", old, "old") - makeRecording("recent", recent, strings.Repeat("recent", 50)) - - store := NewStore(dir) - result, err := store.Cleanup(30, 0) - if err != nil { - t.Fatalf("Cleanup by age failed: %v", err) - } - if result.Deleted != 1 { - t.Fatalf("deleted = %d, want 1", result.Deleted) - } - if _, err := os.Stat(filepath.Join(dir, "old.cast")); !os.IsNotExist(err) { - t.Fatalf("old recording still exists, stat err=%v", err) - } - - info, err := store.StorageInfo() - if err != nil { - t.Fatalf("StorageInfo failed: %v", err) - } - result, err = store.Cleanup(0, info.TotalBytes-1) - if err != nil { - t.Fatalf("Cleanup by size failed: %v", err) - } - if result.Deleted != 1 { - t.Fatalf("deleted = %d, want 1", result.Deleted) - } -} - -func TestStoreRejectsTraversalRecordingID(t *testing.T) { - store := NewStore(t.TempDir()) - if err := store.Delete("../outside"); err == nil { - t.Fatal("expected traversal recording ID to be rejected") - } -} diff --git a/internal/secrets/keychain.go b/internal/secrets/keychain.go deleted file mode 100644 index eb94c18..0000000 --- a/internal/secrets/keychain.go +++ /dev/null @@ -1,66 +0,0 @@ -package secrets - -import ( - "fmt" - - "github.com/zalando/go-keyring" -) - -// KeychainStore attempts to use the OS keychain for secret storage. -// On macOS this uses the Keychain, on Linux it uses the Secret Service API, -// and on Windows it uses the Credential Manager. -// -// This is the preferred storage method when available. Falls back to -// the encrypted file-based SecretStore when the keychain is not accessible. -type KeychainStore struct { - serviceName string -} - -// NewKeychainStore creates a KeychainStore that stores credentials under the -// given service name in the operating system's native keychain. -func NewKeychainStore(serviceName string) *KeychainStore { - return &KeychainStore{serviceName: serviceName} -} - -// Available checks whether the OS keychain is accessible by performing a -// harmless write-then-delete probe. Returns true if the keychain responded -// without error. -func (k *KeychainStore) Available() bool { - // Attempt a round-trip to verify the keychain is usable. - probeKey := "__link_keychain_probe__" - probeVal := "probe" - - if err := keyring.Set(k.serviceName, probeKey, probeVal); err != nil { - return false - } - _ = keyring.Delete(k.serviceName, probeKey) - return true -} - -// SetSecret stores a secret in the OS keychain under the configured service -// name with the given key. -func (k *KeychainStore) SetSecret(key, value string) error { - if err := keyring.Set(k.serviceName, key, value); err != nil { - return fmt.Errorf("keychain: failed to store secret %q: %w", key, err) - } - return nil -} - -// GetSecret retrieves a secret from the OS keychain. Returns an error if the -// key does not exist or the keychain is inaccessible. -func (k *KeychainStore) GetSecret(key string) (string, error) { - val, err := keyring.Get(k.serviceName, key) - if err != nil { - return "", fmt.Errorf("keychain: failed to retrieve secret %q: %w", key, err) - } - return val, nil -} - -// DeleteSecret removes a secret from the OS keychain. Returns an error if the -// key does not exist or the keychain is inaccessible. -func (k *KeychainStore) DeleteSecret(key string) error { - if err := keyring.Delete(k.serviceName, key); err != nil { - return fmt.Errorf("keychain: failed to delete secret %q: %w", key, err) - } - return nil -} diff --git a/internal/secrets/redact.go b/internal/secrets/redact.go deleted file mode 100644 index 6de96ee..0000000 --- a/internal/secrets/redact.go +++ /dev/null @@ -1,37 +0,0 @@ -// Package secrets provides encrypted credential storage and redaction utilities. -package secrets - -import "strings" - -// RedactSecret replaces a secret string with a redacted placeholder. -// Used for logging and UI display to prevent accidental secret exposure. -func RedactSecret(s string) string { - if len(s) == 0 { - return "" - } - return "••••••••" -} - -// RedactInString replaces all occurrences of a secret within a larger string -// with a redacted placeholder. This is useful for sanitising log output that -// may inadvertently contain sensitive material. -func RedactInString(text, secret string) string { - if secret == "" { - return text - } - return strings.ReplaceAll(text, secret, "••••••••") -} - -// RedactMap returns a shallow copy of the map with every value replaced by -// a redacted placeholder. The original map is not modified. -func RedactMap(m map[string]string) map[string]string { - redacted := make(map[string]string, len(m)) - for k, v := range m { - if v == "" { - redacted[k] = "" - } else { - redacted[k] = "••••••••" - } - } - return redacted -} diff --git a/internal/secrets/secrets.go b/internal/secrets/secrets.go deleted file mode 100644 index 73ae149..0000000 --- a/internal/secrets/secrets.go +++ /dev/null @@ -1,357 +0,0 @@ -package secrets - -import ( - "crypto/aes" - "crypto/cipher" - "crypto/rand" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "os" - "path/filepath" - "strings" - "sync" - - "golang.org/x/crypto/argon2" -) - -const ( - vaultFileName = "vault.json" - vaultVersion = 1 - - // Argon2id key derivation parameters. - argonTime = 3 - argonMemory = 128 * 1024 // 128 MiB — deliberately strong for a desktop app - argonThreads = 4 - argonKeyLen = 32 - - saltLen = 32 - - maxVaultFileBytes = 10 * 1024 * 1024 -) - -// SecretStore manages encrypted credential storage. -// It uses AES-256-GCM with Argon2id key derivation from a user-controlled -// master password. No hard-coded secrets are used. -// -// Threat model: -// - Protects secrets at rest on the local filesystem -// - Requires the master password to decrypt -// - Does NOT protect against a compromised running process -// - Does NOT protect against memory forensics on a compromised machine -// - The master password is held in memory only for the duration of the session -type SecretStore struct { - mu sync.RWMutex - storePath string - key []byte // derived from master password, held in memory - vault *secretVault - unlocked bool -} - -type secretVault struct { - Version int `json:"version"` - Salt string `json:"salt"` // base64-encoded - Secrets map[string]string `json:"secrets"` // key -> base64(nonce + ciphertext) -} - -// NewSecretStore creates a new SecretStore that persists its vault file in the -// given directory path. The vault is not unlocked until Unlock is called. -func NewSecretStore(storePath string) *SecretStore { - return &SecretStore{ - storePath: storePath, - } -} - -// IsInitialised returns true if a vault file already exists on disc. -func (s *SecretStore) IsInitialised() bool { - _, err := os.Stat(s.vaultPath()) - return err == nil -} - -// Initialise creates a new vault with the given master password. A random -// 32-byte salt is generated and the key is derived using Argon2id. Returns an -// error if a vault already exists. -func (s *SecretStore) Initialise(masterPassword string) error { - s.mu.Lock() - defer s.mu.Unlock() - - if s.IsInitialised() { - return errors.New("secrets: vault already initialised") - } - - salt := make([]byte, saltLen) - if _, err := io.ReadFull(rand.Reader, salt); err != nil { - return fmt.Errorf("secrets: failed to generate salt: %w", err) - } - - s.vault = &secretVault{ - Version: vaultVersion, - Salt: base64.StdEncoding.EncodeToString(salt), - Secrets: make(map[string]string), - } - s.key = deriveKey(masterPassword, salt) - s.unlocked = true - - if err := os.MkdirAll(s.storePath, 0700); err != nil { - return fmt.Errorf("secrets: failed to create store directory: %w", err) - } - - if err := s.save(); err != nil { - return fmt.Errorf("secrets: failed to save initial vault: %w", err) - } - - return nil -} - -// Unlock decrypts the vault using the provided master password. The derived -// key is verified by attempting to decrypt the first stored secret; if no -// secrets exist the password is accepted optimistically. -func (s *SecretStore) Unlock(masterPassword string) error { - s.mu.Lock() - defer s.mu.Unlock() - - vault, err := s.load() - if err != nil { - return fmt.Errorf("secrets: failed to load vault: %w", err) - } - - salt, err := base64.StdEncoding.DecodeString(vault.Salt) - if err != nil { - return fmt.Errorf("secrets: corrupt vault salt: %w", err) - } - - s.vault = vault - s.key = deriveKey(masterPassword, salt) - s.unlocked = true - - // Attempt to decrypt any existing secret to verify the password. - for _, encVal := range vault.Secrets { - if _, err := s.decrypt(encVal); err != nil { - s.lockInternal() - return fmt.Errorf("secrets: incorrect master password or corrupt vault") - } - break - } - - return nil -} - -// Lock zeroes the derived key material and locks the vault. After calling Lock -// no secret operations are possible until Unlock is called again. -func (s *SecretStore) Lock() { - s.mu.Lock() - defer s.mu.Unlock() - s.lockInternal() -} - -// lockInternal performs the actual lock without acquiring the mutex. The caller -// must already hold s.mu. -func (s *SecretStore) lockInternal() { - for i := range s.key { - s.key[i] = 0 - } - s.key = nil - s.vault = nil - s.unlocked = false -} - -// IsUnlocked returns whether the vault is currently unlocked and ready for use. -func (s *SecretStore) IsUnlocked() bool { - s.mu.RLock() - defer s.mu.RUnlock() - return s.unlocked -} - -// SetSecret encrypts and stores a secret under the given key. The key format -// should follow "profile:{id}:password" or "profile:{id}:passphrase". -func (s *SecretStore) SetSecret(key string, value string) error { - s.mu.Lock() - defer s.mu.Unlock() - - if !s.unlocked { - return errors.New("secrets: vault is locked") - } - - encrypted, err := s.encrypt(value) - if err != nil { - return fmt.Errorf("secrets: encryption failed: %w", err) - } - - s.vault.Secrets[key] = encrypted - return s.save() -} - -// GetSecret retrieves and decrypts a secret by key. Returns an error if the -// key does not exist or decryption fails. -func (s *SecretStore) GetSecret(key string) (string, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - if !s.unlocked { - return "", errors.New("secrets: vault is locked") - } - - encVal, ok := s.vault.Secrets[key] - if !ok { - return "", fmt.Errorf("secrets: key %q not found", key) - } - - return s.decrypt(encVal) -} - -// DeleteSecret removes a secret by key. Returns an error if the vault is -// locked or the save fails. -func (s *SecretStore) DeleteSecret(key string) error { - s.mu.Lock() - defer s.mu.Unlock() - - if !s.unlocked { - return errors.New("secrets: vault is locked") - } - - delete(s.vault.Secrets, key) - return s.save() -} - -// DeleteProfileSecrets removes all secrets whose key begins with -// "profile:{profileID}:". -func (s *SecretStore) DeleteProfileSecrets(profileID string) error { - s.mu.Lock() - defer s.mu.Unlock() - - if !s.unlocked { - return errors.New("secrets: vault is locked") - } - - prefix := "profile:" + profileID + ":" - for k := range s.vault.Secrets { - if strings.HasPrefix(k, prefix) { - delete(s.vault.Secrets, k) - } - } - - return s.save() -} - -// ListKeys returns all secret keys stored in the vault. Secret values are -// never returned by this method. -func (s *SecretStore) ListKeys() []string { - s.mu.RLock() - defer s.mu.RUnlock() - - if !s.unlocked || s.vault == nil { - return nil - } - - keys := make([]string, 0, len(s.vault.Secrets)) - for k := range s.vault.Secrets { - keys = append(keys, k) - } - return keys -} - -// --- internal helpers -------------------------------------------------------- - -// vaultPath returns the full filesystem path to the vault JSON file. -func (s *SecretStore) vaultPath() string { - return filepath.Join(s.storePath, vaultFileName) -} - -// deriveKey uses Argon2id to derive a 256-bit key from the master password and -// salt. The parameters are intentionally hard-coded to ensure consistency -// across vault open/close cycles. -func deriveKey(password string, salt []byte) []byte { - return argon2.IDKey([]byte(password), salt, argonTime, argonMemory, argonThreads, argonKeyLen) -} - -// encrypt encrypts plaintext using AES-256-GCM. A random nonce is generated -// for each call and prepended to the ciphertext. The result is returned as a -// base64-encoded string. -func (s *SecretStore) encrypt(plaintext string) (string, error) { - block, err := aes.NewCipher(s.key) - if err != nil { - return "", fmt.Errorf("failed to create cipher: %w", err) - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return "", fmt.Errorf("failed to create GCM: %w", err) - } - - nonce := make([]byte, gcm.NonceSize()) - if _, err := io.ReadFull(rand.Reader, nonce); err != nil { - return "", fmt.Errorf("failed to generate nonce: %w", err) - } - - ciphertext := gcm.Seal(nonce, nonce, []byte(plaintext), nil) - return base64.StdEncoding.EncodeToString(ciphertext), nil -} - -// decrypt decodes a base64 string and decrypts it using AES-256-GCM. The -// nonce is expected to be prepended to the ciphertext. -func (s *SecretStore) decrypt(encoded string) (string, error) { - data, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return "", fmt.Errorf("failed to decode secret: %w", err) - } - - block, err := aes.NewCipher(s.key) - if err != nil { - return "", fmt.Errorf("failed to create cipher: %w", err) - } - - gcm, err := cipher.NewGCM(block) - if err != nil { - return "", fmt.Errorf("failed to create GCM: %w", err) - } - - nonceSize := gcm.NonceSize() - if len(data) < nonceSize { - return "", errors.New("ciphertext too short") - } - - nonce, ciphertext := data[:nonceSize], data[nonceSize:] - plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) - if err != nil { - return "", fmt.Errorf("decryption failed: %w", err) - } - - return string(plaintext), nil -} - -// save writes the vault to disc as a JSON file with restricted permissions. -func (s *SecretStore) save() error { - data, err := json.MarshalIndent(s.vault, "", " ") - if err != nil { - return fmt.Errorf("failed to marshal vault: %w", err) - } - - return os.WriteFile(s.vaultPath(), data, 0600) -} - -// load reads and unmarshals the vault from disc. -func (s *SecretStore) load() (*secretVault, error) { - info, err := os.Stat(s.vaultPath()) - if err != nil { - return nil, fmt.Errorf("failed to stat vault file: %w", err) - } - if info.IsDir() { - return nil, errors.New("vault path is a directory") - } - if info.Size() > maxVaultFileBytes { - return nil, fmt.Errorf("vault file is too large: %d bytes exceeds %d bytes", info.Size(), maxVaultFileBytes) - } - data, err := os.ReadFile(s.vaultPath()) - if err != nil { - return nil, fmt.Errorf("failed to read vault file: %w", err) - } - - var vault secretVault - if err := json.Unmarshal(data, &vault); err != nil { - return nil, fmt.Errorf("failed to parse vault file: %w", err) - } - - return &vault, nil -} diff --git a/internal/secrets/secrets_test.go b/internal/secrets/secrets_test.go deleted file mode 100644 index 8ea956f..0000000 --- a/internal/secrets/secrets_test.go +++ /dev/null @@ -1,537 +0,0 @@ -package secrets - -import ( - "os" - "path/filepath" - "strings" - "testing" -) - -// --------------------------------------------------------------------------- -// Full lifecycle: init → set → get → lock → fail → unlock → get -// --------------------------------------------------------------------------- - -func TestSecretStoreLifecycle(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - - if store.IsInitialised() { - t.Fatal("expected vault to be uninitialised on a fresh directory") - } - - // Initialise with a master password. - if err := store.Initialise("correct-horse-battery-staple"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - if !store.IsInitialised() { - t.Fatal("expected vault to be initialised after Initialise()") - } - if !store.IsUnlocked() { - t.Fatal("expected vault to be unlocked after Initialise()") - } - - // Store a secret and retrieve it. - if err := store.SetSecret("profile:abc:password", "s3cret"); err != nil { - t.Fatalf("SetSecret() error = %v", err) - } - val, err := store.GetSecret("profile:abc:password") - if err != nil { - t.Fatalf("GetSecret() error = %v", err) - } - if val != "s3cret" { - t.Errorf("GetSecret() = %q, want %q", val, "s3cret") - } - - // Lock the vault — should prevent reads. - store.Lock() - if store.IsUnlocked() { - t.Fatal("expected vault to be locked after Lock()") - } - if _, err := store.GetSecret("profile:abc:password"); err == nil { - t.Fatal("expected error when getting secret from locked vault") - } - - // Unlock and read again. - if err := store.Unlock("correct-horse-battery-staple"); err != nil { - t.Fatalf("Unlock() error = %v", err) - } - val, err = store.GetSecret("profile:abc:password") - if err != nil { - t.Fatalf("GetSecret() after unlock error = %v", err) - } - if val != "s3cret" { - t.Errorf("GetSecret() after unlock = %q, want %q", val, "s3cret") - } -} - -// --------------------------------------------------------------------------- -// Wrong password should fail to unlock -// --------------------------------------------------------------------------- - -func TestSecretStoreWrongPassword(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - - if err := store.Initialise("passwordA"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - // Store a secret so the password verification has something to decrypt. - if err := store.SetSecret("test:key", "value"); err != nil { - t.Fatalf("SetSecret() error = %v", err) - } - store.Lock() - - if err := store.Unlock("passwordB"); err == nil { - t.Fatal("expected error when unlocking with wrong password") - } -} - -// --------------------------------------------------------------------------- -// Multiple secrets — store, retrieve, delete selectively -// --------------------------------------------------------------------------- - -func TestSecretStoreMultipleSecrets(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - - if err := store.Initialise("master"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - - secrets := map[string]string{ - "profile:a:password": "alpha", - "profile:b:password": "bravo", - "profile:c:passphrase": "charlie", - } - for k, v := range secrets { - if err := store.SetSecret(k, v); err != nil { - t.Fatalf("SetSecret(%q) error = %v", k, err) - } - } - - // Verify each secret. - for k, want := range secrets { - got, err := store.GetSecret(k) - if err != nil { - t.Fatalf("GetSecret(%q) error = %v", k, err) - } - if got != want { - t.Errorf("GetSecret(%q) = %q, want %q", k, got, want) - } - } - - // Delete one secret and verify the others remain. - if err := store.DeleteSecret("profile:b:password"); err != nil { - t.Fatalf("DeleteSecret() error = %v", err) - } - if _, err := store.GetSecret("profile:b:password"); err == nil { - t.Error("expected error when getting deleted secret") - } - for _, key := range []string{"profile:a:password", "profile:c:passphrase"} { - if _, err := store.GetSecret(key); err != nil { - t.Errorf("GetSecret(%q) should still work after unrelated delete, got %v", key, err) - } - } -} - -// --------------------------------------------------------------------------- -// DeleteProfileSecrets -// --------------------------------------------------------------------------- - -func TestDeleteProfileSecrets(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - - if err := store.Initialise("master"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - - // Store secrets for two profiles. - if err := store.SetSecret("profile:p1:password", "pw1"); err != nil { - t.Fatalf("SetSecret() error = %v", err) - } - if err := store.SetSecret("profile:p1:passphrase", "pp1"); err != nil { - t.Fatalf("SetSecret() error = %v", err) - } - if err := store.SetSecret("profile:p2:password", "pw2"); err != nil { - t.Fatalf("SetSecret() error = %v", err) - } - - // Delete profile p1's secrets. - if err := store.DeleteProfileSecrets("p1"); err != nil { - t.Fatalf("DeleteProfileSecrets() error = %v", err) - } - - // p1 secrets should be gone. - if _, err := store.GetSecret("profile:p1:password"); err == nil { - t.Error("expected p1 password to be deleted") - } - if _, err := store.GetSecret("profile:p1:passphrase"); err == nil { - t.Error("expected p1 passphrase to be deleted") - } - - // p2 secret should remain. - val, err := store.GetSecret("profile:p2:password") - if err != nil { - t.Fatalf("GetSecret(p2) error = %v", err) - } - if val != "pw2" { - t.Errorf("GetSecret(p2) = %q, want %q", val, "pw2") - } -} - -// --------------------------------------------------------------------------- -// Reinitialise an existing vault should fail -// --------------------------------------------------------------------------- - -func TestSecretStoreDoubleInitialise(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - - if err := store.Initialise("pw"); err != nil { - t.Fatalf("first Initialise() error = %v", err) - } - if err := store.Initialise("pw"); err == nil { - t.Fatal("expected error on second Initialise()") - } -} - -// --------------------------------------------------------------------------- -// Persistence across store instances -// --------------------------------------------------------------------------- - -func TestSecretStorePersistence(t *testing.T) { - dir := t.TempDir() - - // Instance 1: create and store. - s1 := NewSecretStore(dir) - if err := s1.Initialise("master"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - if err := s1.SetSecret("k", "v"); err != nil { - t.Fatalf("SetSecret() error = %v", err) - } - s1.Lock() - - // Instance 2: open the same directory and unlock. - s2 := NewSecretStore(dir) - if !s2.IsInitialised() { - t.Fatal("expected vault to be initialised from disc") - } - if err := s2.Unlock("master"); err != nil { - t.Fatalf("Unlock() on second instance error = %v", err) - } - val, err := s2.GetSecret("k") - if err != nil { - t.Fatalf("GetSecret() on second instance error = %v", err) - } - if val != "v" { - t.Errorf("got %q, want %q", val, "v") - } -} - -// --------------------------------------------------------------------------- -// ListKeys -// --------------------------------------------------------------------------- - -func TestListKeys(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - - if err := store.Initialise("master"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - _ = store.SetSecret("a", "1") - _ = store.SetSecret("b", "2") - - keys := store.ListKeys() - if len(keys) != 2 { - t.Fatalf("ListKeys() returned %d keys, want 2", len(keys)) - } -} - -// --------------------------------------------------------------------------- -// Vault path uses the store directory -// --------------------------------------------------------------------------- - -func TestVaultPath(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - want := filepath.Join(dir, "vault.json") - - // Access vaultPath via reflection-free approach: just initialise and check the file. - if err := store.Initialise("pw"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - // The file should exist at the expected path. - if !store.IsInitialised() { - t.Fatalf("expected vault file at %s", want) - } -} - -func TestSecretStoreLoadRejectsOversizedVault(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, vaultFileName) - file, err := os.Create(path) - if err != nil { - t.Fatalf("Create() failed: %v", err) - } - if err := file.Truncate(maxVaultFileBytes + 1); err != nil { - t.Fatalf("Truncate() failed: %v", err) - } - if err := file.Close(); err != nil { - t.Fatalf("Close() failed: %v", err) - } - - store := NewSecretStore(dir) - _, err = store.load() - if err == nil { - t.Fatal("expected oversized vault to be rejected") - } - if !strings.Contains(err.Error(), "vault file is too large") { - t.Fatalf("unexpected error: %v", err) - } -} - -// --------------------------------------------------------------------------- -// Redaction helpers -// --------------------------------------------------------------------------- - -func TestRedactSecret(t *testing.T) { - tests := []struct { - name string - input string - want string - }{ - {name: "empty string returns empty", input: "", want: ""}, - {name: "non-empty returns bullets", input: "super-secret", want: "••••••••"}, - {name: "single char returns bullets", input: "x", want: "••••••••"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := RedactSecret(tt.input) - if got != tt.want { - t.Errorf("RedactSecret(%q) = %q, want %q", tt.input, got, tt.want) - } - }) - } -} - -func TestRedactInString(t *testing.T) { - tests := []struct { - name string - text string - secret string - want string - }{ - { - name: "replaces secret in text", - text: "connecting with password=hunter2 now", - secret: "hunter2", - want: "connecting with password=•••••••• now", - }, - { - name: "empty secret returns text unchanged", - text: "no redaction here", - secret: "", - want: "no redaction here", - }, - { - name: "secret not present returns text unchanged", - text: "nothing to see", - secret: "absent", - want: "nothing to see", - }, - { - name: "multiple occurrences all redacted", - text: "pw=abc and again abc", - secret: "abc", - want: "pw=•••••••• and again ••••••••", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := RedactInString(tt.text, tt.secret) - if got != tt.want { - t.Errorf("RedactInString() = %q, want %q", got, tt.want) - } - }) - } -} - -func TestRedactMap(t *testing.T) { - input := map[string]string{ - "password": "hunter2", - "token": "abc123", - "empty": "", - } - - result := RedactMap(input) - - if result["password"] != "••••••••" { - t.Errorf("expected password to be redacted, got %q", result["password"]) - } - if result["token"] != "••••••••" { - t.Errorf("expected token to be redacted, got %q", result["token"]) - } - if result["empty"] != "" { - t.Errorf("expected empty value to remain empty, got %q", result["empty"]) - } - - // Verify the original map is not modified. - if input["password"] != "hunter2" { - t.Error("original map was modified") - } -} - -// --------------------------------------------------------------------------- -// Initialise standalone — verifies vault file creation and unlocked state -// --------------------------------------------------------------------------- - -func TestSecretStoreInitialise(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - - if store.IsInitialised() { - t.Fatal("expected uninitialised state before Initialise()") - } - - if err := store.Initialise("test-master-pw"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - - // Vault file must exist on disc. - if !store.IsInitialised() { - t.Fatal("expected initialised state after Initialise()") - } - - // Store should be unlocked immediately after initialisation. - if !store.IsUnlocked() { - t.Fatal("expected unlocked state after Initialise()") - } - - // Should be able to set and retrieve a secret straight away. - if err := store.SetSecret("init-test", "value"); err != nil { - t.Fatalf("SetSecret() after Initialise error = %v", err) - } - got, err := store.GetSecret("init-test") - if err != nil { - t.Fatalf("GetSecret() after Initialise error = %v", err) - } - if got != "value" { - t.Errorf("GetSecret() = %q, want %q", got, "value") - } -} - -// --------------------------------------------------------------------------- -// Encrypt / Decrypt round-trip (same-package access to unexported methods) -// --------------------------------------------------------------------------- - -func TestEncryptDecrypt(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - if err := store.Initialise("roundtrip-pw"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - - tests := []struct { - name string - plaintext string - }{ - {name: "short string", plaintext: "hello"}, - {name: "empty string", plaintext: ""}, - {name: "long string", plaintext: "The quick brown fox jumps over the lazy dog. 0123456789!@#$%^&*()"}, - {name: "unicode text", plaintext: "café résumé naïve über 日本語"}, - {name: "binary-ish content", plaintext: "\x00\x01\x02\xff"}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - encrypted, err := store.encrypt(tt.plaintext) - if err != nil { - t.Fatalf("encrypt() error = %v", err) - } - - // Encrypted text must differ from plaintext (unless empty — GCM - // still produces a tag). - if encrypted == tt.plaintext && tt.plaintext != "" { - t.Error("encrypted output should not equal plaintext") - } - - decrypted, err := store.decrypt(encrypted) - if err != nil { - t.Fatalf("decrypt() error = %v", err) - } - if decrypted != tt.plaintext { - t.Errorf("decrypt() = %q, want %q", decrypted, tt.plaintext) - } - }) - } -} - -func TestEncryptDecryptWrongKey(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - if err := store.Initialise("key-A"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - - encrypted, err := store.encrypt("secret data") - if err != nil { - t.Fatalf("encrypt() error = %v", err) - } - - // Create a second store with a different key. - dir2 := t.TempDir() - store2 := NewSecretStore(dir2) - if err := store2.Initialise("key-B"); err != nil { - t.Fatalf("Initialise(key-B) error = %v", err) - } - - // Attempting to decrypt with the wrong key should fail. - _, err = store2.decrypt(encrypted) - if err == nil { - t.Error("expected error when decrypting with wrong key") - } -} - -func TestEncryptProducesDifferentCiphertexts(t *testing.T) { - // Each call to encrypt should use a fresh random nonce, so encrypting - // the same plaintext twice must produce different ciphertexts. - dir := t.TempDir() - store := NewSecretStore(dir) - if err := store.Initialise("nonce-test"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - - a, err := store.encrypt("same input") - if err != nil { - t.Fatalf("first encrypt() error = %v", err) - } - b, err := store.encrypt("same input") - if err != nil { - t.Fatalf("second encrypt() error = %v", err) - } - if a == b { - t.Error("two encryptions of identical plaintext produced identical ciphertext — nonce reuse?") - } -} - -// --------------------------------------------------------------------------- -// SetSecret and GetSecret when vault is locked -// --------------------------------------------------------------------------- - -func TestSetSecretWhenLocked(t *testing.T) { - dir := t.TempDir() - store := NewSecretStore(dir) - if err := store.Initialise("lock-test"); err != nil { - t.Fatalf("Initialise() error = %v", err) - } - store.Lock() - - if err := store.SetSecret("k", "v"); err == nil { - t.Error("expected error when calling SetSecret on a locked vault") - } -} diff --git a/internal/sessions/manager.go b/internal/sessions/manager.go deleted file mode 100644 index 3fe9561..0000000 --- a/internal/sessions/manager.go +++ /dev/null @@ -1,2753 +0,0 @@ -// Package sessions orchestrates multiple simultaneous SSH, Telnet, RAW TCP, -// and GitHub sessions. It coordinates between protocol clients, the storage -// layer, the secret store, and the Wails runtime for event emission. -package sessions - -import ( - "context" - "encoding/base64" - "fmt" - "io" - "net" - "os" - "strings" - "sync" - "time" - - "github.com/google/uuid" - wailsRuntime "github.com/wailsapp/wails/v2/pkg/runtime" - "golang.org/x/crypto/ssh" - - "github.com/andydixon/link/internal/audit" - "github.com/andydixon/link/internal/githubclient" - "github.com/andydixon/link/internal/ircclient" - "github.com/andydixon/link/internal/localshell" - "github.com/andydixon/link/internal/models" - "github.com/andydixon/link/internal/rawclient" - "github.com/andydixon/link/internal/recording" - "github.com/andydixon/link/internal/secrets" - "github.com/andydixon/link/internal/sftpclient" - "github.com/andydixon/link/internal/sshclient" - "github.com/andydixon/link/internal/storage" - "github.com/andydixon/link/internal/telnetclient" - "github.com/andydixon/link/internal/vncclient" -) - -const maxTunnelPrivateKeyBytes = 1 << 20 - -// Session wraps a protocol client with metadata and state. -type Session struct { - Info models.SessionInfo - closed bool // explicitly removed from the manager - sshClient *sshclient.Client - telnetClient *telnetclient.Client - rawClient *rawclient.Client - githubClient *githubclient.Client - githubCancel context.CancelFunc // stops the auto-refresh goroutine - vncClient *vncclient.Client // VNC proxy client, nil for non-VNC sessions - ircClient *ircclient.Client // IRC client, nil for non-IRC sessions - localClient *localshell.Client // local shell PTY, nil for non-local sessions - localShell string // configured shell path, retained for restart - localCwd string // last known local working directory - sftpClient *sftpclient.Client // SFTP subsystem, nil until requested - profile *models.Profile - recorder *recording.Recorder // active recording, nil if not recording - reconnectCount int // number of reconnection attempts so far - cancelReconn context.CancelFunc // cancels an in-progress reconnect backoff - - // Latest terminal geometry received before a PTY-capable client is attached. - // The frontend can know the correct size before SSH/Telnet/local setup finishes. - pendingResizeCols int - pendingResizeRows int - - // Login script support — temporary buffer for expect-style matching. - loginBuf []byte - loginBufMu sync.Mutex - loginDone chan struct{} // closed when login script completes or is abandoned -} - -// Manager handles the lifecycle of all active sessions. -// It coordinates between protocol clients, the storage layer, -// the secret store, and the Wails runtime for event emission. -type Manager struct { - mu sync.RWMutex - sessions map[string]*Session - store *storage.Store - secretStore *secrets.SecretStore - keychainStore *secrets.KeychainStore - auditLog *audit.Logger - recordStore *recording.Store - ctx context.Context // Wails app context for event emission - eventSink func(string, interface{}) - hostKeyMu sync.Mutex - hostKeyChans map[string]chan bool - hostKeyListenerRegistered bool - hostKeyListenerCancel func() - secretMu sync.Mutex - secretChans map[string]chan secretPromptResponse - secretListenerRegistered bool - secretListenerCancel func() -} - -type secretPromptResponse struct { - value string - cancelled bool -} - -// NewManager creates a Manager wired to the given backing services. -func NewManager(store *storage.Store, secretStore *secrets.SecretStore, keychainStore *secrets.KeychainStore, auditLog *audit.Logger, recordStore *recording.Store) *Manager { - return &Manager{ - sessions: make(map[string]*Session), - store: store, - secretStore: secretStore, - keychainStore: keychainStore, - auditLog: auditLog, - recordStore: recordStore, - } -} - -// SetContext stores the Wails runtime context for event emission. -func (m *Manager) SetContext(ctx context.Context) { - m.ctx = ctx - m.registerHostKeyResponseListener() - m.registerSecretResponseListener() -} - -// SetEventSink registers an alternate UI event sink for non-Wails hosts. -// Wails remains supported through SetContext; this hook is used by the VS Code -// bridge so existing protocol/session code can be reused without a webview -// runtime context. -func (m *Manager) SetEventSink(sink func(string, interface{})) { - m.mu.Lock() - m.eventSink = sink - m.mu.Unlock() -} - -// Connect creates a new session from a profile and connects it. -// The getSecret callback is used to prompt the user for credentials when -// required. Returns the session ID on success. -func (m *Manager) Connect(profile *models.Profile, getSecret func(prompt string) (string, error)) (string, error) { - sessionID := uuid.New().String() - now := time.Now() - - info := models.SessionInfo{ - ID: sessionID, - ProfileID: profile.ID, - ProfileName: profile.DisplayName, - Protocol: profile.Protocol, - Host: profile.Host, - Port: profile.Port, - State: models.SessionConnecting, - TabName: m.createTabName(profile), - ConnectedAt: now, - } - - sess := &Session{ - Info: info, - profile: profile, - } - - m.mu.Lock() - m.sessions[sessionID] = sess - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionConnecting), - }) - - // Perform the actual protocol handshake in a goroutine so we do not - // block the Wails event loop — secret prompts require the UI thread. - go func() { - var err error - switch profile.Protocol { - case models.ProtocolSSH: - err = m.connectSSH(sess, getSecret) - case models.ProtocolTelnet: - err = m.connectTelnet(sess) - case models.ProtocolRAW: - err = m.connectRAW(sess) - case models.ProtocolGitHub: - err = m.connectGitHub(sess, getSecret) - case models.ProtocolVNC: - err = m.connectVNC(sess, getSecret) - case models.ProtocolIRC: - err = m.connectIRC(sess, getSecret) - default: - err = fmt.Errorf("unsupported protocol %q", profile.Protocol) - } - - if err != nil { - m.mu.Lock() - if current, ok := m.sessions[sessionID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - m.closeSession(sess) - return - } - sess.Info.State = models.SessionError - sess.Info.Error = err.Error() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionError), - "error": err.Error(), - }) - return - } - - m.mu.Lock() - if current, ok := m.sessions[sessionID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - m.closeSession(sess) - return - } - sess.Info.State = models.SessionConnected - sess.Info.ConnectedAt = time.Now() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionConnected), - }) - - if m.auditLog != nil { - m.auditLog.LogConnect(profile.ID, sessionID, string(profile.Protocol), profile.Host) - } - }() - - return sessionID, nil -} - -// ConnectLocal creates a local shell session without a saved profile. -// The shell path is resolved from the given parameter or falls back to the system default. -func (m *Manager) ConnectLocal(shell string) (string, error) { - sessionID := uuid.New().String() - now := time.Now() - - info := models.SessionInfo{ - ID: sessionID, - ProfileID: "", - ProfileName: "Local Machine", - Protocol: models.ProtocolLocal, - Host: "localhost", - Port: 0, - State: models.SessionConnecting, - TabName: "Local", - ConnectedAt: now, - } - - sess := &Session{ - Info: info, - localShell: shell, - } - - m.mu.Lock() - m.sessions[sessionID] = sess - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionConnecting), - }) - - go m.startLocalSession(sess, 80, 24) - - return sessionID, nil -} - -// RestartLocal starts a replacement shell inside an exited local session. -// Keeping the session ID lets the frontend retain the tab and terminal buffer. -func (m *Manager) RestartLocal(sessionID string, cols, rows int) error { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if !ok { - m.mu.Unlock() - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.Info.Protocol != models.ProtocolLocal { - m.mu.Unlock() - return fmt.Errorf("sessions: session %q is not local", sessionID) - } - if sess.localClient != nil || sess.Info.State == models.SessionConnecting || sess.Info.State == models.SessionConnected { - m.mu.Unlock() - return fmt.Errorf("sessions: local session %q is still running", sessionID) - } - if cols <= 0 { - cols = 80 - } - if rows <= 0 { - rows = 24 - } - sess.Info.State = models.SessionConnecting - sess.Info.Error = "" - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionConnecting), - }) - go m.startLocalSession(sess, cols, rows) - return nil -} - -func (m *Manager) startLocalSession(sess *Session, cols, rows int) { - m.mu.RLock() - sessionID := sess.Info.ID - shell := sess.localShell - startDir := sess.localCwd - m.mu.RUnlock() - var client *localshell.Client - client = localshell.NewClient(localshell.Config{ - Shell: shell, - StartDir: startDir, - Cols: terminalDimension(cols, 80), - Rows: terminalDimension(rows, 24), - OnData: func(data []byte) { - m.emitEvent("session:data", map[string]interface{}{ - "sessionID": sessionID, - "data": base64.StdEncoding.EncodeToString(data), - }) - m.mu.RLock() - rec := sess.recorder - m.mu.RUnlock() - if rec != nil { - rec.WriteOutput(data) - } - }, - OnError: func(err error) { - m.mu.Lock() - if sess.localClient != client { - m.mu.Unlock() - return - } - sess.Info.State = models.SessionError - sess.Info.Error = err.Error() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionError), - "error": err.Error(), - }) - }, - OnClose: func() { - m.handleLocalSessionClosed(sessionID, client) - }, - }) - - // Publish the client before Start so a resize arriving during process - // creation is retained by Client.Resize and used for the initial PTY size. - m.mu.Lock() - if sess.closed || sess.Info.State != models.SessionConnecting { - m.mu.Unlock() - return - } - sess.localClient = client - pendingCols, pendingRows := sess.consumePendingResizeLocked() - m.mu.Unlock() - applyLocalResize(client, pendingCols, pendingRows) - - if err := client.Start(); err != nil { - m.mu.Lock() - emitError := false - if sess.localClient == client { - sess.localClient = nil - sess.Info.State = models.SessionError - sess.Info.Error = err.Error() - emitError = true - } - m.mu.Unlock() - - if emitError { - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionError), - "error": err.Error(), - }) - } - return - } - - m.mu.Lock() - if sess.closed || sess.localClient != client || sess.Info.State != models.SessionConnecting { - m.mu.Unlock() - client.Close() - return - } - sess.Info.State = models.SessionConnected - sess.Info.ConnectedAt = time.Now() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionConnected), - }) - if m.auditLog != nil { - m.auditLog.LogConnect("", sessionID, "local", "localhost") - } -} - -func (m *Manager) handleLocalSessionClosed(sessionID string, client *localshell.Client) { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if !ok || sess.localClient != client { - m.mu.Unlock() - return - } - sess.localClient = nil - if cwd := client.WorkingDirectory(); cwd != "" { - sess.localCwd = cwd - } - m.mu.Unlock() - - m.handleSessionClosed(sessionID) -} - -func terminalDimension(value int, fallback uint16) uint16 { - if value <= 0 { - return fallback - } - if value > int(^uint16(0)) { - return ^uint16(0) - } - return uint16(value) -} - -// Disconnect closes a specific session. -func (m *Manager) Disconnect(sessionID string) error { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if !ok { - m.mu.Unlock() - return fmt.Errorf("sessions: session %q not found", sessionID) - } - // Explicit disconnect means the tab/session is gone, not merely offline. - // Detach it before closing clients so their onClose callbacks cannot start - // auto-reconnect work or retain the session in the manager indefinitely. - delete(m.sessions, sessionID) - sess.closed = true - m.mu.Unlock() - - m.closeSession(sess) - if m.auditLog != nil { - m.auditLog.LogDisconnect(sessionID) - } - return nil -} - -// Reconnect closes and re-establishes a session using its original profile. -func (m *Manager) Reconnect(sessionID string, getSecret func(prompt string) (string, error)) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - if !ok { - m.mu.RUnlock() - return fmt.Errorf("sessions: session %q not found", sessionID) - } - profile := sess.profile - tabName := sess.Info.TabName - pinned := sess.Info.Pinned - m.mu.RUnlock() - - // Close the existing session. - m.closeSession(sess) - - // Remove the old entry. - m.mu.Lock() - delete(m.sessions, sessionID) - m.mu.Unlock() - - // Reconnect with the same profile. - newID, err := m.Connect(profile, getSecret) - if err != nil { - return err - } - - // Preserve tab name and pin state. - m.mu.Lock() - if newSess, ok := m.sessions[newID]; ok { - newSess.Info.TabName = tabName - newSess.Info.Pinned = pinned - } - m.mu.Unlock() - - return nil -} - -// WriteToSession sends data to a session's input. -func (m *Manager) WriteToSession(sessionID string, data []byte) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - if !ok { - m.mu.RUnlock() - return fmt.Errorf("sessions: session %q not found", sessionID) - } - rec := sess.recorder - sshClient := sess.sshClient - telnetClient := sess.telnetClient - rawClient := sess.rawClient - localClient := sess.localClient - m.mu.RUnlock() - - // Record input if recording is active. - if rec != nil { - rec.WriteInput(data) - } - - switch { - case sshClient != nil: - return sshClient.Write(data) - case telnetClient != nil: - return telnetClient.Write(data) - case rawClient != nil: - return rawClient.Write(data) - case localClient != nil: - return localClient.Write(data) - } - return fmt.Errorf("sessions: no active client for session %q", sessionID) -} - -// ResizeSession sends a terminal resize to a session. -func (m *Manager) ResizeSession(sessionID string, cols, rows int) error { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if !ok { - m.mu.Unlock() - return fmt.Errorf("sessions: session %q not found", sessionID) - } - - sshClient := sess.sshClient - telnetClient := sess.telnetClient - localClient := sess.localClient - isRaw := sess.rawClient != nil || (sess.profile != nil && sess.profile.Protocol == models.ProtocolRAW) - if sshClient == nil && telnetClient == nil && localClient == nil && !isRaw { - sess.pendingResizeCols = cols - sess.pendingResizeRows = rows - } - m.mu.Unlock() - - switch { - case sshClient != nil: - return sshClient.Resize(cols, rows) - case telnetClient != nil: - return telnetClient.Resize(cols, rows) - case localClient != nil: - return localClient.Resize(terminalDimension(cols, 80), terminalDimension(rows, 24)) - } - // RAW sessions have no concept of terminal size. - return nil -} - -// consumePendingResizeLocked returns and clears the latest pre-connect geometry. -// m.mu must be held by the caller. -func (sess *Session) consumePendingResizeLocked() (int, int) { - cols := sess.pendingResizeCols - rows := sess.pendingResizeRows - sess.pendingResizeCols = 0 - sess.pendingResizeRows = 0 - return cols, rows -} - -func applySSHResize(client *sshclient.Client, cols, rows int) { - if cols <= 0 || rows <= 0 { - return - } - _ = client.Resize(cols, rows) -} - -func applyTelnetResize(client *telnetclient.Client, cols, rows int) { - if cols <= 0 || rows <= 0 { - return - } - _ = client.Resize(cols, rows) -} - -func applyLocalResize(client *localshell.Client, cols, rows int) { - if cols <= 0 || rows <= 0 { - return - } - _ = client.Resize(terminalDimension(cols, 80), terminalDimension(rows, 24)) -} - -// GetSessionCwd returns a best-effort working directory for SSH sessions. -func (m *Manager) GetSessionCwd(sessionID string) (string, error) { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return "", fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.sshClient == nil { - return "", fmt.Errorf("sessions: session %q is not an SSH connection", sessionID) - } - - cwd, err := sess.sshClient.RunCommand("pwd") - if err != nil { - return "", err - } - return strings.TrimSpace(cwd), nil -} - -// GetSession returns session info by ID. -func (m *Manager) GetSession(sessionID string) (*models.SessionInfo, error) { - m.mu.RLock() - defer m.mu.RUnlock() - - sess, ok := m.sessions[sessionID] - if !ok { - return nil, fmt.Errorf("sessions: session %q not found", sessionID) - } - info := sess.Info - return &info, nil -} - -// GetSessionInfo returns session metadata or nil if the session does not exist. -func (m *Manager) GetSessionInfo(sessionID string) *models.SessionInfo { - m.mu.RLock() - defer m.mu.RUnlock() - - sess, ok := m.sessions[sessionID] - if !ok { - return nil - } - info := sess.Info - return &info -} - -// ListSessions returns all active session info. -func (m *Manager) ListSessions() []models.SessionInfo { - m.mu.RLock() - defer m.mu.RUnlock() - - result := make([]models.SessionInfo, 0, len(m.sessions)) - for _, sess := range m.sessions { - result = append(result, sess.Info) - } - return result -} - -// RenameSession changes a session's tab name. -func (m *Manager) RenameSession(sessionID string, name string) error { - m.mu.Lock() - defer m.mu.Unlock() - - sess, ok := m.sessions[sessionID] - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - sess.Info.TabName = name - return nil -} - -// PinSession toggles pin status. -func (m *Manager) PinSession(sessionID string, pinned bool) error { - m.mu.Lock() - defer m.mu.Unlock() - - sess, ok := m.sessions[sessionID] - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - sess.Info.Pinned = pinned - return nil -} - -// CloseAll closes every active session. -func (m *Manager) CloseAll() { - m.mu.Lock() - sessions := make([]*Session, 0, len(m.sessions)) - for _, sess := range m.sessions { - sess.closed = true - sessions = append(sessions, sess) - } - // Detach all sessions before closing protocol clients. Close callbacks can - // otherwise schedule reconnects while the application is shutting down. - m.sessions = make(map[string]*Session) - m.mu.Unlock() - - for _, sess := range sessions { - m.closeSession(sess) - } -} - -// Shutdown closes all sessions, unregisters app-wide Wails listeners, and -// releases any prompts still waiting for frontend input. -func (m *Manager) Shutdown() { - m.CloseAll() - - m.hostKeyMu.Lock() - hostKeyCancel := m.hostKeyListenerCancel - m.hostKeyListenerCancel = nil - m.hostKeyListenerRegistered = false - hostKeyChans := m.hostKeyChans - m.hostKeyChans = make(map[string]chan bool) - m.hostKeyMu.Unlock() - if hostKeyCancel != nil { - hostKeyCancel() - } - for _, ch := range hostKeyChans { - select { - case ch <- false: - default: - } - } - - m.secretMu.Lock() - secretCancel := m.secretListenerCancel - m.secretListenerCancel = nil - m.secretListenerRegistered = false - secretChans := m.secretChans - m.secretChans = make(map[string]chan secretPromptResponse) - m.secretMu.Unlock() - if secretCancel != nil { - secretCancel() - } - for _, ch := range secretChans { - select { - case ch <- secretPromptResponse{cancelled: true}: - default: - } - } -} - -// GetSessionHistory returns the RAW session history (RAW protocol only). -func (m *Manager) GetSessionHistory(sessionID string) ([]rawclient.HistoryEntry, error) { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return nil, fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.rawClient == nil { - return nil, fmt.Errorf("sessions: session %q is not a RAW connection", sessionID) - } - return sess.rawClient.GetHistory(), nil -} - -// ClearSessionHistory clears RAW session history. -func (m *Manager) ClearSessionHistory(sessionID string) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.rawClient == nil { - return fmt.Errorf("sessions: session %q is not a RAW connection", sessionID) - } - sess.rawClient.ClearHistory() - return nil -} - -// SaveSessionCapture saves RAW session capture to file. -func (m *Manager) SaveSessionCapture(sessionID string, path string) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.rawClient == nil { - return fmt.Errorf("sessions: session %q is not a RAW connection", sessionID) - } - return sess.rawClient.SaveCapture(path) -} - -// SendBreak sends a Telnet break signal (Telnet only). -func (m *Manager) SendBreak(sessionID string) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.telnetClient == nil { - return fmt.Errorf("sessions: session %q is not a Telnet connection", sessionID) - } - return sess.telnetClient.SendBreak() -} - -// IRCHandleInput sends a user-entered composer line to an IRC session. -func (m *Manager) IRCHandleInput(sessionID string, target string, input string) error { - client, err := m.getIRC(sessionID) - if err != nil { - return err - } - return client.HandleInput(target, input) -} - -// IRCJoin joins an IRC room. -func (m *Manager) IRCJoin(sessionID string, room string) error { - client, err := m.getIRC(sessionID) - if err != nil { - return err - } - return client.Join(room) -} - -// IRCPart leaves an IRC room. -func (m *Manager) IRCPart(sessionID string, room string) error { - client, err := m.getIRC(sessionID) - if err != nil { - return err - } - return client.Part(room) -} - -// IRCListRooms requests a server-side IRC room list. -func (m *Manager) IRCListRooms(sessionID string) error { - client, err := m.getIRC(sessionID) - if err != nil { - return err - } - return client.ListRooms() -} - -// IRCRooms returns currently joined IRC rooms. -func (m *Manager) IRCRooms(sessionID string) ([]ircclient.RoomInfo, error) { - client, err := m.getIRC(sessionID) - if err != nil { - return nil, err - } - return client.Rooms(), nil -} - -// IRCUsers returns known users in an IRC room. -func (m *Manager) IRCUsers(sessionID string, room string) ([]string, error) { - client, err := m.getIRC(sessionID) - if err != nil { - return nil, err - } - return client.Users(room), nil -} - -func (m *Manager) getIRC(sessionID string) (*ircclient.Client, error) { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return nil, fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.ircClient == nil { - return nil, fmt.Errorf("sessions: session %q is not an IRC connection", sessionID) - } - return sess.ircClient, nil -} - -// WriteSessionText sends text with line ending via the RAW client. -func (m *Manager) WriteSessionText(sessionID string, text string) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.rawClient == nil { - return fmt.Errorf("sessions: session %q is not a RAW connection", sessionID) - } - return sess.rawClient.WriteText(text) -} - -// WriteSessionHex sends hex-encoded data via the RAW client. -func (m *Manager) WriteSessionHex(sessionID string, hexStr string) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.rawClient == nil { - return fmt.Errorf("sessions: session %q is not a RAW connection", sessionID) - } - return sess.rawClient.WriteHex(hexStr) -} - -// WriteSessionEscaped sends text with C-style escapes via the RAW client. -func (m *Manager) WriteSessionEscaped(sessionID string, text string) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.rawClient == nil { - return fmt.Errorf("sessions: session %q is not a RAW connection", sessionID) - } - return sess.rawClient.WriteEscaped(text) -} - -// --------------------------------------------------------------------------- -// Internal: protocol-specific connection logic -// --------------------------------------------------------------------------- - -// connectSSH creates an SSH client, sets callbacks, and connects. -func (m *Manager) connectSSH(sess *Session, getSecret func(prompt string) (string, error)) error { - profile := sess.profile - - client := sshclient.NewClient(profile) - client.SetCallbacks( - // onData — forward terminal output to the frontend. - func(data []byte) { - m.emitEvent("session:data", map[string]interface{}{ - "sessionID": sess.Info.ID, - "data": base64.StdEncoding.EncodeToString(data), - }) - // Write to active recording if present. - m.mu.RLock() - rec := sess.recorder - m.mu.RUnlock() - if rec != nil { - rec.WriteOutput(data) - } - // Feed data to the login script buffer if active. - sess.loginBufMu.Lock() - if sess.loginDone != nil { - select { - case <-sess.loginDone: - // Login script finished — stop buffering. - default: - sess.loginBuf = append(sess.loginBuf, data...) - if len(sess.loginBuf) > 16384 { - sess.loginBuf = sess.loginBuf[len(sess.loginBuf)-16384:] - } - } - } - sess.loginBufMu.Unlock() - }, - // onError — report connection errors to the frontend. - func(err error) { - m.mu.Lock() - sess.Info.State = models.SessionError - sess.Info.Error = err.Error() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sess.Info.ID, - "state": string(models.SessionError), - "error": err.Error(), - }) - }, - // onClose — handle remote session closure. - func() { - m.handleSessionClosed(sess.Info.ID) - }, - ) - - // Build credential callbacks that respect the profile's secret mode. - sshConf := profile.SSH - getPassword := m.buildSecretCallback(profile.ID, "password", sshConf.SecretMode, getSecret) - getPassphrase := m.buildSecretCallback(profile.ID, "passphrase", sshConf.KeySecretMode, getSecret) - - // Resolve TOTP secret from vault if not already inline. - if sshConf.TOTPSecret == "" { - totpKey := fmt.Sprintf("profile:%s:totp", profile.ID) - if m.keychainStore != nil && m.keychainStore.Available() { - if val, err := m.keychainStore.GetSecret(totpKey); err == nil && val != "" { - sshConf.TOTPSecret = val - } - } - if sshConf.TOTPSecret == "" && m.secretStore != nil && m.secretStore.IsUnlocked() { - if val, err := m.secretStore.GetSecret(totpKey); err == nil && val != "" { - sshConf.TOTPSecret = val - } - } - } - - if err := client.ConnectWithHostKeyCallback(m.hostKeyCallback(sess.Info.ID, sshConf.HostKeyPolicy), getPassword, getPassphrase); err != nil { - return err - } - - m.mu.Lock() - if current, ok := m.sessions[sess.Info.ID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - _ = client.Close() - return fmt.Errorf("session %q was closed while connecting", sess.Info.ID) - } - sess.sshClient = client - cols, rows := sess.consumePendingResizeLocked() - m.mu.Unlock() - applySSHResize(client, cols, rows) - - // Start any port-forwarding rules configured in the profile. - if sshConf != nil && len(sshConf.PortForwards) > 0 { - fwdMgr := client.Forwards() - if fwdMgr != nil { - for _, pf := range sshConf.PortForwards { - if !pf.Enabled { - continue - } - if err := fwdMgr.StartForward(pf); err != nil { - // Non-fatal — log and notify but don't abort the session. - m.emitEvent("session:forward:error", map[string]interface{}{ - "sessionID": sess.Info.ID, - "forwardID": pf.ID, - "label": pf.Label, - "error": err.Error(), - }) - } else { - m.emitEvent("session:forward:started", map[string]interface{}{ - "sessionID": sess.Info.ID, - "forward": pf, - }) - } - } - } - } - - // Execute login scripts if configured. - if sshConf != nil && len(sshConf.LoginScripts) > 0 { - go m.executeLoginScripts(sess, sshConf.LoginScripts) - } - - return nil -} - -// connectTelnet creates a Telnet client, sets callbacks, and connects. -func (m *Manager) connectTelnet(sess *Session) error { - profile := sess.profile - - client := telnetclient.NewClient(profile) - client.SetCallbacks( - // onData - func(data []byte) { - m.emitEvent("session:data", map[string]interface{}{ - "sessionID": sess.Info.ID, - "data": base64.StdEncoding.EncodeToString(data), - }) - // Write to active recording if present. - m.mu.RLock() - rec := sess.recorder - m.mu.RUnlock() - if rec != nil { - rec.WriteOutput(data) - } - // Feed data to the login script buffer if active. - sess.loginBufMu.Lock() - if sess.loginDone != nil { - select { - case <-sess.loginDone: - default: - sess.loginBuf = append(sess.loginBuf, data...) - if len(sess.loginBuf) > 16384 { - sess.loginBuf = sess.loginBuf[len(sess.loginBuf)-16384:] - } - } - } - sess.loginBufMu.Unlock() - }, - // onError - func(err error) { - m.mu.Lock() - sess.Info.State = models.SessionError - sess.Info.Error = err.Error() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sess.Info.ID, - "state": string(models.SessionError), - "error": err.Error(), - }) - }, - // onClose - func() { - m.handleSessionClosed(sess.Info.ID) - }, - // onDebug - func(msg string) { - m.emitEvent("session:debug", map[string]interface{}{ - "sessionID": sess.Info.ID, - "message": msg, - }) - }, - ) - - if err := client.Connect(); err != nil { - return err - } - - m.mu.Lock() - if current, ok := m.sessions[sess.Info.ID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - _ = client.Close() - return fmt.Errorf("session %q was closed while connecting", sess.Info.ID) - } - sess.telnetClient = client - cols, rows := sess.consumePendingResizeLocked() - m.mu.Unlock() - applyTelnetResize(client, cols, rows) - - // Execute login scripts if configured. - telConf := profile.Telnet - if telConf != nil && len(telConf.LoginScripts) > 0 { - go m.executeLoginScripts(sess, telConf.LoginScripts) - } - - return nil -} - -// connectRAW creates a RAW TCP client, sets callbacks, and connects. -func (m *Manager) connectRAW(sess *Session) error { - profile := sess.profile - - client := rawclient.NewClient(profile) - client.SetCallbacks( - // onData — includes direction and timestamp for RAW protocol. - func(data []byte, direction string, timestamp time.Time) { - m.emitEvent("session:raw:data", map[string]interface{}{ - "sessionID": sess.Info.ID, - "data": base64.StdEncoding.EncodeToString(data), - "direction": direction, - "timestamp": timestamp.UnixMilli(), - }) - }, - // onError - func(err error) { - m.mu.Lock() - sess.Info.State = models.SessionError - sess.Info.Error = err.Error() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sess.Info.ID, - "state": string(models.SessionError), - "error": err.Error(), - }) - }, - // onClose - func() { - m.handleSessionClosed(sess.Info.ID) - }, - ) - - if err := client.Connect(); err != nil { - return err - } - - m.mu.Lock() - if current, ok := m.sessions[sess.Info.ID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - _ = client.Close() - return fmt.Errorf("session %q was closed while connecting", sess.Info.ID) - } - sess.rawClient = client - m.mu.Unlock() - return nil -} - -// connectGitHub validates the token and performs the initial repository fetch. -// It also starts a background goroutine for periodic refresh. -func (m *Manager) connectGitHub(sess *Session, getSecret func(prompt string) (string, error)) error { - profile := sess.profile - ghConf := profile.GitHub - if ghConf == nil { - return fmt.Errorf("GitHub configuration missing on profile %q", profile.ID) - } - - // Resolve the access token using the standard secret flow. - getToken := m.buildSecretCallback(profile.ID, "github-token", ghConf.SecretMode, getSecret) - token, err := getToken() - if err != nil { - return fmt.Errorf("obtaining GitHub token: %w", err) - } - if token == "" { - return fmt.Errorf("GitHub token cannot be empty") - } - - client := githubclient.New(ghConf.BaseURL, token) - - // Validate the token. - login, err := client.ValidateToken(context.Background()) - if err != nil { - if m.isGitHubAuthError(err) && getSecret != nil { - retryToken, retryErr := m.promptAndStoreSecret( - profile.ID, - "github-token", - ghConf.SecretMode, - getSecret, - "GitHub token did not work. Please enter a valid token:", - ) - if retryErr == nil && retryToken != "" { - client = githubclient.New(ghConf.BaseURL, retryToken) - login, err = client.ValidateToken(context.Background()) - } - } - if err != nil { - return fmt.Errorf("GitHub authentication failed: %w", err) - } - } - - m.mu.Lock() - if current, ok := m.sessions[sess.Info.ID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - return fmt.Errorf("session %q was closed while connecting", sess.Info.ID) - } - sess.githubClient = client - m.mu.Unlock() - - // Perform the initial fetch. - m.emitEvent("session:github:status", map[string]interface{}{ - "sessionID": sess.Info.ID, - "status": "Loading repositories...", - }) - repos, err := client.FetchReposWithProgress(context.Background(), func(done int, total int) { - if total <= 0 { - return - } - m.emitEvent("session:github:status", map[string]interface{}{ - "sessionID": sess.Info.ID, - "status": fmt.Sprintf("Loading commit info (%d/%d)", done, total), - }) - }) - if err != nil { - return fmt.Errorf("fetching repositories: %w", err) - } - - // Emit the initial repo list. - m.emitEvent("session:github:repos", map[string]interface{}{ - "sessionID": sess.Info.ID, - "login": login, - "repos": repos, - }) - - // Start the periodic refresh goroutine. - interval := time.Duration(ghConf.RefreshIntervalSecs) * time.Second - if interval < 60*time.Second { - interval = 5 * time.Minute - } - - ctx, cancel := context.WithCancel(context.Background()) - m.mu.Lock() - if current, ok := m.sessions[sess.Info.ID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - cancel() - return fmt.Errorf("session %q was closed while connecting", sess.Info.ID) - } - sess.githubCancel = cancel - m.mu.Unlock() - - go func() { - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for { - select { - case <-ctx.Done(): - return - case <-ticker.C: - m.emitEvent("session:github:status", map[string]interface{}{ - "sessionID": sess.Info.ID, - "status": "Refreshing repositories...", - }) - updated, fetchErr := client.FetchReposWithProgress(ctx, func(done int, total int) { - if total <= 0 { - return - } - m.emitEvent("session:github:status", map[string]interface{}{ - "sessionID": sess.Info.ID, - "status": fmt.Sprintf("Loading commit info (%d/%d)", done, total), - }) - }) - if fetchErr != nil { - m.emitEvent("session:github:error", map[string]interface{}{ - "sessionID": sess.Info.ID, - "error": fetchErr.Error(), - }) - continue - } - m.emitEvent("session:github:repos", map[string]interface{}{ - "sessionID": sess.Info.ID, - "login": login, - "repos": updated, - }) - } - } - }() - - return nil -} - -// FetchGitHubRepos triggers an on-demand refresh of GitHub repos for a session. -func (m *Manager) FetchGitHubRepos(sessionID string) ([]models.GitHubRepo, error) { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return nil, fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.githubClient == nil { - return nil, fmt.Errorf("sessions: session %q is not a GitHub connection", sessionID) - } - - repos, err := sess.githubClient.FetchRepos(context.Background()) - if err != nil { - return nil, fmt.Errorf("fetching repos: %w", err) - } - - m.emitEvent("session:github:repos", map[string]interface{}{ - "sessionID": sessionID, - "repos": repos, - }) - - return repos, nil -} - -// DownloadGitHubArchive downloads a repository zip archive for a GitHub session. -func (m *Manager) DownloadGitHubArchive(sessionID string, fullName string, ref string, destPath string) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.githubClient == nil { - return fmt.Errorf("sessions: session %q is not a GitHub connection", sessionID) - } - - if ref == "" { - ref = "HEAD" - } - - if err := sess.githubClient.DownloadRepoArchive(context.Background(), fullName, ref, destPath); err != nil { - return fmt.Errorf("downloading archive: %w", err) - } - return nil -} - -// connectVNC establishes a VNC connection (with optional SSH tunnel) and -// emits the local WebSocket proxy URL to the frontend. -func (m *Manager) connectVNC(sess *Session, getSecret func(prompt string) (string, error)) error { - profile := sess.profile - vncConf := profile.VNC - if vncConf == nil { - return fmt.Errorf("VNC configuration missing") - } - - // Fall back to the profile-level host if VNC-specific host is empty. - vncHost := vncConf.VNCHost - if vncHost == "" { - vncHost = profile.Host - } - - cfg := vncclient.Config{ - VNCHost: vncHost, - VNCPort: vncConf.VNCPort, - OnError: func(err error) { - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sess.Info.ID, - "state": string(models.SessionError), - "error": err.Error(), - }) - }, - } - - // SSH tunnel configuration - if vncConf.SSHTunnel { - cfg.SSHTunnel = true - cfg.SSHHost = vncConf.SSHHost - cfg.SSHPort = vncConf.SSHPort - cfg.SSHUsername = vncConf.SSHUsername - cfg.SSHAuthMethod = vncConf.SSHAuthMethod - cfg.SSHAgentAuth = vncConf.SSHAgentAuth - cfg.SSHHostKeyCallback = m.hostKeyCallback(sess.Info.ID, models.HostKeyAsk) - - // Resolve SSH credentials - switch vncConf.SSHAuthMethod { - case "password": - getPass := m.buildSecretCallback(profile.ID, "vnc-ssh-password", vncConf.SSHSecretMode, getSecret) - password, err := getPass() - if err != nil { - return fmt.Errorf("SSH password: %w", err) - } - cfg.SSHPassword = password - case "publickey": - if vncConf.SSHPrivateKey != "" { - info, err := os.Stat(vncConf.SSHPrivateKey) - if err != nil { - return fmt.Errorf("reading SSH key metadata: %w", err) - } - if info.IsDir() { - return fmt.Errorf("SSH key path is a directory") - } - if info.Size() > maxTunnelPrivateKeyBytes { - return fmt.Errorf("SSH key is too large: %d bytes exceeds %d bytes", info.Size(), maxTunnelPrivateKeyBytes) - } - keyData, err := os.ReadFile(vncConf.SSHPrivateKey) - if err != nil { - return fmt.Errorf("reading SSH key: %w", err) - } - cfg.SSHPrivateKey = keyData - // Get passphrase if needed - getPassphrase := m.buildSecretCallback(profile.ID, "vnc-ssh-passphrase", vncConf.SSHSecretMode, getSecret) - passphrase, _ := getPassphrase() - cfg.SSHPassphrase = passphrase - } - } - - // SSH agent support - if vncConf.SSHAgentAuth { - socketPath := os.Getenv("SSH_AUTH_SOCK") - if socketPath != "" { - agentConn, err := net.Dial("unix", socketPath) - if err == nil { - cfg.SSHAgentConn = agentConn - } - } - } - } else { - // Direct VNC — may need a VNC password - getVNCPass := m.buildSecretCallback(profile.ID, "vnc-password", vncConf.SecretMode, getSecret) - password, _ := getVNCPass() - // VNC auth is handled by the RFB protocol in the frontend (noVNC), - // but we store it in case we need server-side auth negotiation later. - _ = password - } - - client := vncclient.NewClient(cfg) - wsURL, err := client.Connect(cfg) - if err != nil { - return err - } - - m.mu.Lock() - if current, ok := m.sessions[sess.Info.ID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - client.Close() - return fmt.Errorf("session %q was closed while connecting", sess.Info.ID) - } - sess.vncClient = client - m.mu.Unlock() - - // Emit the WebSocket URL so the frontend can connect noVNC. - m.emitEvent("session:vnc:ready", map[string]interface{}{ - "sessionID": sess.Info.ID, - "wsURL": wsURL, - }) - - return nil -} - -// connectIRC establishes an IRC connection, optionally through an SSH tunnel, -// and forwards typed IRC events to the frontend. -func (m *Manager) connectIRC(sess *Session, getSecret func(prompt string) (string, error)) error { - profile := sess.profile - ircConf := profile.IRC - if ircConf == nil { - return fmt.Errorf("IRC configuration missing") - } - - serverPass := "" - if ircConf.ServerPasswordRequired { - getPass := m.buildSecretCallback(profile.ID, "irc-server-password", ircConf.ServerPasswordMode, getSecret) - password, err := getPass() - if err != nil { - return fmt.Errorf("IRC server password: %w", err) - } - serverPass = password - } - - nickServPass := "" - if ircConf.NickServIdentify { - getPass := m.buildSecretCallback(profile.ID, "irc-nickserv-password", ircConf.NickServMode, getSecret) - password, err := getPass() - if err != nil { - return fmt.Errorf("NickServ password: %w", err) - } - nickServPass = password - } - - autoJoinRooms := append([]string{}, ircConf.AutoJoinRooms...) - if m.store != nil && profile.ID != "" { - lastRooms, err := m.store.GetIRCLastRooms(profile.ID) - if err != nil { - return fmt.Errorf("loading remembered IRC rooms: %w", err) - } - autoJoinRooms = mergeIRCRooms(autoJoinRooms, lastRooms) - } - - cfg := ircclient.Config{ - Server: profile.Host, - Port: profile.Port, - Nick: ircConf.Nick, - User: profile.Username, - RealName: ircConf.RealName, - UseTLS: ircConf.UseTLS, - ServerPass: serverPass, - AutoJoinRooms: autoJoinRooms, - NickServIdentify: ircConf.NickServIdentify, - NickServAccount: ircConf.NickServAccount, - NickServPassword: nickServPass, - ConnectTimeout: time.Duration(profile.ConnectTimeout) * time.Second, - OnEvent: func(event ircclient.Event) { m.emitIRCEvent(sess.Info.ID, event) }, - OnError: func(err error) { m.markSessionError(sess, err) }, - OnClose: func() { m.handleSessionClosed(sess.Info.ID) }, - SSHTunnel: ircConf.SSHTunnel, - SSHHost: ircConf.SSHHost, - SSHPort: ircConf.SSHPort, - SSHUsername: ircConf.SSHUsername, - SSHAuthMethod: ircConf.SSHAuthMethod, - SSHAgentAuth: ircConf.SSHAgentAuth, - SSHHostKeyCallback: m.hostKeyCallback(sess.Info.ID, models.HostKeyAsk), - } - if m.store != nil && profile.ID != "" { - profileID := profile.ID - cfg.OnRoomsChanged = func(rooms []string) { - if err := m.store.SaveIRCLastRooms(profileID, rooms); err != nil { - m.emitIRCEvent(sess.Info.ID, ircclient.Event{ - Type: "system", - Target: ircclient.ServerTarget, - Text: "Could not remember open IRC rooms: " + err.Error(), - Timestamp: time.Now().UnixMilli(), - }) - } - } - } - - if cfg.User == "" { - cfg.User = ircConf.Nick - } - - if ircConf.SSHTunnel { - switch ircConf.SSHAuthMethod { - case "password": - getPass := m.buildSecretCallback(profile.ID, "irc-ssh-password", ircConf.SSHSecretMode, getSecret) - password, err := getPass() - if err != nil { - return fmt.Errorf("IRC SSH password: %w", err) - } - cfg.SSHPassword = password - case "publickey": - if ircConf.SSHPrivateKey != "" { - info, err := os.Stat(ircConf.SSHPrivateKey) - if err != nil { - return fmt.Errorf("reading IRC SSH key metadata: %w", err) - } - if info.IsDir() { - return fmt.Errorf("IRC SSH key path is a directory") - } - if info.Size() > maxTunnelPrivateKeyBytes { - return fmt.Errorf("IRC SSH key is too large: %d bytes exceeds %d bytes", info.Size(), maxTunnelPrivateKeyBytes) - } - keyData, err := os.ReadFile(ircConf.SSHPrivateKey) - if err != nil { - return fmt.Errorf("reading IRC SSH key: %w", err) - } - cfg.SSHPrivateKey = keyData - getPassphrase := m.buildSecretCallback(profile.ID, "irc-ssh-passphrase", ircConf.SSHSecretMode, getSecret) - passphrase, _ := getPassphrase() - cfg.SSHPassphrase = passphrase - } - } - - if ircConf.SSHAgentAuth { - socketPath := os.Getenv("SSH_AUTH_SOCK") - if socketPath != "" { - agentConn, err := net.Dial("unix", socketPath) - if err == nil { - cfg.SSHAgentConn = agentConn - } - } - } - } - - client := ircclient.New(cfg) - if err := client.Connect(); err != nil { - return err - } - - m.mu.Lock() - if current, ok := m.sessions[sess.Info.ID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - client.Close() - return fmt.Errorf("session %q was closed while connecting", sess.Info.ID) - } - sess.ircClient = client - m.mu.Unlock() - return nil -} - -func mergeIRCRooms(groups ...[]string) []string { - var rooms []string - seen := make(map[string]struct{}) - for _, group := range groups { - for _, room := range group { - room = strings.TrimSpace(room) - if room == "" { - continue - } - key := strings.ToLower(room) - if _, exists := seen[key]; exists { - continue - } - seen[key] = struct{}{} - rooms = append(rooms, room) - } - } - return rooms -} - -func (m *Manager) emitIRCEvent(sessionID string, event ircclient.Event) { - m.emitEvent("session:irc:event", map[string]interface{}{ - "sessionID": sessionID, - "event": event, - }) -} - -func (m *Manager) markSessionError(sess *Session, err error) { - if err == nil { - return - } - m.mu.Lock() - sess.Info.State = models.SessionError - sess.Info.Error = err.Error() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sess.Info.ID, - "state": string(models.SessionError), - "error": err.Error(), - }) -} - -// GetVNCWebSocketURL returns the local WebSocket proxy URL for a VNC session. -func (m *Manager) GetVNCWebSocketURL(sessionID string) (string, error) { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return "", fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.vncClient == nil { - return "", fmt.Errorf("sessions: session %q is not a VNC connection", sessionID) - } - return sess.vncClient.WSURL(), nil -} - -// --------------------------------------------------------------------------- -// Session recording -// --------------------------------------------------------------------------- - -// StartRecording begins recording terminal I/O for a session. -// Returns the recording ID (same as session ID) on success. -func (m *Manager) StartRecording(sessionID string, cols, rows int) (string, error) { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if !ok { - m.mu.Unlock() - return "", fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.recorder != nil { - m.mu.Unlock() - return "", fmt.Errorf("sessions: session %q is already being recorded", sessionID) - } - m.mu.Unlock() - - if m.recordStore == nil { - return "", fmt.Errorf("sessions: recording store not configured") - } - - title := fmt.Sprintf("%s — %s", sess.Info.ProfileName, sess.Info.Host) - rec, err := recording.NewRecorder(m.recordStore.Dir(), sessionID, title, cols, rows) - if err != nil { - return "", fmt.Errorf("sessions: failed to start recording: %w", err) - } - - m.mu.Lock() - sess.recorder = rec - m.mu.Unlock() - - m.emitEvent("session:recording:started", map[string]interface{}{ - "sessionID": sessionID, - }) - - return sessionID, nil -} - -// StopRecording stops an active recording for a session. -func (m *Manager) StopRecording(sessionID string) error { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if !ok { - m.mu.Unlock() - return fmt.Errorf("sessions: session %q not found", sessionID) - } - rec := sess.recorder - sess.recorder = nil - m.mu.Unlock() - - if rec == nil { - return fmt.Errorf("sessions: session %q is not being recorded", sessionID) - } - - if err := rec.Close(); err != nil { - return fmt.Errorf("sessions: failed to stop recording: %w", err) - } - - m.emitEvent("session:recording:stopped", map[string]interface{}{ - "sessionID": sessionID, - "duration": rec.Duration(), - "events": rec.EventCount(), - }) - - return nil -} - -// IsRecording reports whether a session is currently being recorded. -func (m *Manager) IsRecording(sessionID string) bool { - m.mu.RLock() - defer m.mu.RUnlock() - - sess, ok := m.sessions[sessionID] - if !ok { - return false - } - return sess.recorder != nil -} - -// ListRecordings returns metadata for all saved recordings. -func (m *Manager) ListRecordings() ([]recording.RecordingMeta, error) { - if m.recordStore == nil { - return nil, fmt.Errorf("sessions: recording store not configured") - } - return m.recordStore.List() -} - -// GetRecording returns the full recording data (header + events) for playback. -func (m *Manager) GetRecording(id string) (*recording.Header, []recording.Event, error) { - if m.recordStore == nil { - return nil, nil, fmt.Errorf("sessions: recording store not configured") - } - return m.recordStore.Get(id) -} - -// DeleteRecording removes a saved recording. -func (m *Manager) DeleteRecording(id string) error { - if m.recordStore == nil { - return fmt.Errorf("sessions: recording store not configured") - } - return m.recordStore.Delete(id) -} - -// RecordingStorageInfo returns aggregate recording storage usage. -func (m *Manager) RecordingStorageInfo() (recording.StorageInfo, error) { - if m.recordStore == nil { - return recording.StorageInfo{}, fmt.Errorf("sessions: recording store not configured") - } - return m.recordStore.StorageInfo() -} - -// CleanupRecordings applies age and total-size retention limits. -func (m *Manager) CleanupRecordings(maxAgeDays int, maxTotalBytes int64) (recording.CleanupResult, error) { - if m.recordStore == nil { - return recording.CleanupResult{}, fmt.Errorf("sessions: recording store not configured") - } - return m.recordStore.Cleanup(maxAgeDays, maxTotalBytes) -} - -// --------------------------------------------------------------------------- -// SFTP file browser -// --------------------------------------------------------------------------- - -// getSFTP returns or lazily creates the SFTP client for an SSH session. -func (m *Manager) getSFTP(sessionID string) (*sftpclient.Client, error) { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if !ok { - m.mu.Unlock() - return nil, fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.sshClient == nil { - m.mu.Unlock() - return nil, fmt.Errorf("sessions: session %q is not an SSH connection", sessionID) - } - - // Return existing SFTP client if already open. - if sess.sftpClient != nil { - c := sess.sftpClient - m.mu.Unlock() - return c, nil - } - m.mu.Unlock() - - // Create a new SFTP subsystem. - sshConn := sess.sshClient.SSHConn() - if sshConn == nil { - return nil, fmt.Errorf("sessions: SSH connection is not established") - } - - sc, err := sftpclient.NewClient(sshConn) - if err != nil { - return nil, err - } - - m.mu.Lock() - sess.sftpClient = sc - m.mu.Unlock() - - return sc, nil -} - -// SFTPList lists the contents of a remote directory. -func (m *Manager) SFTPList(sessionID, remotePath string) ([]sftpclient.FileEntry, error) { - sc, err := m.getSFTP(sessionID) - if err != nil { - return nil, err - } - return sc.List(remotePath) -} - -// SFTPStat returns information about a remote path. -func (m *Manager) SFTPStat(sessionID, remotePath string) (*sftpclient.FileEntry, error) { - sc, err := m.getSFTP(sessionID) - if err != nil { - return nil, err - } - return sc.Stat(remotePath) -} - -// SFTPMkdir creates a remote directory. -func (m *Manager) SFTPMkdir(sessionID, remotePath string) error { - sc, err := m.getSFTP(sessionID) - if err != nil { - return err - } - return sc.MkdirAll(remotePath) -} - -// SFTPRemove deletes a remote file or empty directory. -func (m *Manager) SFTPRemove(sessionID, remotePath string) error { - sc, err := m.getSFTP(sessionID) - if err != nil { - return err - } - return sc.Remove(remotePath) -} - -// SFTPRename moves or renames a remote file/directory. -func (m *Manager) SFTPRename(sessionID, oldPath, newPath string) error { - sc, err := m.getSFTP(sessionID) - if err != nil { - return err - } - return sc.Rename(oldPath, newPath) -} - -// SFTPDownload downloads a remote file to a local path. -func (m *Manager) SFTPDownload(sessionID, remotePath, localPath string) error { - sc, err := m.getSFTP(sessionID) - if err != nil { - return err - } - return sc.Download(remotePath, localPath, func(p sftpclient.TransferProgress) { - m.emitEvent("session:sftp:progress", map[string]interface{}{ - "sessionID": sessionID, - "direction": "download", - "transferred": p.Transferred, - "total": p.Total, - "path": p.Path, - "done": p.Done, - }) - }) -} - -// SFTPUpload uploads a local file to a remote path. -func (m *Manager) SFTPUpload(sessionID, localPath, remotePath string) error { - sc, err := m.getSFTP(sessionID) - if err != nil { - return err - } - return sc.Upload(localPath, remotePath, func(p sftpclient.TransferProgress) { - m.emitEvent("session:sftp:progress", map[string]interface{}{ - "sessionID": sessionID, - "direction": "upload", - "transferred": p.Transferred, - "total": p.Total, - "path": p.Path, - "done": p.Done, - }) - }) -} - -// SFTPUploadBytes uploads in-memory data to a remote path. -func (m *Manager) SFTPUploadBytes(sessionID, remotePath string, data []byte) error { - sc, err := m.getSFTP(sessionID) - if err != nil { - return err - } - return sc.UploadBytes(remotePath, data, func(p sftpclient.TransferProgress) { - m.emitEvent("session:sftp:progress", map[string]interface{}{ - "sessionID": sessionID, - "direction": "upload", - "transferred": p.Transferred, - "total": p.Total, - "path": p.Path, - "done": p.Done, - }) - }) -} - -// SFTPOpenUpload opens a remote file for a caller-managed streaming upload. -func (m *Manager) SFTPOpenUpload(sessionID, remotePath string) (io.WriteCloser, error) { - sc, err := m.getSFTP(sessionID) - if err != nil { - return nil, err - } - return sc.CreateWriter(remotePath) -} - -// SFTPPwd returns the current working directory on the remote host. -func (m *Manager) SFTPPwd(sessionID string) (string, error) { - sc, err := m.getSFTP(sessionID) - if err != nil { - return "", err - } - return sc.Pwd() -} - -// --------------------------------------------------------------------------- -// Port-forwarding management -// --------------------------------------------------------------------------- - -// ListPortForwards returns the active port-forwarding rules for a session. -func (m *Manager) ListPortForwards(sessionID string) ([]models.PortForward, error) { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return nil, fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.sshClient == nil { - return nil, fmt.Errorf("sessions: session %q is not an SSH connection", sessionID) - } - - fwdMgr := sess.sshClient.Forwards() - if fwdMgr == nil { - return []models.PortForward{}, nil - } - return fwdMgr.ActiveForwards(), nil -} - -// StartPortForward activates a new forwarding tunnel on a live SSH session. -func (m *Manager) StartPortForward(sessionID string, rule models.PortForward) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.sshClient == nil { - return fmt.Errorf("sessions: session %q is not an SSH connection", sessionID) - } - - fwdMgr := sess.sshClient.Forwards() - if fwdMgr == nil { - return fmt.Errorf("sessions: forward manager not initialised") - } - - if err := fwdMgr.StartForward(rule); err != nil { - return err - } - - m.emitEvent("session:forward:started", map[string]interface{}{ - "sessionID": sessionID, - "forward": rule, - }) - return nil -} - -// StopPortForward terminates an active forwarding tunnel. -func (m *Manager) StopPortForward(sessionID string, forwardID string) error { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.sshClient == nil { - return fmt.Errorf("sessions: session %q is not an SSH connection", sessionID) - } - - fwdMgr := sess.sshClient.Forwards() - if fwdMgr == nil { - return fmt.Errorf("sessions: forward manager not initialised") - } - - if err := fwdMgr.StopForward(forwardID); err != nil { - return err - } - - m.emitEvent("session:forward:stopped", map[string]interface{}{ - "sessionID": sessionID, - "forwardID": forwardID, - }) - return nil -} - -// PingSession sends a keepalive request to the server and returns the RTT in milliseconds. -func (m *Manager) PingSession(sessionID string) (int64, error) { - m.mu.RLock() - sess, ok := m.sessions[sessionID] - m.mu.RUnlock() - - if !ok { - return 0, fmt.Errorf("sessions: session %q not found", sessionID) - } - if sess.sshClient == nil { - return 0, fmt.Errorf("sessions: session %q is not an SSH connection", sessionID) - } - - return sess.sshClient.Ping() -} - -// --------------------------------------------------------------------------- -// Internal: helpers -// --------------------------------------------------------------------------- - -func (m *Manager) hostKeyCallback(sessionID string, policy models.HostKeyPolicy) ssh.HostKeyCallback { - if policy == "" { - policy = models.HostKeyAsk - } - verifier := sshclient.NewHostKeyVerifier(m.store, policy) - verifier.SetPromptHandler(func(host string, keyType string, fingerprint string) (bool, error) { - return m.promptHostKey(sessionID, host, keyType, fingerprint) - }) - return verifier.HostKeyCallback() -} - -func (m *Manager) promptHostKey(sessionID string, host string, keyType string, fingerprint string) (bool, error) { - if m.ctx == nil && m.eventSink == nil { - return false, fmt.Errorf("host key prompt unavailable") - } - - requestID := uuid.New().String() - ch := make(chan bool, 1) - - m.hostKeyMu.Lock() - if m.hostKeyChans == nil { - m.hostKeyChans = make(map[string]chan bool) - } - m.hostKeyChans[requestID] = ch - m.hostKeyMu.Unlock() - defer func() { - m.hostKeyMu.Lock() - delete(m.hostKeyChans, requestID) - m.hostKeyMu.Unlock() - }() - - m.emitEvent("session:hostkey:verify", map[string]interface{}{ - "requestID": requestID, - "sessionID": sessionID, - "host": host, - "keyType": keyType, - "fingerprint": fingerprint, - }) - - select { - case accepted := <-ch: - return accepted, nil - case <-time.After(60 * time.Second): - return false, fmt.Errorf("host key prompt timed out waiting for user response") - } -} - -func (m *Manager) registerHostKeyResponseListener() { - ctx := m.ctx - if ctx == nil { - return - } - - m.hostKeyMu.Lock() - if m.hostKeyChans == nil { - m.hostKeyChans = make(map[string]chan bool) - } - if m.hostKeyListenerRegistered { - m.hostKeyMu.Unlock() - return - } - m.hostKeyListenerRegistered = true - m.hostKeyMu.Unlock() - - cancel := wailsRuntime.EventsOn(ctx, "session:hostkey:response", m.handleHostKeyResponse) - m.hostKeyMu.Lock() - m.hostKeyListenerCancel = cancel - m.hostKeyMu.Unlock() -} - -func (m *Manager) handleHostKeyResponse(optionalData ...interface{}) { - if len(optionalData) == 0 { - return - } - data, ok := optionalData[0].(map[string]interface{}) - if !ok { - return - } - requestID, _ := data["requestID"].(string) - accepted, _ := data["accepted"].(bool) - - m.hostKeyMu.Lock() - ch, exists := m.hostKeyChans[requestID] - m.hostKeyMu.Unlock() - - if exists { - select { - case ch <- accepted: - default: - } - } -} - -// RespondHostKey supplies a host-key prompt response for non-Wails hosts. -func (m *Manager) RespondHostKey(requestID string, accepted bool) { - m.hostKeyMu.Lock() - ch, exists := m.hostKeyChans[requestID] - m.hostKeyMu.Unlock() - - if exists { - select { - case ch <- accepted: - default: - } - } -} - -// buildSecretCallback returns a func() (string, error) that resolves a -// credential according to the profile's SecretMode: -// - "saved": check keychain/vault first, prompt if missing, then persist -// - "session-only": check an in-memory session cache, prompt once per session -// - "ask-every-time" (or any other value): always prompt the user -func (m *Manager) buildSecretCallback(profileID string, secretType string, mode models.SecretMode, getSecret func(prompt string) (string, error)) func() (string, error) { - secretKey := fmt.Sprintf("profile:%s:%s", profileID, secretType) - - // Session-local cache for "session-only" mode. The closure captures this - // variable so it survives across multiple calls within the same connection. - var sessionCached string - var sessionCachedSet bool - - return func() (string, error) { - // ── "saved" mode: try persistent stores first ──────────────────── - if mode == models.SecretModeSaved { - // Try the OS keychain first. - if m.keychainStore != nil { - if val, err := m.keychainStore.GetSecret(secretKey); err == nil && val != "" { - m.auditLog.LogSecretAccess(profileID, secretType) - return val, nil - } - } - - // Try the encrypted file store. - if m.secretStore != nil && m.secretStore.IsUnlocked() { - if val, err := m.secretStore.GetSecret(secretKey); err == nil && val != "" { - m.auditLog.LogSecretAccess(profileID, secretType) - return val, nil - } - } - } - - // ── "session-only" mode: return cached value if we already asked ─ - if mode == models.SecretModeSessionOnly && sessionCachedSet { - return sessionCached, nil - } - - // ── Prompt the user ────────────────────────────────────────────── - if getSecret == nil { - return "", fmt.Errorf("no %s available and no prompt callback provided", secretType) - } - - prompt := fmt.Sprintf("Enter %s:", secretType) - val, err := getSecret(prompt) - if err != nil { - return "", err - } - - // ── Persist according to mode ──────────────────────────────────── - switch mode { - case models.SecretModeSaved: - // Prefer OS keychain, fall back to encrypted vault. - stored := false - if m.keychainStore != nil { - if sErr := m.keychainStore.SetSecret(secretKey, val); sErr == nil { - stored = true - } - } - if !stored && m.secretStore != nil && m.secretStore.IsUnlocked() { - _ = m.secretStore.SetSecret(secretKey, val) - } - - case models.SecretModeSessionOnly: - sessionCached = val - sessionCachedSet = true - } - - return val, nil - } -} - -// promptAndStoreSecret asks the user for a secret and stores it based on the mode. -func (m *Manager) promptAndStoreSecret(profileID string, secretType string, mode models.SecretMode, getSecret func(prompt string) (string, error), prompt string) (string, error) { - if getSecret == nil { - return "", fmt.Errorf("no %s available and no prompt callback provided", secretType) - } - val, err := getSecret(prompt) - if err != nil { - return "", err - } - if val == "" { - return "", fmt.Errorf("%s cannot be empty", secretType) - } - - if mode == models.SecretModeSaved { - stored := false - secretKey := fmt.Sprintf("profile:%s:%s", profileID, secretType) - if m.keychainStore != nil { - if sErr := m.keychainStore.SetSecret(secretKey, val); sErr == nil { - stored = true - } - } - if !stored && m.secretStore != nil && m.secretStore.IsUnlocked() { - _ = m.secretStore.SetSecret(secretKey, val) - } - } - - return val, nil -} - -// isGitHubAuthError checks whether an error indicates authentication failure. -func (m *Manager) isGitHubAuthError(err error) bool { - if err == nil { - return false - } - msg := strings.ToLower(err.Error()) - return strings.Contains(msg, "token validation failed") || - strings.Contains(msg, "authentication failed") || - strings.Contains(msg, "http 401") || - strings.Contains(msg, "http 403") -} - -// closeSession shuts down the protocol client for a session. -func (m *Manager) closeSession(sess *Session) { - m.mu.Lock() - sess.closed = true - cancelReconn := sess.cancelReconn - recorder := sess.recorder - sftpClient := sess.sftpClient - sshClient := sess.sshClient - telnetClient := sess.telnetClient - rawClient := sess.rawClient - githubCancel := sess.githubCancel - vncClient := sess.vncClient - ircClient := sess.ircClient - localClient := sess.localClient - - // Break all retained client/callback references while holding the lifecycle - // lock. This makes repeated close calls harmless and lets closed sessions be - // collected even if a short-lived callback still holds the Session object. - sess.cancelReconn = nil - sess.recorder = nil - sess.sftpClient = nil - sess.sshClient = nil - sess.telnetClient = nil - sess.rawClient = nil - sess.githubClient = nil - sess.githubCancel = nil - sess.vncClient = nil - sess.ircClient = nil - sess.localClient = nil - sess.profile = nil - sess.localShell = "" - sess.localCwd = "" - sess.reconnectCount = 0 - sess.pendingResizeCols = 0 - sess.pendingResizeRows = 0 - sess.Info.State = models.SessionDisconnected - m.mu.Unlock() - - if cancelReconn != nil { - cancelReconn() - } - if recorder != nil { - _ = recorder.Close() - } - if sftpClient != nil { - _ = sftpClient.Close() - } - if sshClient != nil { - _ = sshClient.Close() - } - if telnetClient != nil { - _ = telnetClient.Close() - } - if rawClient != nil { - _ = rawClient.Close() - } - if githubCancel != nil { - githubCancel() - } - if vncClient != nil { - vncClient.Close() - } - if ircClient != nil { - ircClient.Close() - } - if localClient != nil { - localClient.Close() - } -} - -// --------------------------------------------------------------------------- -// Login script execution (expect-style send/wait) -// --------------------------------------------------------------------------- - -// executeLoginScripts processes an expect-style login script sequence against -// a connected session. "wait" steps block until the specified text appears in -// the session output (with a timeout). "send" steps write text to the session -// input. The session's loginBuf/loginDone fields are used for pattern matching. -func (m *Manager) executeLoginScripts(sess *Session, steps []models.LoginScriptStep) { - // Initialise the login buffer so the onData hook starts accumulating. - sess.loginBufMu.Lock() - sess.loginBuf = nil - sess.loginDone = make(chan struct{}) - sess.loginBufMu.Unlock() - - defer func() { - sess.loginBufMu.Lock() - close(sess.loginDone) - sess.loginBuf = nil - sess.loginBufMu.Unlock() - }() - - // Short delay to allow the shell to start and produce its initial prompt. - time.Sleep(500 * time.Millisecond) - - for i, step := range steps { - // Check session is still alive. - m.mu.RLock() - state := sess.Info.State - m.mu.RUnlock() - if state != models.SessionConnected { - return - } - - switch step.Type { - case "wait": - timeout := time.Duration(step.Timeout) * time.Second - if timeout <= 0 { - timeout = 10 * time.Second - } - if err := m.waitForOutput(sess, step.Value, timeout); err != nil { - m.emitEvent("session:debug", map[string]interface{}{ - "sessionID": sess.Info.ID, - "message": fmt.Sprintf("login script step %d timed out waiting for %q", i+1, step.Value), - }) - return - } - - case "send": - data := []byte(step.Value + "\n") - if err := m.WriteToSession(sess.Info.ID, data); err != nil { - m.emitEvent("session:debug", map[string]interface{}{ - "sessionID": sess.Info.ID, - "message": fmt.Sprintf("login script step %d failed to send: %v", i+1, err), - }) - return - } - // Small inter-step delay for the remote to process. - time.Sleep(200 * time.Millisecond) - - default: - // Unknown step type — skip. - } - } -} - -// waitForOutput polls the session's login buffer until the specified text -// appears or the timeout expires. -func (m *Manager) waitForOutput(sess *Session, pattern string, timeout time.Duration) error { - if pattern == "" { - return nil - } - - deadline := time.After(timeout) - ticker := time.NewTicker(100 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-deadline: - return fmt.Errorf("timeout waiting for %q", pattern) - case <-ticker.C: - sess.loginBufMu.Lock() - found := strings.Contains(string(sess.loginBuf), pattern) - if found { - // Clear the buffer up to the match so subsequent waits start fresh. - idx := strings.Index(string(sess.loginBuf), pattern) - sess.loginBuf = sess.loginBuf[idx+len(pattern):] - } - sess.loginBufMu.Unlock() - if found { - return nil - } - } - } -} - -// handleSessionClosed is called when a protocol client reports closure. -func (m *Manager) handleSessionClosed(sessionID string) { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if !ok || sess.closed { - m.mu.Unlock() - return - } - sess.Info.State = models.SessionDisconnected - m.mu.Unlock() - - if m.auditLog != nil { - m.auditLog.LogDisconnect(sessionID) - } - - // Check whether auto-reconnect is enabled for this session. - if m.shouldAutoReconnect(sess) { - m.scheduleReconnect(sess, sessionID) - return - } - - m.emitEvent("session:closed", map[string]interface{}{ - "sessionID": sessionID, - }) - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionDisconnected), - }) -} - -// shouldAutoReconnect checks whether a session is configured for automatic reconnection. -func (m *Manager) shouldAutoReconnect(sess *Session) bool { - if sess.profile == nil { - return false - } - switch sess.profile.Protocol { - case models.ProtocolSSH: - if sess.profile.SSH != nil { - return sess.profile.SSH.AutoReconnect - } - case models.ProtocolTelnet: - if sess.profile.Telnet != nil { - return sess.profile.Telnet.AutoReconnect - } - case models.ProtocolRAW: - if sess.profile.RAW != nil { - return sess.profile.RAW.AutoReconnect - } - case models.ProtocolIRC: - if sess.profile.IRC != nil { - return sess.profile.IRC.AutoReconnect - } - } - return false -} - -// maxReconnectAttempts returns the configured (or default) max retry count. -func (m *Manager) maxReconnectAttempts(sess *Session) int { - maxTries := 5 - switch sess.profile.Protocol { - case models.ProtocolSSH: - if sess.profile.SSH != nil && sess.profile.SSH.MaxReconnectTries > 0 { - maxTries = sess.profile.SSH.MaxReconnectTries - } - case models.ProtocolTelnet: - if sess.profile.Telnet != nil && sess.profile.Telnet.MaxReconnectTries > 0 { - maxTries = sess.profile.Telnet.MaxReconnectTries - } - case models.ProtocolRAW: - if sess.profile.RAW != nil { - maxTries = 5 - } - case models.ProtocolIRC: - if sess.profile.IRC != nil && sess.profile.IRC.MaxReconnectTries > 0 { - maxTries = sess.profile.IRC.MaxReconnectTries - } - } - return maxTries -} - -// scheduleReconnect initiates an exponential-backoff reconnection loop for a session. -func (m *Manager) scheduleReconnect(sess *Session, sessionID string) { - maxTries := m.maxReconnectAttempts(sess) - - m.mu.Lock() - sess.reconnectCount++ - attempt := sess.reconnectCount - if attempt > maxTries { - // Give up — too many attempts. - sess.Info.State = models.SessionDisconnected - m.mu.Unlock() - - m.emitEvent("session:closed", map[string]interface{}{ - "sessionID": sessionID, - }) - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionDisconnected), - "error": "auto-reconnect failed after maximum attempts", - }) - return - } - - sess.Info.State = models.SessionReconnecting - sess.Info.Error = "" - - // Cancel any previous reconnect timer. - if sess.cancelReconn != nil { - sess.cancelReconn() - } - ctx, cancel := context.WithCancel(context.Background()) - sess.cancelReconn = cancel - m.mu.Unlock() - - // Exponential backoff: 1s, 2s, 4s, 8s, 16s, capped at 30s. - delaySec := 1 << (attempt - 1) // 2^(attempt-1) - if delaySec > 30 { - delaySec = 30 - } - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionReconnecting), - "attempt": attempt, - "maxRetries": maxTries, - "delaySec": delaySec, - }) - - go func() { - select { - case <-ctx.Done(): - return - case <-time.After(time.Duration(delaySec) * time.Second): - } - - // Clear old clients before reconnecting. - m.mu.Lock() - if current, ok := m.sessions[sessionID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - return - } - sess.sshClient = nil - sess.telnetClient = nil - sess.rawClient = nil - sess.ircClient = nil - sess.sftpClient = nil - sess.Info.State = models.SessionConnecting - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionConnecting), - }) - - var err error - switch sess.profile.Protocol { - case models.ProtocolSSH: - err = m.connectSSH(sess, m.buildInteractiveSecretCallback(ctx, sessionID)) - case models.ProtocolTelnet: - err = m.connectTelnet(sess) - case models.ProtocolRAW: - err = m.connectRAW(sess) - case models.ProtocolIRC: - err = m.connectIRC(sess, m.buildInteractiveSecretCallback(ctx, sessionID)) - default: - err = fmt.Errorf("unsupported protocol for reconnect: %s", sess.profile.Protocol) - } - - if err != nil { - m.mu.Lock() - if current, ok := m.sessions[sessionID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - m.closeSession(sess) - return - } - sess.Info.State = models.SessionError - sess.Info.Error = err.Error() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionError), - "error": err.Error(), - }) - - // Schedule the next attempt. - m.scheduleReconnect(sess, sessionID) - return - } - - // Success — reset the counter. - m.mu.Lock() - if current, ok := m.sessions[sessionID]; !ok || current != sess || sess.closed { - m.mu.Unlock() - m.closeSession(sess) - return - } - sess.reconnectCount = 0 - sess.Info.State = models.SessionConnected - sess.Info.ConnectedAt = time.Now() - m.mu.Unlock() - - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionConnected), - }) - }() -} - -// buildInteractiveSecretCallback creates a getSecret callback that prompts -// the user via Wails events. Used during auto-reconnect flows where the -// original callback is no longer available. -func (m *Manager) buildInteractiveSecretCallback(ctx context.Context, sessionID string) func(prompt string) (string, error) { - return func(prompt string) (string, error) { - requestID := uuid.New().String() - - ch := make(chan secretPromptResponse, 1) - m.secretMu.Lock() - if m.secretChans == nil { - m.secretChans = make(map[string]chan secretPromptResponse) - } - m.secretChans[requestID] = ch - m.secretMu.Unlock() - defer func() { - m.secretMu.Lock() - delete(m.secretChans, requestID) - m.secretMu.Unlock() - }() - - m.emitEvent("session:secret:prompt", map[string]interface{}{ - "requestID": requestID, - "sessionID": sessionID, - "title": "Reconnection Credentials", - "message": prompt, - }) - - select { - case response := <-ch: - if response.cancelled { - return "", fmt.Errorf("credential prompt cancelled during reconnect") - } - return response.value, nil - case <-ctx.Done(): - return "", fmt.Errorf("credential prompt cancelled during reconnect: %w", ctx.Err()) - case <-time.After(60 * time.Second): - return "", fmt.Errorf("credential prompt timed out during reconnect") - } - } -} - -func (m *Manager) registerSecretResponseListener() { - ctx := m.ctx - if ctx == nil { - return - } - - m.secretMu.Lock() - if m.secretChans == nil { - m.secretChans = make(map[string]chan secretPromptResponse) - } - if m.secretListenerRegistered { - m.secretMu.Unlock() - return - } - m.secretListenerRegistered = true - m.secretMu.Unlock() - - cancel := wailsRuntime.EventsOn(ctx, "session:secret:response", m.handleSecretResponse) - m.secretMu.Lock() - m.secretListenerCancel = cancel - m.secretMu.Unlock() -} - -func (m *Manager) handleSecretResponse(optionalData ...interface{}) { - if len(optionalData) == 0 { - return - } - data, ok := optionalData[0].(map[string]interface{}) - if !ok { - return - } - requestID, _ := data["requestID"].(string) - value, _ := data["value"].(string) - cancelled, _ := data["cancelled"].(bool) - m.RespondSecret(requestID, value, cancelled) -} - -// RespondSecret supplies a credential prompt response for reconnect prompts in -// non-Wails hosts. -func (m *Manager) RespondSecret(requestID string, value string, cancelled bool) { - m.secretMu.Lock() - ch, exists := m.secretChans[requestID] - m.secretMu.Unlock() - - if exists { - select { - case ch <- secretPromptResponse{value: value, cancelled: cancelled}: - default: - } - } -} - -// CancelReconnect cancels any in-progress auto-reconnect for a session. -func (m *Manager) CancelReconnect(sessionID string) { - m.mu.Lock() - sess, ok := m.sessions[sessionID] - if ok && sess.cancelReconn != nil { - sess.cancelReconn() - sess.cancelReconn = nil - sess.reconnectCount = 0 - sess.Info.State = models.SessionDisconnected - } - m.mu.Unlock() - - if ok { - m.emitEvent("session:state", map[string]interface{}{ - "sessionID": sessionID, - "state": string(models.SessionDisconnected), - }) - } -} - -// createTabName generates a default tab name from the profile's display name -// and host. -func (m *Manager) createTabName(profile *models.Profile) string { - if profile.DisplayName != "" { - return profile.DisplayName - } - if profile.Username != "" { - return fmt.Sprintf("%s@%s", profile.Username, profile.Host) - } - return profile.Host -} - -// emitEvent sends an event to the Wails frontend via the runtime context. -func (m *Manager) emitEvent(eventName string, data interface{}) { - m.mu.RLock() - sink := m.eventSink - m.mu.RUnlock() - if sink != nil { - sink(eventName, data) - } - if m.ctx == nil { - return - } - wailsRuntime.EventsEmit(m.ctx, eventName, data) -} diff --git a/internal/sessions/manager_test.go b/internal/sessions/manager_test.go deleted file mode 100644 index 8f025ee..0000000 --- a/internal/sessions/manager_test.go +++ /dev/null @@ -1,507 +0,0 @@ -package sessions - -import ( - "context" - "crypto/ed25519" - "crypto/rand" - "encoding/base64" - "fmt" - "os" - "path/filepath" - "runtime" - "strings" - "sync" - "testing" - "time" - - "github.com/andydixon/link/internal/audit" - "github.com/andydixon/link/internal/models" - "github.com/andydixon/link/internal/storage" - "golang.org/x/crypto/ssh" -) - -// --------------------------------------------------------------------------- -// NewManager creates with empty sessions map -// --------------------------------------------------------------------------- - -func TestNewManager(t *testing.T) { - // NewManager requires backing services; pass nil for lightweight tests - // where no actual connections are made. - mgr := NewManager(nil, nil, nil, nil, nil) - if mgr == nil { - t.Fatal("NewManager() returned nil") - } - if mgr.sessions == nil { - t.Fatal("expected sessions map to be initialised") - } - if len(mgr.sessions) != 0 { - t.Errorf("expected empty sessions map, got %d entries", len(mgr.sessions)) - } -} - -// --------------------------------------------------------------------------- -// ListSessions on an empty manager -// --------------------------------------------------------------------------- - -func TestListSessionsEmpty(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - sessions := mgr.ListSessions() - if len(sessions) != 0 { - t.Errorf("expected 0 sessions, got %d", len(sessions)) - } -} - -// --------------------------------------------------------------------------- -// createTabName logic -// --------------------------------------------------------------------------- - -func TestCreateTabName(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - - tests := []struct { - name string - profile *models.Profile - want string - }{ - { - name: "uses display name when set", - profile: &models.Profile{ - DisplayName: "Production DB", - Host: "db.prod.com", - Username: "admin", - }, - want: "Production DB", - }, - { - name: "falls back to user@host when no display name", - profile: &models.Profile{ - DisplayName: "", - Host: "server.local", - Username: "root", - }, - want: "root@server.local", - }, - { - name: "falls back to host only when no display name or username", - profile: &models.Profile{ - DisplayName: "", - Host: "switch.local", - Username: "", - }, - want: "switch.local", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got := mgr.createTabName(tt.profile) - if got != tt.want { - t.Errorf("createTabName() = %q, want %q", got, tt.want) - } - }) - } -} - -func TestMergeIRCRoomsPreservesConfiguredAndRememberedOrder(t *testing.T) { - got := mergeIRCRooms( - []string{" #configured ", "#Shared"}, - []string{"#shared", "#last-session", "", "#CONFIGURED"}, - ) - if joined := strings.Join(got, ","); joined != "#configured,#Shared,#last-session" { - t.Fatalf("mergeIRCRooms() = %q, want configured rooms followed by new remembered rooms", joined) - } -} - -// --------------------------------------------------------------------------- -// RenameSession on non-existent session -// --------------------------------------------------------------------------- - -func TestRenameSessionNotFound(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - err := mgr.RenameSession("nonexistent-id", "New Name") - if err == nil { - t.Error("expected error when renaming non-existent session") - } -} - -// --------------------------------------------------------------------------- -// PinSession on non-existent session -// --------------------------------------------------------------------------- - -func TestPinSessionNotFound(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - err := mgr.PinSession("nonexistent-id", true) - if err == nil { - t.Error("expected error when pinning non-existent session") - } -} - -// --------------------------------------------------------------------------- -// GetSession on non-existent session -// --------------------------------------------------------------------------- - -func TestGetSessionNotFound(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - _, err := mgr.GetSession("nonexistent-id") - if err == nil { - t.Error("expected error when getting non-existent session") - } -} - -// --------------------------------------------------------------------------- -// Disconnect on non-existent session -// --------------------------------------------------------------------------- - -func TestDisconnectNotFound(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - err := mgr.Disconnect("nonexistent-id") - if err == nil { - t.Error("expected error when disconnecting non-existent session") - } -} - -func TestDisconnectRemovesSessionAndReleasesReferences(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - sess := &Session{ - Info: models.SessionInfo{ - ID: "session-1", - State: models.SessionConnected, - }, - profile: &models.Profile{ID: "profile-1"}, - } - mgr.sessions[sess.Info.ID] = sess - - if err := mgr.Disconnect(sess.Info.ID); err != nil { - t.Fatalf("Disconnect() error = %v", err) - } - if got := len(mgr.ListSessions()); got != 0 { - t.Fatalf("ListSessions() returned %d sessions after disconnect, want 0", got) - } - if !sess.closed { - t.Fatal("disconnected session was not marked closed") - } - if sess.profile != nil { - t.Fatal("disconnected session retained its profile") - } -} - -func TestRepeatedDisconnectDoesNotRetainSessions(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - - for i := 0; i < 1000; i++ { - id := fmt.Sprintf("session-%d", i) - mgr.sessions[id] = &Session{ - Info: models.SessionInfo{ - ID: id, - State: models.SessionConnected, - }, - } - if err := mgr.Disconnect(id); err != nil { - t.Fatalf("Disconnect(%q) error = %v", id, err) - } - } - - if got := len(mgr.ListSessions()); got != 0 { - t.Fatalf("ListSessions() retained %d explicitly closed sessions, want 0", got) - } -} - -// --------------------------------------------------------------------------- -// CloseAll on empty manager does not panic -// --------------------------------------------------------------------------- - -func TestCloseAllEmpty(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - // Should not panic. - mgr.CloseAll() - if len(mgr.ListSessions()) != 0 { - t.Error("expected 0 sessions after CloseAll") - } -} - -func TestTerminalDimension(t *testing.T) { - if got := terminalDimension(0, 80); got != 80 { - t.Fatalf("terminalDimension(0, 80) = %d, want 80", got) - } - if got := terminalDimension(132, 80); got != 132 { - t.Fatalf("terminalDimension(132, 80) = %d, want 132", got) - } - if got := terminalDimension(100000, 80); got != ^uint16(0) { - t.Fatalf("terminalDimension(100000, 80) = %d, want %d", got, ^uint16(0)) - } -} - -func TestRestartLocalReusesSessionAndAppliesRequestedSize(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("local PTY test requires a Unix shell") - } - dir := t.TempDir() - script := filepath.Join(dir, "local-shell.sh") - if err := os.WriteFile(script, []byte("#!/bin/sh\nstty size\n"), 0o700); err != nil { - t.Fatal(err) - } - - auditStore := &sessionAuditStore{} - mgr := NewManager(nil, nil, nil, audit.NewLogger(auditStore), nil) - var outputMu sync.Mutex - var output strings.Builder - disconnected := make(chan string, 2) - mgr.SetEventSink(func(name string, payload interface{}) { - values, ok := payload.(map[string]interface{}) - if !ok { - return - } - if name == "session:data" { - encoded, _ := values["data"].(string) - decoded, _ := base64.StdEncoding.DecodeString(encoded) - outputMu.Lock() - _, _ = output.Write(decoded) - outputMu.Unlock() - } - if name == "session:state" && values["state"] == string(models.SessionDisconnected) { - disconnected <- values["sessionID"].(string) - } - }) - - sessionID, err := mgr.ConnectLocal(script) - if err != nil { - t.Fatalf("ConnectLocal() error = %v", err) - } - waitForDisconnectedSession(t, disconnected, sessionID) - - if err := mgr.RestartLocal(sessionID, 132, 43); err != nil { - t.Fatalf("RestartLocal() error = %v", err) - } - waitForDisconnectedSession(t, disconnected, sessionID) - - info := mgr.GetSessionInfo(sessionID) - if info == nil || info.ID != sessionID || info.State != models.SessionDisconnected { - t.Fatalf("session after restart = %#v, want same disconnected session", info) - } - outputMu.Lock() - got := strings.ReplaceAll(output.String(), "\r", "") - outputMu.Unlock() - if !strings.Contains(got, "43 132\n") { - t.Fatalf("local output %q does not contain restarted size 43 132", got) - } -} - -func waitForDisconnectedSession(t *testing.T, states <-chan string, want string) { - t.Helper() - select { - case got := <-states: - if got != want { - t.Fatalf("disconnected session = %q, want %q", got, want) - } - case <-time.After(5 * time.Second): - t.Fatalf("timed out waiting for session %q to disconnect", want) - } -} - -type sessionAuditStore struct { - mu sync.Mutex - entries []*models.AuditEntry -} - -func (s *sessionAuditStore) AddAuditEntry(entry *models.AuditEntry) error { - s.mu.Lock() - s.entries = append(s.entries, entry) - s.mu.Unlock() - return nil -} - -func (s *sessionAuditStore) ListAuditEntries(limit int) ([]*models.AuditEntry, error) { - s.mu.Lock() - defer s.mu.Unlock() - return append([]*models.AuditEntry(nil), s.entries...), nil -} - -func TestHostKeyCallbackAcceptNewStoresHost(t *testing.T) { - store := newSessionTestStore(t) - mgr := NewManager(store, nil, nil, nil, nil) - callback := mgr.hostKeyCallback("session-1", models.HostKeyAcceptNew) - - key := testPublicKey(t) - if err := callback("jump.example:2222", nil, key); err != nil { - t.Fatalf("HostKeyCallback() error = %v", err) - } - - saved, err := store.GetKnownHost("jump.example", 2222) - if err != nil { - t.Fatalf("GetKnownHost() error = %v", err) - } - if saved == nil { - t.Fatal("expected host key to be saved") - } - if saved.Host != "jump.example" { - t.Fatalf("saved host = %q, want jump.example", saved.Host) - } - if saved.Port != 2222 { - t.Fatalf("saved port = %d, want 2222", saved.Port) - } -} - -func TestHostKeyCallbackStrictRejectsUnknownHost(t *testing.T) { - mgr := NewManager(newSessionTestStore(t), nil, nil, nil, nil) - callback := mgr.hostKeyCallback("session-1", models.HostKeyStrict) - - err := callback("unknown.example:22", nil, testPublicKey(t)) - if err == nil { - t.Fatal("expected strict policy to reject unknown host") - } -} - -func TestHandleHostKeyResponseRoutesByRequestID(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - ch := make(chan bool, 1) - mgr.hostKeyChans = map[string]chan bool{"request-1": ch} - - mgr.handleHostKeyResponse(map[string]interface{}{ - "requestID": "request-1", - "accepted": true, - }) - - select { - case accepted := <-ch: - if !accepted { - t.Fatal("accepted = false, want true") - } - default: - t.Fatal("expected host key response to be routed") - } -} - -func TestHandleHostKeyResponseIgnoresOtherRequestIDs(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - ch := make(chan bool, 1) - mgr.hostKeyChans = map[string]chan bool{"request-1": ch} - - mgr.handleHostKeyResponse(map[string]interface{}{ - "requestID": "request-2", - "accepted": true, - }) - - select { - case <-ch: - t.Fatal("unexpected host key response routed") - default: - } -} - -func TestHandleSecretResponseRoutesByRequestID(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - ch := make(chan secretPromptResponse, 1) - mgr.secretChans = map[string]chan secretPromptResponse{"request-1": ch} - - mgr.handleSecretResponse(map[string]interface{}{ - "requestID": "request-1", - "value": "secret", - "cancelled": false, - }) - - select { - case response := <-ch: - if response.value != "secret" || response.cancelled { - t.Fatalf("response = %#v, want non-cancelled secret", response) - } - default: - t.Fatal("expected secret response to be routed") - } -} - -func TestInteractiveSecretPromptIsReleasedByReconnectCancellation(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - prompts := make(chan map[string]interface{}, 1) - mgr.SetEventSink(func(name string, data interface{}) { - if name == "session:secret:prompt" { - prompts <- data.(map[string]interface{}) - } - }) - - ctx, cancel := context.WithCancel(context.Background()) - result := make(chan error, 1) - go func() { - _, err := mgr.buildInteractiveSecretCallback(ctx, "session-1")("Password") - result <- err - }() - - select { - case <-prompts: - case <-time.After(time.Second): - t.Fatal("timed out waiting for secret prompt") - } - cancel() - - select { - case err := <-result: - if err == nil || !strings.Contains(err.Error(), "cancelled") { - t.Fatalf("callback error = %v, want cancellation", err) - } - case <-time.After(time.Second): - t.Fatal("secret prompt did not stop after reconnect cancellation") - } - - mgr.secretMu.Lock() - pending := len(mgr.secretChans) - mgr.secretMu.Unlock() - if pending != 0 { - t.Fatalf("pending secret prompts = %d, want 0", pending) - } -} - -func TestInteractiveSecretPromptHonoursFrontendCancellation(t *testing.T) { - mgr := NewManager(nil, nil, nil, nil, nil) - prompts := make(chan map[string]interface{}, 1) - mgr.SetEventSink(func(name string, data interface{}) { - if name == "session:secret:prompt" { - prompts <- data.(map[string]interface{}) - } - }) - - result := make(chan error, 1) - go func() { - _, err := mgr.buildInteractiveSecretCallback(context.Background(), "session-1")("Password") - result <- err - }() - - var requestID string - select { - case prompt := <-prompts: - requestID, _ = prompt["requestID"].(string) - case <-time.After(time.Second): - t.Fatal("timed out waiting for secret prompt") - } - mgr.RespondSecret(requestID, "", true) - - select { - case err := <-result: - if err == nil || !strings.Contains(err.Error(), "cancelled") { - t.Fatalf("callback error = %v, want frontend cancellation", err) - } - case <-time.After(time.Second): - t.Fatal("secret prompt did not stop after frontend cancellation") - } -} - -func testPublicKey(t *testing.T) ssh.PublicKey { - t.Helper() - pub, _, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatalf("GenerateKey() error = %v", err) - } - key, err := ssh.NewPublicKey(pub) - if err != nil { - t.Fatalf("NewPublicKey() error = %v", err) - } - return key -} - -func newSessionTestStore(t *testing.T) *storage.Store { - t.Helper() - store, err := storage.NewStore(filepath.Join(t.TempDir(), "test.db")) - if err != nil { - t.Fatalf("NewStore() error = %v", err) - } - t.Cleanup(func() { store.Close() }) - return store -} diff --git a/internal/sftpclient/sftpclient.go b/internal/sftpclient/sftpclient.go deleted file mode 100644 index b4f637d..0000000 --- a/internal/sftpclient/sftpclient.go +++ /dev/null @@ -1,341 +0,0 @@ -// Package sftpclient provides an SFTP file browser and transfer capability -// over an established SSH connection. It wraps github.com/pkg/sftp to provide -// directory listing, upload, download, rename, delete, and mkdir operations -// that the frontend can call through the Wails API. -// -// The client is created per-session — each SSH session can have at most one -// SFTP subsystem active. -package sftpclient - -import ( - "bytes" - "fmt" - "io" - "os" - "path" - "sort" - "strings" - "sync" - "time" - - "github.com/pkg/sftp" - "golang.org/x/crypto/ssh" -) - -// FileEntry represents a single file or directory in a remote listing. -type FileEntry struct { - Name string `json:"name"` - Path string `json:"path"` // full remote path - Size int64 `json:"size"` - IsDir bool `json:"isDir"` - Mode string `json:"mode"` // Unix permission string, e.g. "-rwxr-xr-x" - ModifiedAt int64 `json:"modifiedAt"` // Unix epoch milliseconds - Owner string `json:"owner"` // best-effort UID -} - -// TransferProgress reports the state of a file transfer. -type TransferProgress struct { - Transferred int64 `json:"transferred"` // bytes transferred so far - Total int64 `json:"total"` // total bytes (0 if unknown) - Path string `json:"path"` // file being transferred - Done bool `json:"done"` - Error string `json:"error,omitempty"` -} - -// Client wraps an SFTP session. -type Client struct { - mu sync.Mutex - sftp *sftp.Client - closed bool -} - -// NewClient creates an SFTP subsystem over an existing SSH connection. -func NewClient(sshConn *ssh.Client) (*Client, error) { - sc, err := sftp.NewClient(sshConn) - if err != nil { - return nil, fmt.Errorf("sftpclient: failed to start SFTP subsystem: %w", err) - } - return &Client{sftp: sc}, nil -} - -// Close shuts down the SFTP subsystem. -func (c *Client) Close() error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed { - return nil - } - c.closed = true - if c.sftp != nil { - return c.sftp.Close() - } - return nil -} - -// List returns the contents of a remote directory. -func (c *Client) List(remotePath string) ([]FileEntry, error) { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed || c.sftp == nil { - return nil, fmt.Errorf("sftpclient: client is closed") - } - - entries, err := c.sftp.ReadDir(remotePath) - if err != nil { - return nil, fmt.Errorf("sftpclient: failed to list %s: %w", remotePath, err) - } - - result := make([]FileEntry, 0, len(entries)) - for _, e := range entries { - fe := FileEntry{ - Name: e.Name(), - Path: path.Join(remotePath, e.Name()), - Size: e.Size(), - IsDir: e.IsDir(), - Mode: e.Mode().String(), - ModifiedAt: e.ModTime().UnixMilli(), - } - result = append(result, fe) - } - - // Sort: directories first, then alphabetically. - sort.Slice(result, func(i, j int) bool { - if result[i].IsDir != result[j].IsDir { - return result[i].IsDir - } - return strings.ToLower(result[i].Name) < strings.ToLower(result[j].Name) - }) - - return result, nil -} - -// Stat returns information about a single remote path. -func (c *Client) Stat(remotePath string) (*FileEntry, error) { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed || c.sftp == nil { - return nil, fmt.Errorf("sftpclient: client is closed") - } - - info, err := c.sftp.Stat(remotePath) - if err != nil { - return nil, fmt.Errorf("sftpclient: stat failed for %s: %w", remotePath, err) - } - - return &FileEntry{ - Name: info.Name(), - Path: remotePath, - Size: info.Size(), - IsDir: info.IsDir(), - Mode: info.Mode().String(), - ModifiedAt: info.ModTime().UnixMilli(), - }, nil -} - -// Mkdir creates a remote directory. -func (c *Client) Mkdir(remotePath string) error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed || c.sftp == nil { - return fmt.Errorf("sftpclient: client is closed") - } - - return c.sftp.Mkdir(remotePath) -} - -// MkdirAll creates a remote directory and all parents. -func (c *Client) MkdirAll(remotePath string) error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed || c.sftp == nil { - return fmt.Errorf("sftpclient: client is closed") - } - - return c.sftp.MkdirAll(remotePath) -} - -// Remove deletes a remote file or empty directory. -func (c *Client) Remove(remotePath string) error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed || c.sftp == nil { - return fmt.Errorf("sftpclient: client is closed") - } - - return c.sftp.Remove(remotePath) -} - -// Rename moves or renames a remote file/directory. -func (c *Client) Rename(oldPath, newPath string) error { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed || c.sftp == nil { - return fmt.Errorf("sftpclient: client is closed") - } - - return c.sftp.Rename(oldPath, newPath) -} - -// Download copies a remote file to a local path. The onProgress callback -// is invoked periodically with transfer progress (may be nil). -func (c *Client) Download(remotePath, localPath string, onProgress func(TransferProgress)) error { - c.mu.Lock() - if c.closed || c.sftp == nil { - c.mu.Unlock() - return fmt.Errorf("sftpclient: client is closed") - } - - // Get file size for progress reporting. - info, err := c.sftp.Stat(remotePath) - if err != nil { - c.mu.Unlock() - return fmt.Errorf("sftpclient: stat failed: %w", err) - } - totalSize := info.Size() - - remote, err := c.sftp.Open(remotePath) - if err != nil { - c.mu.Unlock() - return fmt.Errorf("sftpclient: failed to open remote file: %w", err) - } - c.mu.Unlock() - defer remote.Close() - - local, err := os.Create(localPath) - if err != nil { - return fmt.Errorf("sftpclient: failed to create local file: %w", err) - } - defer local.Close() - - return c.copyWithProgress(remote, local, totalSize, remotePath, onProgress) -} - -// Upload copies a local file to a remote path. The onProgress callback -// is invoked periodically with transfer progress (may be nil). -func (c *Client) Upload(localPath, remotePath string, onProgress func(TransferProgress)) error { - local, err := os.Open(localPath) - if err != nil { - return fmt.Errorf("sftpclient: failed to open local file: %w", err) - } - defer local.Close() - - info, err := local.Stat() - if err != nil { - return fmt.Errorf("sftpclient: failed to stat local file: %w", err) - } - totalSize := info.Size() - - c.mu.Lock() - if c.closed || c.sftp == nil { - c.mu.Unlock() - return fmt.Errorf("sftpclient: client is closed") - } - remote, err := c.sftp.Create(remotePath) - if err != nil { - c.mu.Unlock() - return fmt.Errorf("sftpclient: failed to create remote file: %w", err) - } - c.mu.Unlock() - defer remote.Close() - - return c.copyWithProgress(local, remote, totalSize, remotePath, onProgress) -} - -// UploadBytes writes in-memory data to a remote file path. -func (c *Client) UploadBytes(remotePath string, data []byte, onProgress func(TransferProgress)) error { - c.mu.Lock() - if c.closed || c.sftp == nil { - c.mu.Unlock() - return fmt.Errorf("sftpclient: client is closed") - } - remote, err := c.sftp.Create(remotePath) - if err != nil { - c.mu.Unlock() - return fmt.Errorf("sftpclient: failed to create remote file: %w", err) - } - c.mu.Unlock() - defer remote.Close() - - reader := bytes.NewReader(data) - return c.copyWithProgress(reader, remote, int64(len(data)), remotePath, onProgress) -} - -// CreateWriter creates or truncates a remote file and returns a streaming -// writer for callers that already manage progress and chunking. -func (c *Client) CreateWriter(remotePath string) (io.WriteCloser, error) { - c.mu.Lock() - defer c.mu.Unlock() - if c.closed || c.sftp == nil { - return nil, fmt.Errorf("sftpclient: client is closed") - } - remote, err := c.sftp.Create(remotePath) - if err != nil { - return nil, fmt.Errorf("sftpclient: failed to create remote file: %w", err) - } - return remote, nil -} - -// Pwd returns the current working directory on the remote host. -func (c *Client) Pwd() (string, error) { - c.mu.Lock() - defer c.mu.Unlock() - - if c.closed || c.sftp == nil { - return "", fmt.Errorf("sftpclient: client is closed") - } - - return c.sftp.Getwd() -} - -// copyWithProgress copies from src to dst, reporting progress periodically. -func (c *Client) copyWithProgress(src io.Reader, dst io.Writer, totalSize int64, filePath string, onProgress func(TransferProgress)) error { - buf := make([]byte, 32*1024) // 32 KB buffer - var transferred int64 - lastReport := time.Now() - - for { - n, err := src.Read(buf) - if n > 0 { - nw, wErr := dst.Write(buf[:n]) - if wErr != nil { - return fmt.Errorf("sftpclient: write failed: %w", wErr) - } - transferred += int64(nw) - - // Report progress at most every 100ms. - if onProgress != nil && time.Since(lastReport) > 100*time.Millisecond { - onProgress(TransferProgress{ - Transferred: transferred, - Total: totalSize, - Path: filePath, - }) - lastReport = time.Now() - } - } - if err == io.EOF { - break - } - if err != nil { - return fmt.Errorf("sftpclient: read failed: %w", err) - } - } - - // Final progress report. - if onProgress != nil { - onProgress(TransferProgress{ - Transferred: transferred, - Total: totalSize, - Path: filePath, - Done: true, - }) - } - - return nil -} diff --git a/internal/sshclient/agent.go b/internal/sshclient/agent.go deleted file mode 100644 index 8de7d5c..0000000 --- a/internal/sshclient/agent.go +++ /dev/null @@ -1,80 +0,0 @@ -// Package sshclient — SSH agent integration. -// -// This file provides SSH agent authentication and agent forwarding. -// It connects to the local SSH agent via the SSH_AUTH_SOCK environment -// variable and can both use agent keys for authentication and forward -// the agent channel to the remote host (allowing the remote session -// to authenticate further hops using local keys). -package sshclient - -import ( - "fmt" - "net" - "os" - - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" -) - -// connectAgent connects to the local SSH agent via SSH_AUTH_SOCK. -// Returns the agent and a cleanup function, or an error if unavailable. -func connectAgent() (agent.Agent, func(), error) { - sock := os.Getenv("SSH_AUTH_SOCK") - if sock == "" { - return nil, nil, fmt.Errorf("SSH_AUTH_SOCK not set — SSH agent unavailable") - } - - conn, err := net.Dial("unix", sock) - if err != nil { - return nil, nil, fmt.Errorf("unable to connect to SSH agent at %s: %w", sock, err) - } - - ag := agent.NewClient(conn) - cleanup := func() { conn.Close() } - return ag, cleanup, nil -} - -// agentAuthMethod returns an ssh.AuthMethod that delegates authentication -// to the running SSH agent. Returns nil if the agent is unavailable. -func agentAuthMethod() (ssh.AuthMethod, func(), error) { - ag, cleanup, err := connectAgent() - if err != nil { - return nil, nil, err - } - - signers, err := ag.Signers() - if err != nil { - cleanup() - return nil, nil, fmt.Errorf("SSH agent: failed to list keys: %w", err) - } - - if len(signers) == 0 { - cleanup() - return nil, nil, fmt.Errorf("SSH agent: no keys loaded") - } - - return ssh.PublicKeysCallback(ag.Signers), cleanup, nil -} - -// requestAgentForwarding sets up agent forwarding on the SSH session so that -// the remote host can use local agent keys for further authentication. -// This is equivalent to `ssh -A`. -func requestAgentForwarding(sshClient *ssh.Client, session *ssh.Session) error { - if err := agent.RequestAgentForwarding(session); err != nil { - return fmt.Errorf("requesting agent forwarding: %w", err) - } - - ag, cleanup, err := connectAgent() - if err != nil { - return err - } - - // ForwardToAgent runs in a background goroutine and serves agent - // requests from the remote side. It stops when the SSH connection closes. - go func() { - defer cleanup() - agent.ForwardToAgent(sshClient, ag) - }() - - return nil -} diff --git a/internal/sshclient/hostkey.go b/internal/sshclient/hostkey.go deleted file mode 100644 index 57a0b29..0000000 --- a/internal/sshclient/hostkey.go +++ /dev/null @@ -1,162 +0,0 @@ -package sshclient - -import ( - "crypto/sha256" - "encoding/base64" - "fmt" - "net" - "time" - - "github.com/andydixon/link/internal/models" - "golang.org/x/crypto/ssh" -) - -// HostKeyStore is the interface for known-host persistence. -// Implementations handle the underlying storage mechanism (e.g. SQLite, file). -type HostKeyStore interface { - GetKnownHost(host string, port int) (*models.KnownHost, error) - SaveKnownHost(kh *models.KnownHost) error -} - -// HostKeyVerifier handles SSH host key verification against the known-hosts -// store. It supports four policies: -// - strict: reject unknown or changed keys -// - ask: prompt the user for unknown keys (via callback) -// - accept-new: automatically accept new keys, reject changed keys -// - insecure: accept all keys (for testing only — clearly warned) -type HostKeyVerifier struct { - store HostKeyStore - policy models.HostKeyPolicy - onPrompt func(host string, keyType string, fingerprint string) (bool, error) -} - -// NewHostKeyVerifier creates a verifier with the given store and policy. -func NewHostKeyVerifier(store HostKeyStore, policy models.HostKeyPolicy) *HostKeyVerifier { - return &HostKeyVerifier{ - store: store, - policy: policy, - } -} - -// SetPromptHandler sets the callback used in "ask" mode to prompt the user -// for acceptance of an unknown host key. -func (v *HostKeyVerifier) SetPromptHandler(fn func(string, string, string) (bool, error)) { - v.onPrompt = fn -} - -// HostKeyCallback returns an ssh.HostKeyCallback suitable for use with -// ssh.ClientConfig. The behaviour depends on the configured policy. -func (v *HostKeyVerifier) HostKeyCallback() ssh.HostKeyCallback { - return func(hostname string, remote net.Addr, key ssh.PublicKey) error { - host, port := extractHostPort(hostname, remote) - keyType := key.Type() - fingerprint := computeFingerprint(key) - - stored, err := v.store.GetKnownHost(host, port) - if err != nil { - return fmt.Errorf("failed to query known hosts: %w", err) - } - - switch v.policy { - case models.HostKeyInsecure: - return v.saveIfNew(stored, host, port, keyType, fingerprint, key) - - case models.HostKeyAcceptNew: - if stored == nil { - return v.saveHost(host, port, keyType, fingerprint, key) - } - return v.verifyStored(stored, fingerprint, host) - - case models.HostKeyAsk: - if stored == nil { - return v.promptAndSave(host, port, keyType, fingerprint, key) - } - return v.verifyStored(stored, fingerprint, host) - - case models.HostKeyStrict: - if stored == nil { - return fmt.Errorf("host key for %s:%d is not known (policy: strict)", host, port) - } - return v.verifyStored(stored, fingerprint, host) - - default: - return fmt.Errorf("unknown host key policy: %s", v.policy) - } - } -} - -// verifyStored checks that the presented fingerprint matches the stored one. -func (v *HostKeyVerifier) verifyStored(stored *models.KnownHost, fingerprint, host string) error { - if stored.Fingerprint != fingerprint { - return fmt.Errorf( - "host key for %s has changed! stored fingerprint: %s, received: %s — possible MITM attack", - host, stored.Fingerprint, fingerprint, - ) - } - stored.LastSeen = time.Now() - _ = v.store.SaveKnownHost(stored) - return nil -} - -// promptAndSave asks the user whether to accept an unknown host key. -func (v *HostKeyVerifier) promptAndSave(host string, port int, keyType, fingerprint string, key ssh.PublicKey) error { - if v.onPrompt == nil { - return fmt.Errorf("host key for %s:%d is unknown and no prompt handler is set (policy: ask)", host, port) - } - - accepted, err := v.onPrompt(host, keyType, fingerprint) - if err != nil { - return fmt.Errorf("host key prompt failed: %w", err) - } - if !accepted { - return fmt.Errorf("host key for %s:%d rejected by user", host, port) - } - - return v.saveHost(host, port, keyType, fingerprint, key) -} - -// saveIfNew stores the host key only when no prior record exists. -func (v *HostKeyVerifier) saveIfNew(stored *models.KnownHost, host string, port int, keyType, fingerprint string, key ssh.PublicKey) error { - if stored == nil { - return v.saveHost(host, port, keyType, fingerprint, key) - } - stored.LastSeen = time.Now() - _ = v.store.SaveKnownHost(stored) - return nil -} - -// saveHost persists a new known-host record. -func (v *HostKeyVerifier) saveHost(host string, port int, keyType, fingerprint string, key ssh.PublicKey) error { - now := time.Now() - kh := &models.KnownHost{ - Host: host, - Port: port, - KeyType: keyType, - Fingerprint: fingerprint, - PublicKey: base64.StdEncoding.EncodeToString(key.Marshal()), - FirstSeen: now, - LastSeen: now, - Trusted: true, - } - return v.store.SaveKnownHost(kh) -} - -// computeFingerprint returns the SHA-256 fingerprint of a public key in -// the standard "SHA256:" format. -func computeFingerprint(key ssh.PublicKey) string { - hash := sha256.Sum256(key.Marshal()) - return "SHA256:" + base64.RawStdEncoding.EncodeToString(hash[:]) -} - -// extractHostPort splits the hostname and port from the SSH callback arguments. -func extractHostPort(hostname string, remote net.Addr) (string, int) { - host, portStr, err := net.SplitHostPort(hostname) - if err != nil { - host, portStr, _ = net.SplitHostPort(remote.String()) - } - port := 22 - if portStr != "" { - fmt.Sscanf(portStr, "%d", &port) - } - return host, port -} diff --git a/internal/sshclient/portforward.go b/internal/sshclient/portforward.go deleted file mode 100644 index 3f79833..0000000 --- a/internal/sshclient/portforward.go +++ /dev/null @@ -1,269 +0,0 @@ -// Package sshclient — port forwarding support. -// -// This file implements local and remote SSH port forwarding (tunnelling). -// Local forwarding listens on a local address and pipes connections through -// the SSH channel to a remote destination. Remote forwarding requests the -// SSH server to listen on a remote address and pipes accepted connections -// back to a local destination. -package sshclient - -import ( - "fmt" - "io" - "net" - "sync" - - "github.com/andydixon/link/internal/models" - "golang.org/x/crypto/ssh" -) - -// ActiveForward tracks a running port-forwarding tunnel. -type ActiveForward struct { - Rule models.PortForward `json:"rule"` - Listener net.Listener `json:"-"` - done chan struct{} - mu sync.Mutex - conns []net.Conn // tracked connections for clean shutdown -} - -// ForwardManager manages active port-forwarding tunnels for a single SSH connection. -type ForwardManager struct { - mu sync.Mutex - forwards map[string]*ActiveForward - client *ssh.Client -} - -// NewForwardManager creates a ForwardManager bound to the given SSH client. -func NewForwardManager(client *ssh.Client) *ForwardManager { - return &ForwardManager{ - forwards: make(map[string]*ActiveForward), - client: client, - } -} - -// StartForward activates a port-forwarding rule. Returns an error if the -// rule's ID is already active or the listener cannot be established. -func (fm *ForwardManager) StartForward(rule models.PortForward) error { - fm.mu.Lock() - if _, exists := fm.forwards[rule.ID]; exists { - fm.mu.Unlock() - return fmt.Errorf("forward %q is already active", rule.ID) - } - fm.mu.Unlock() - - switch rule.Direction { - case models.PortForwardLocal: - return fm.startLocalForward(rule) - case models.PortForwardRemote: - return fm.startRemoteForward(rule) - default: - return fmt.Errorf("unsupported forward direction %q", rule.Direction) - } -} - -// StopForward terminates a specific active forward. -func (fm *ForwardManager) StopForward(forwardID string) error { - fm.mu.Lock() - af, ok := fm.forwards[forwardID] - if !ok { - fm.mu.Unlock() - return fmt.Errorf("forward %q not found", forwardID) - } - delete(fm.forwards, forwardID) - fm.mu.Unlock() - - return af.shutdown() -} - -// StopAll terminates every active forward. Called on session close. -func (fm *ForwardManager) StopAll() { - fm.mu.Lock() - all := make([]*ActiveForward, 0, len(fm.forwards)) - for id, af := range fm.forwards { - all = append(all, af) - delete(fm.forwards, id) - } - fm.mu.Unlock() - - for _, af := range all { - _ = af.shutdown() - } -} - -// ActiveForwards returns the list of currently active forwarding rules. -func (fm *ForwardManager) ActiveForwards() []models.PortForward { - fm.mu.Lock() - defer fm.mu.Unlock() - - result := make([]models.PortForward, 0, len(fm.forwards)) - for _, af := range fm.forwards { - result = append(result, af.Rule) - } - return result -} - -// --------------------------------------------------------------------------- -// Local forwarding: listen locally → tunnel to remote via SSH -// --------------------------------------------------------------------------- - -func (fm *ForwardManager) startLocalForward(rule models.PortForward) error { - bindAddr := rule.BindHost - if bindAddr == "" { - bindAddr = "127.0.0.1" - } - listenAddr := net.JoinHostPort(bindAddr, fmt.Sprintf("%d", rule.BindPort)) - - listener, err := net.Listen("tcp", listenAddr) - if err != nil { - return fmt.Errorf("local forward: listen on %s failed: %w", listenAddr, err) - } - - af := &ActiveForward{ - Rule: rule, - Listener: listener, - done: make(chan struct{}), - } - - fm.mu.Lock() - fm.forwards[rule.ID] = af - fm.mu.Unlock() - - destAddr := net.JoinHostPort(rule.DestHost, fmt.Sprintf("%d", rule.DestPort)) - - go func() { - defer close(af.done) - for { - localConn, acceptErr := listener.Accept() - if acceptErr != nil { - // Listener closed — normal shutdown path. - return - } - - af.mu.Lock() - af.conns = append(af.conns, localConn) - af.mu.Unlock() - - go func(lc net.Conn) { - defer lc.Close() - - remoteConn, dialErr := fm.client.Dial("tcp", destAddr) - if dialErr != nil { - return - } - defer remoteConn.Close() - - af.mu.Lock() - af.conns = append(af.conns, remoteConn) - af.mu.Unlock() - - pipeBidirectional(lc, remoteConn) - }(localConn) - } - }() - - return nil -} - -// --------------------------------------------------------------------------- -// Remote forwarding: ask server to listen → tunnel back to local destination -// --------------------------------------------------------------------------- - -func (fm *ForwardManager) startRemoteForward(rule models.PortForward) error { - bindAddr := rule.BindHost - if bindAddr == "" { - bindAddr = "0.0.0.0" - } - listenAddr := net.JoinHostPort(bindAddr, fmt.Sprintf("%d", rule.BindPort)) - - listener, err := fm.client.Listen("tcp", listenAddr) - if err != nil { - return fmt.Errorf("remote forward: server listen on %s failed: %w", listenAddr, err) - } - - af := &ActiveForward{ - Rule: rule, - Listener: listener, - done: make(chan struct{}), - } - - fm.mu.Lock() - fm.forwards[rule.ID] = af - fm.mu.Unlock() - - destAddr := net.JoinHostPort(rule.DestHost, fmt.Sprintf("%d", rule.DestPort)) - - go func() { - defer close(af.done) - for { - remoteConn, acceptErr := listener.Accept() - if acceptErr != nil { - return - } - - af.mu.Lock() - af.conns = append(af.conns, remoteConn) - af.mu.Unlock() - - go func(rc net.Conn) { - defer rc.Close() - - localConn, dialErr := net.Dial("tcp", destAddr) - if dialErr != nil { - return - } - defer localConn.Close() - - af.mu.Lock() - af.conns = append(af.conns, localConn) - af.mu.Unlock() - - pipeBidirectional(rc, localConn) - }(remoteConn) - } - }() - - return nil -} - -// --------------------------------------------------------------------------- -// Helpers -// --------------------------------------------------------------------------- - -// shutdown closes the listener and all tracked connections. -func (af *ActiveForward) shutdown() error { - var firstErr error - if af.Listener != nil { - if err := af.Listener.Close(); err != nil { - firstErr = err - } - } - - af.mu.Lock() - conns := af.conns - af.conns = nil - af.mu.Unlock() - - for _, c := range conns { - _ = c.Close() - } - return firstErr -} - -// pipeBidirectional copies data between two connections until either side closes. -func pipeBidirectional(a, b net.Conn) { - var wg sync.WaitGroup - wg.Add(2) - - copy := func(dst, src net.Conn) { - defer wg.Done() - _, _ = io.Copy(dst, src) - // Signal the other direction to stop by closing the write-half. - if tc, ok := dst.(*net.TCPConn); ok { - _ = tc.CloseWrite() - } - } - - go copy(a, b) - go copy(b, a) - wg.Wait() -} diff --git a/internal/sshclient/sshclient.go b/internal/sshclient/sshclient.go deleted file mode 100644 index b74d236..0000000 --- a/internal/sshclient/sshclient.go +++ /dev/null @@ -1,816 +0,0 @@ -package sshclient - -import ( - "bufio" - "context" - "encoding/base64" - "fmt" - "io" - "net" - "net/http" - "os" - "strings" - "sync" - "time" - - "github.com/andydixon/link/internal/models" - "github.com/andydixon/link/internal/terminalenv" - "github.com/pquerna/otp/totp" - "golang.org/x/crypto/ssh" - "golang.org/x/net/proxy" -) - -const maxSSHPrivateKeyBytes = 1 << 20 - -// Client manages a single SSH session lifecycle. -// It handles connection establishment, authentication, PTY allocation, -// terminal resize propagation, port forwarding, and clean shutdown. -type Client struct { - mu sync.Mutex - config *models.Profile - sshClient *ssh.Client - session *ssh.Session - stdin io.WriteCloser - stdout io.Reader - stderr io.Reader - connected bool - onData func(data []byte) - onError func(err error) - onClose func() - ctx context.Context - cancel context.CancelFunc - forwards *ForwardManager // port forwarding tunnels - agentCleanup func() // closes agent socket connection -} - -// NewClient creates a new SSH client bound to the given profile. -func NewClient(profile *models.Profile) *Client { - ctx, cancel := context.WithCancel(context.Background()) - return &Client{ - config: profile, - ctx: ctx, - cancel: cancel, - } -} - -// SetCallbacks sets the data, error, and close callbacks. Must be called -// before Connect. -func (c *Client) SetCallbacks(onData func([]byte), onError func(error), onClose func()) { - c.mu.Lock() - defer c.mu.Unlock() - c.onData = onData - c.onError = onError - c.onClose = onClose -} - -// sshConf returns the SSH-specific configuration, or a zero value if nil. -func (c *Client) sshConf() models.SSHConfig { - if c.config.SSH != nil { - return *c.config.SSH - } - return models.SSHConfig{} -} - -// Connect refuses to connect without an explicit host-key callback. Use -// ConnectWithHostKeyCallback so all callers make host-key policy visible. -func (c *Client) Connect(getPassword func() (string, error), getPassphrase func() (string, error)) error { - return fmt.Errorf("sshclient: host key callback is required; use ConnectWithHostKeyCallback") -} - -// ConnectWithHostKeyCallback applies a custom HostKeyCallback (e.g. from -// HostKeyVerifier) and then establishes the connection. -func (c *Client) ConnectWithHostKeyCallback( - hkCallback ssh.HostKeyCallback, - getPassword func() (string, error), - getPassphrase func() (string, error), -) error { - return c.connectInternal(hkCallback, getPassword, getPassphrase) -} - -// connectInternal is the shared implementation for both Connect variants. -func (c *Client) connectInternal( - hkCallback ssh.HostKeyCallback, - getPassword func() (string, error), - getPassphrase func() (string, error), -) error { - c.mu.Lock() - if c.connected { - c.mu.Unlock() - return fmt.Errorf("already connected") - } - c.mu.Unlock() - - sc := c.sshConf() - - authMethods, err := c.buildAuthMethods(sc, getPassword, getPassphrase) - if err != nil { - return fmt.Errorf("failed to build auth methods: %w", err) - } - - termType := sc.TerminalType - if termType == "" { - termType = "xterm-256color" - } - - timeout := time.Duration(c.config.ConnectTimeout) * time.Second - if timeout == 0 { - timeout = 30 * time.Second - } - - sshConfig := &ssh.ClientConfig{ - User: c.config.Username, - Auth: authMethods, - HostKeyCallback: hkCallback, - Timeout: timeout, - } - - var sshConn *ssh.Client - - if len(sc.JumpHosts) > 0 { - sshConn, err = c.connectViaJumpHosts(sc, sshConfig, getPassphrase) - } else { - port := c.config.Port - if port == 0 { - port = 22 - } - addr := net.JoinHostPort(c.config.Host, fmt.Sprintf("%d", port)) - - // Dial through proxy if configured, otherwise use direct TCP. - conn, dialErr := c.dialTarget(sc, addr, timeout) - if dialErr != nil { - return fmt.Errorf("SSH connection failed: %w", dialErr) - } - ncc, chans, reqs, sshErr := ssh.NewClientConn(conn, addr, sshConfig) - if sshErr != nil { - conn.Close() - return fmt.Errorf("SSH handshake failed: %w", sshErr) - } - sshConn = ssh.NewClient(ncc, chans, reqs) - } - if err != nil { - return fmt.Errorf("SSH connection failed: %w", err) - } - - session, err := sshConn.NewSession() - if err != nil { - sshConn.Close() - return fmt.Errorf("failed to create SSH session: %w", err) - } - - // Allocate a PTY with sensible defaults and terminal modes. - cols := 80 - rows := 24 - modes := ssh.TerminalModes{ - ssh.ECHO: 1, - ssh.TTY_OP_ISPEED: 115200, - ssh.TTY_OP_OSPEED: 115200, - ssh.IUTF8: 1, - } - - sessionEnv := terminalenv.SSHEnvVars(sc.EnvVars) - for _, key := range terminalenv.SortedEnvKeys(sessionEnv) { - // Some SSH servers never reply to env requests. Send them without - // requiring a reply so optional locale/profile env cannot stall login. - sendSessionEnv(session, key, sessionEnv[key]) - } - - if err := session.RequestPty(termType, rows, cols, modes); err != nil { - session.Close() - sshConn.Close() - return fmt.Errorf("PTY request failed: %w", err) - } - - // Set up SSH agent forwarding if requested (must happen before shell start). - if sc.AgentForwarding { - if fwdErr := requestAgentForwarding(sshConn, session); fwdErr != nil { - // Non-fatal — log but don't abort the connection. - if c.onError != nil { - c.onError(fmt.Errorf("agent forwarding: %w", fwdErr)) - } - } - } - - stdinPipe, err := session.StdinPipe() - if err != nil { - session.Close() - sshConn.Close() - return fmt.Errorf("failed to get stdin pipe: %w", err) - } - - stdoutPipe, err := session.StdoutPipe() - if err != nil { - session.Close() - sshConn.Close() - return fmt.Errorf("failed to get stdout pipe: %w", err) - } - - stderrPipe, err := session.StderrPipe() - if err != nil { - session.Close() - sshConn.Close() - return fmt.Errorf("failed to get stderr pipe: %w", err) - } - - // Start shell or explicit startup command. - if sc.StartupCommand != "" { - if err := session.Start(sc.StartupCommand); err != nil { - session.Close() - sshConn.Close() - return fmt.Errorf("failed to start command: %w", err) - } - } else { - if err := session.Shell(); err != nil { - session.Close() - sshConn.Close() - return fmt.Errorf("failed to start shell: %w", err) - } - } - - c.mu.Lock() - c.sshClient = sshConn - c.session = session - c.stdin = stdinPipe - c.stdout = stdoutPipe - c.stderr = stderrPipe - c.connected = true - c.forwards = NewForwardManager(sshConn) - c.mu.Unlock() - - go c.readLoop() - - if sc.KeepaliveInterval > 0 { - go c.startKeepalive(sc.KeepaliveInterval) - } - - return nil -} - -func sendSessionEnv(session *ssh.Session, name, value string) { - _, _ = session.SendRequest("env", false, ssh.Marshal(struct { - Name string - Value string - }{ - Name: name, - Value: value, - })) -} - -// Write sends data to the SSH session's stdin. -func (c *Client) Write(data []byte) error { - c.mu.Lock() - if !c.connected || c.stdin == nil { - c.mu.Unlock() - return fmt.Errorf("not connected") - } - stdin := c.stdin - c.mu.Unlock() - - _, err := stdin.Write(data) - return err -} - -// Resize sends a terminal resize request to the remote PTY. -func (c *Client) Resize(cols, rows int) error { - c.mu.Lock() - if !c.connected || c.session == nil { - c.mu.Unlock() - return fmt.Errorf("not connected") - } - session := c.session - c.mu.Unlock() - - return session.WindowChange(rows, cols) -} - -// Ping sends a keepalive request and returns the round-trip time in milliseconds. -func (c *Client) Ping() (int64, error) { - c.mu.Lock() - if !c.connected || c.sshClient == nil { - c.mu.Unlock() - return 0, fmt.Errorf("not connected") - } - client := c.sshClient - c.mu.Unlock() - - start := time.Now() - _, _, err := client.SendRequest("keepalive@openssh.com", true, nil) - if err != nil { - return 0, err - } - return time.Since(start).Milliseconds(), nil -} - -// RunCommand executes a single command using a new SSH session. -func (c *Client) RunCommand(command string) (string, error) { - c.mu.Lock() - if !c.connected || c.sshClient == nil { - c.mu.Unlock() - return "", fmt.Errorf("not connected") - } - client := c.sshClient - c.mu.Unlock() - - session, err := client.NewSession() - if err != nil { - return "", fmt.Errorf("failed to open SSH session: %w", err) - } - defer session.Close() - - out, err := session.Output(command) - if err != nil { - return "", fmt.Errorf("command failed: %w", err) - } - return strings.TrimSpace(string(out)), nil -} - -// Close cleanly shuts down the SSH session and connection, cancels the -// background context, and invokes the onClose callback. -func (c *Client) Close() error { - c.mu.Lock() - if !c.connected { - c.mu.Unlock() - return nil - } - c.connected = false - session := c.session - sshClient := c.sshClient - fwdMgr := c.forwards - agCleanup := c.agentCleanup - onClose := c.onClose - c.mu.Unlock() - - c.cancel() - - // Shut down any active port forwards first. - if fwdMgr != nil { - fwdMgr.StopAll() - } - - // Close the SSH agent socket connection. - if agCleanup != nil { - agCleanup() - } - - var firstErr error - if session != nil { - if err := session.Close(); err != nil && firstErr == nil { - firstErr = err - } - } - if sshClient != nil { - if err := sshClient.Close(); err != nil && firstErr == nil { - firstErr = err - } - } - - if onClose != nil { - onClose() - } - return firstErr -} - -// IsConnected returns the current connection state. -func (c *Client) IsConnected() bool { - c.mu.Lock() - defer c.mu.Unlock() - return c.connected -} - -// Forwards returns the ForwardManager for this client, or nil if not connected. -func (c *Client) Forwards() *ForwardManager { - c.mu.Lock() - defer c.mu.Unlock() - return c.forwards -} - -// SSHConn returns the underlying ssh.Client for use by subsystems such as -// SFTP. Returns nil if not connected. -func (c *Client) SSHConn() *ssh.Client { - c.mu.Lock() - defer c.mu.Unlock() - return c.sshClient -} - -// buildAuthMethods constructs the SSH authentication method list from the -// profile's SSHConfig and the provided credential callbacks. -func (c *Client) buildAuthMethods(sc models.SSHConfig, getPassword func() (string, error), getPassphrase func() (string, error)) ([]ssh.AuthMethod, error) { - var methods []ssh.AuthMethod - - // If agent auth is enabled, try to use the local SSH agent first. - if sc.AgentAuth { - am, cleanup, err := agentAuthMethod() - if err == nil { - methods = append(methods, am) - c.mu.Lock() - c.agentCleanup = cleanup - c.mu.Unlock() - } - // Non-fatal — fall through to other auth methods if agent unavailable. - } - - authList := sc.AuthMethods - if len(authList) == 0 { - authList = []string{"publickey", "password", "keyboard-interactive"} - } - - for _, m := range authList { - switch m { - case "password": - if getPassword != nil { - methods = append(methods, ssh.PasswordCallback(func() (string, error) { - return getPassword() - })) - } - - case "publickey": - if sc.PrivateKeyPath != "" { - signer, err := c.parsePrivateKey(sc.PrivateKeyPath, getPassphrase) - if err != nil { - return nil, fmt.Errorf("failed to parse private key %s: %w", sc.PrivateKeyPath, err) - } - methods = append(methods, ssh.PublicKeys(signer)) - } - - case "keyboard-interactive": - if getPassword != nil { - pwdCallback := getPassword - totpSecret := "" - if sc.TOTPSecret != "" { - totpSecret = sc.TOTPSecret - } - methods = append(methods, ssh.KeyboardInteractive( - func(name, instruction string, questions []string, echos []bool) ([]string, error) { - answers := make([]string, len(questions)) - for i, question := range questions { - q := strings.ToLower(question) - // Detect TOTP/2FA prompts and auto-fill when secret is configured. - if totpSecret != "" && isTOTPPrompt(q) { - code, err := totp.GenerateCode(totpSecret, time.Now()) - if err != nil { - return nil, fmt.Errorf("failed to generate TOTP code: %w", err) - } - answers[i] = code - } else { - ans, err := pwdCallback() - if err != nil { - return nil, err - } - answers[i] = ans - } - } - return answers, nil - }, - )) - } - } - } - - if len(methods) == 0 { - return nil, fmt.Errorf("no authentication methods available") - } - return methods, nil -} - -// isTOTPPrompt checks whether a keyboard-interactive question looks like a -// TOTP/2FA prompt. Common patterns include "verification code", "otp", -// "two-factor", "2fa", "authenticator", etc. -func isTOTPPrompt(question string) bool { - totpKeywords := []string{ - "verification code", - "otp", - "one-time", - "one time", - "two-factor", - "two factor", - "2fa", - "totp", - "authenticator", - "security code", - "6-digit", - "6 digit", - } - for _, kw := range totpKeywords { - if strings.Contains(question, kw) { - return true - } - } - return false -} - -// dialTarget dials the target address, optionally through a SOCKS5 or HTTP -// CONNECT proxy as configured in the profile's SSH settings. -func (c *Client) dialTarget(sc models.SSHConfig, addr string, timeout time.Duration) (net.Conn, error) { - switch strings.ToLower(sc.ProxyType) { - case "socks5": - return c.dialSOCKS5(sc, addr, timeout) - case "http": - return c.dialHTTPProxy(sc, addr, timeout) - default: - // No proxy — direct TCP dial. - return net.DialTimeout("tcp", addr, timeout) - } -} - -// dialSOCKS5 connects to the target via a SOCKS5 proxy. -func (c *Client) dialSOCKS5(sc models.SSHConfig, addr string, timeout time.Duration) (net.Conn, error) { - proxyPort := sc.ProxyPort - if proxyPort == 0 { - proxyPort = 1080 - } - proxyAddr := net.JoinHostPort(sc.ProxyHost, fmt.Sprintf("%d", proxyPort)) - - var auth *proxy.Auth - if sc.ProxyUsername != "" { - auth = &proxy.Auth{ - User: sc.ProxyUsername, - Password: sc.ProxyPassword, - } - } - - dialer, err := proxy.SOCKS5("tcp", proxyAddr, auth, &net.Dialer{Timeout: timeout}) - if err != nil { - return nil, fmt.Errorf("SOCKS5 proxy setup failed: %w", err) - } - - conn, err := dialer.Dial("tcp", addr) - if err != nil { - return nil, fmt.Errorf("SOCKS5 proxy connection to %s failed: %w", addr, err) - } - return conn, nil -} - -// dialHTTPProxy connects to the target via an HTTP CONNECT proxy. -func (c *Client) dialHTTPProxy(sc models.SSHConfig, addr string, timeout time.Duration) (net.Conn, error) { - proxyPort := sc.ProxyPort - if proxyPort == 0 { - proxyPort = 8080 - } - proxyAddr := net.JoinHostPort(sc.ProxyHost, fmt.Sprintf("%d", proxyPort)) - - conn, err := net.DialTimeout("tcp", proxyAddr, timeout) - if err != nil { - return nil, fmt.Errorf("HTTP proxy connection failed: %w", err) - } - - // Send CONNECT request. - connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n", addr, addr) - if sc.ProxyUsername != "" { - cred := base64.StdEncoding.EncodeToString([]byte(sc.ProxyUsername + ":" + sc.ProxyPassword)) - connectReq += fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", cred) - } - connectReq += "\r\n" - - if _, err := conn.Write([]byte(connectReq)); err != nil { - conn.Close() - return nil, fmt.Errorf("HTTP CONNECT write failed: %w", err) - } - - // Read response. - br := bufio.NewReader(conn) - resp, err := http.ReadResponse(br, &http.Request{Method: "CONNECT"}) - if err != nil { - conn.Close() - return nil, fmt.Errorf("HTTP CONNECT response read failed: %w", err) - } - resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - conn.Close() - return nil, fmt.Errorf("HTTP CONNECT proxy returned %d %s", resp.StatusCode, resp.Status) - } - - return conn, nil -} - -// connectViaJumpHosts establishes the SSH connection through a chain of -// jump hosts. Each hop opens a new net.Conn through the previous hop's -// SSH client. -func (c *Client) connectViaJumpHosts(sc models.SSHConfig, finalConfig *ssh.ClientConfig, getPassphrase func() (string, error)) (*ssh.Client, error) { - var currentClient *ssh.Client - - for i, jump := range sc.JumpHosts { - jumpPort := jump.Port - if jumpPort == 0 { - jumpPort = 22 - } - jumpAddr := net.JoinHostPort(jump.Host, fmt.Sprintf("%d", jumpPort)) - - var jumpAuth []ssh.AuthMethod - if jump.PrivateKeyPath != "" { - signer, err := c.parsePrivateKey(jump.PrivateKeyPath, getPassphrase) - if err != nil { - if currentClient != nil { - currentClient.Close() - } - return nil, fmt.Errorf("jump host %d private key error: %w", i, err) - } - jumpAuth = append(jumpAuth, ssh.PublicKeys(signer)) - } - - jumpConfig := jumpHostClientConfig(jump, jumpAuth, finalConfig) - - if currentClient == nil { - client, err := ssh.Dial("tcp", jumpAddr, jumpConfig) - if err != nil { - return nil, fmt.Errorf("failed to connect to jump host %d (%s): %w", i, jumpAddr, err) - } - currentClient = client - } else { - conn, err := currentClient.Dial("tcp", jumpAddr) - if err != nil { - currentClient.Close() - return nil, fmt.Errorf("failed to dial through jump host %d (%s): %w", i, jumpAddr, err) - } - ncc, chans, reqs, err := ssh.NewClientConn(conn, jumpAddr, jumpConfig) - if err != nil { - conn.Close() - currentClient.Close() - return nil, fmt.Errorf("failed to establish SSH through jump host %d: %w", i, err) - } - currentClient = ssh.NewClient(ncc, chans, reqs) - } - } - - // Final hop to the target host. - targetPort := c.config.Port - if targetPort == 0 { - targetPort = 22 - } - targetAddr := net.JoinHostPort(c.config.Host, fmt.Sprintf("%d", targetPort)) - - conn, err := currentClient.Dial("tcp", targetAddr) - if err != nil { - currentClient.Close() - return nil, fmt.Errorf("failed to dial target %s through jump hosts: %w", targetAddr, err) - } - - ncc, chans, reqs, err := ssh.NewClientConn(conn, targetAddr, finalConfig) - if err != nil { - conn.Close() - currentClient.Close() - return nil, fmt.Errorf("failed to establish SSH to target %s: %w", targetAddr, err) - } - - return ssh.NewClient(ncc, chans, reqs), nil -} - -func jumpHostClientConfig(jump models.JumpHost, auth []ssh.AuthMethod, finalConfig *ssh.ClientConfig) *ssh.ClientConfig { - return &ssh.ClientConfig{ - User: jump.Username, - Auth: auth, - HostKeyCallback: finalConfig.HostKeyCallback, - Timeout: finalConfig.Timeout, - } -} - -// parsePrivateKey loads and parses an SSH private key file. If the key is -// encrypted and getPassphrase is provided, it will attempt decryption. -func (c *Client) parsePrivateKey(path string, getPassphrase func() (string, error)) (ssh.Signer, error) { - info, err := os.Stat(path) - if err != nil { - return nil, fmt.Errorf("unable to stat key file: %w", err) - } - if info.IsDir() { - return nil, fmt.Errorf("key path is a directory") - } - if info.Size() > maxSSHPrivateKeyBytes { - return nil, fmt.Errorf("key file is too large: %d bytes exceeds %d bytes", info.Size(), maxSSHPrivateKeyBytes) - } - keyData, err := os.ReadFile(path) - if err != nil { - return nil, fmt.Errorf("unable to read key file: %w", err) - } - - signer, err := ssh.ParsePrivateKey(keyData) - if err == nil { - return signer, nil - } - - if getPassphrase == nil { - return nil, fmt.Errorf("key is encrypted and no passphrase callback provided: %w", err) - } - - passphrase, err := getPassphrase() - if err != nil { - return nil, fmt.Errorf("failed to get passphrase: %w", err) - } - - signer, err = ssh.ParsePrivateKeyWithPassphrase(keyData, []byte(passphrase)) - if err != nil { - return nil, fmt.Errorf("failed to parse encrypted key: %w", err) - } - return signer, nil -} - -// startKeepalive periodically sends keepalive requests to the server. -// It exits when the context is cancelled or the connection closes. -func (c *Client) startKeepalive(intervalSecs int) { - interval := time.Duration(intervalSecs) * time.Second - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for { - select { - case <-c.ctx.Done(): - return - case <-ticker.C: - c.mu.Lock() - client := c.sshClient - connected := c.connected - c.mu.Unlock() - - if !connected || client == nil { - return - } - - _, _, err := client.SendRequest("keepalive@openssh.com", true, nil) - if err != nil { - c.mu.Lock() - onError := c.onError - c.mu.Unlock() - if onError != nil { - onError(fmt.Errorf("keepalive failed: %w", err)) - } - return - } - } - } -} - -// readLoop reads from stdout and stderr, dispatching received data to the -// onData callback. It runs until the context is cancelled or the streams close. -func (c *Client) readLoop() { - done := make(chan struct{}, 2) - - go func() { - defer func() { done <- struct{}{} }() - buf := make([]byte, 32*1024) - for { - select { - case <-c.ctx.Done(): - return - default: - } - - c.mu.Lock() - stdout := c.stdout - c.mu.Unlock() - if stdout == nil { - return - } - - n, err := stdout.Read(buf) - if n > 0 { - data := make([]byte, n) - copy(data, buf[:n]) - c.mu.Lock() - cb := c.onData - c.mu.Unlock() - if cb != nil { - cb(data) - } - } - if err != nil { - return - } - } - }() - - go func() { - defer func() { done <- struct{}{} }() - buf := make([]byte, 32*1024) - for { - select { - case <-c.ctx.Done(): - return - default: - } - - c.mu.Lock() - stderr := c.stderr - c.mu.Unlock() - if stderr == nil { - return - } - - n, err := stderr.Read(buf) - if n > 0 { - data := make([]byte, n) - copy(data, buf[:n]) - c.mu.Lock() - cb := c.onData - c.mu.Unlock() - if cb != nil { - cb(data) - } - } - if err != nil { - return - } - } - }() - - <-done - <-done - c.Close() -} diff --git a/internal/sshclient/sshclient_test.go b/internal/sshclient/sshclient_test.go deleted file mode 100644 index 2ecbcd9..0000000 --- a/internal/sshclient/sshclient_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package sshclient - -import ( - "crypto/ed25519" - "crypto/rand" - "net" - "testing" - "time" - - "github.com/andydixon/link/internal/models" - "golang.org/x/crypto/ssh" -) - -func TestConnectRequiresHostKeyCallback(t *testing.T) { - profile := models.NewProfile(models.ProtocolSSH) - client := NewClient(&profile) - - err := client.Connect(nil, nil) - if err == nil { - t.Fatal("expected Connect to reject missing host key callback") - } -} - -func TestJumpHostClientConfigUsesFinalHostKeyCallback(t *testing.T) { - called := false - hostKeyCallback := func(hostname string, _ net.Addr, _ ssh.PublicKey) error { - called = true - if hostname != "jump.example:2222" { - t.Fatalf("hostname = %q, want jump.example:2222", hostname) - } - return nil - } - - finalConfig := &ssh.ClientConfig{ - HostKeyCallback: hostKeyCallback, - Timeout: 12 * time.Second, - } - jump := models.JumpHost{ - Host: "jump.example", - Port: 2222, - Username: "alice", - } - - got := jumpHostClientConfig(jump, nil, finalConfig) - if got.User != jump.Username { - t.Fatalf("User = %q, want %q", got.User, jump.Username) - } - if got.Timeout != finalConfig.Timeout { - t.Fatalf("Timeout = %s, want %s", got.Timeout, finalConfig.Timeout) - } - if got.HostKeyCallback == nil { - t.Fatal("HostKeyCallback is nil") - } - - if err := got.HostKeyCallback("jump.example:2222", nil, nil); err != nil { - t.Fatalf("HostKeyCallback() error = %v", err) - } - if !called { - t.Fatal("final host key callback was not used") - } -} - -func TestPingReturnsAfterSSHTransportCloses(t *testing.T) { - _, privateKey, err := ed25519.GenerateKey(rand.Reader) - if err != nil { - t.Fatalf("GenerateKey() error = %v", err) - } - signer, err := ssh.NewSignerFromKey(privateKey) - if err != nil { - t.Fatalf("NewSignerFromKey() error = %v", err) - } - - serverConfig := &ssh.ServerConfig{NoClientAuth: true} - serverConfig.AddHostKey(signer) - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatalf("Listen() error = %v", err) - } - t.Cleanup(func() { _ = listener.Close() }) - - serverReady := make(chan *ssh.ServerConn, 1) - serverErrors := make(chan error, 1) - go func() { - conn, acceptErr := listener.Accept() - if acceptErr != nil { - serverErrors <- acceptErr - return - } - serverConn, _, _, handshakeErr := ssh.NewServerConn(conn, serverConfig) - if handshakeErr != nil { - _ = conn.Close() - serverErrors <- handshakeErr - return - } - serverReady <- serverConn - }() - - conn, err := net.DialTimeout("tcp", listener.Addr().String(), 2*time.Second) - if err != nil { - t.Fatalf("DialTimeout() error = %v", err) - } - clientConn, channels, requests, err := ssh.NewClientConn(conn, listener.Addr().String(), &ssh.ClientConfig{ - User: "test", - HostKeyCallback: ssh.InsecureIgnoreHostKey(), // Test server uses an ephemeral key. - }) - if err != nil { - _ = conn.Close() - t.Fatalf("NewClientConn() error = %v", err) - } - rawClient := ssh.NewClient(clientConn, channels, requests) - - var serverConn *ssh.ServerConn - select { - case serverConn = <-serverReady: - case serverErr := <-serverErrors: - t.Fatalf("SSH server handshake error = %v", serverErr) - case <-time.After(2 * time.Second): - t.Fatal("timed out waiting for SSH server handshake") - } - - client := NewClient(&models.Profile{}) - client.mu.Lock() - client.sshClient = rawClient - client.connected = true - client.mu.Unlock() - t.Cleanup(func() { _ = client.Close() }) - - if err := serverConn.Close(); err != nil { - t.Fatalf("server Close() error = %v", err) - } - waitDone := make(chan error, 1) - go func() { waitDone <- rawClient.Wait() }() - select { - case <-waitDone: - case <-time.After(2 * time.Second): - t.Fatal("timed out waiting for SSH transport to close") - } - - pingDone := make(chan error, 1) - go func() { - _, pingErr := client.Ping() - pingDone <- pingErr - }() - select { - case pingErr := <-pingDone: - if pingErr == nil { - t.Fatal("Ping() succeeded after the SSH transport closed") - } - case <-time.After(2 * time.Second): - t.Fatal("Ping() did not return after the SSH transport closed") - } -} diff --git a/internal/sshconfig/sshconfig.go b/internal/sshconfig/sshconfig.go deleted file mode 100644 index 1a3ae1e..0000000 --- a/internal/sshconfig/sshconfig.go +++ /dev/null @@ -1,302 +0,0 @@ -// Package sshconfig parses OpenSSH ~/.ssh/config files and converts -// Host blocks into Alchementary Link connection profiles. Supports a useful -// subset of SSH config directives: HostName, Port, User, IdentityFile, -// ProxyJump, ForwardAgent, and Compression. -// -// Limitations (future work): -// - Match blocks are ignored -// - Wildcards in Host patterns are skipped -// - Include directives are not followed -// - Token expansion (%h, %p, etc.) is not performed -package sshconfig - -import ( - "bufio" - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/andydixon/link/internal/models" - "github.com/google/uuid" -) - -const ( - maxConfigFileBytes = 5 * 1024 * 1024 - maxConfigLineBytes = 256 * 1024 - maxHostBlocks = 5000 -) - -// HostBlock represents a parsed SSH config Host section. -type HostBlock struct { - Aliases []string - HostName string - Port int - User string - IdentityFile string - ProxyJump string - ForwardAgent bool - Compression bool -} - -// ParseFile reads and parses an OpenSSH config file. -func ParseFile(path string) ([]HostBlock, error) { - f, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("sshconfig: failed to open %s: %w", path, err) - } - defer f.Close() - info, err := f.Stat() - if err != nil { - return nil, fmt.Errorf("sshconfig: failed to stat %s: %w", path, err) - } - if info.IsDir() { - return nil, fmt.Errorf("sshconfig: %s is a directory", path) - } - if info.Size() > maxConfigFileBytes { - return nil, fmt.Errorf("sshconfig: config file is too large: %d bytes exceeds %d bytes", info.Size(), maxConfigFileBytes) - } - - var blocks []HostBlock - var current *HostBlock - commitCurrent := func() error { - if current == nil { - return nil - } - if len(blocks) >= maxHostBlocks { - return fmt.Errorf("sshconfig: too many Host blocks (max %d)", maxHostBlocks) - } - blocks = append(blocks, *current) - current = nil - return nil - } - - scanner := bufio.NewScanner(f) - scanner.Buffer(make([]byte, 0, 64*1024), maxConfigLineBytes) - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - - // Skip empty lines and comments. - if line == "" || strings.HasPrefix(line, "#") { - continue - } - - // Split into directive and value. - key, value := splitDirective(line) - key = strings.ToLower(key) - - switch key { - case "host": - // Commit previous block. - if err := commitCurrent(); err != nil { - return nil, err - } - aliases := strings.Fields(value) - // Skip wildcard-only hosts (e.g. "Host *"). - hasWildcard := false - for _, a := range aliases { - if strings.Contains(a, "*") || strings.Contains(a, "?") { - hasWildcard = true - } - } - if hasWildcard && len(aliases) == 1 { - current = nil - continue - } - current = &HostBlock{ - Aliases: aliases, - Port: 22, - } - - case "match": - // Commit previous block and skip Match blocks. - if err := commitCurrent(); err != nil { - return nil, err - } - current = nil - - case "hostname": - if current != nil { - current.HostName = value - } - case "port": - if current != nil { - if p, err := strconv.Atoi(value); err == nil { - current.Port = p - } - } - case "user": - if current != nil { - current.User = value - } - case "identityfile": - if current != nil { - current.IdentityFile = expandTilde(value) - } - case "proxyjump": - if current != nil { - current.ProxyJump = value - } - case "forwardagent": - if current != nil { - current.ForwardAgent = isTrueish(value) - } - case "compression": - if current != nil { - current.Compression = isTrueish(value) - } - } - } - - // Commit final block. - if err := commitCurrent(); err != nil { - return nil, err - } - - if err := scanner.Err(); err != nil { - return nil, fmt.Errorf("sshconfig: read error: %w", err) - } - - return blocks, nil -} - -// DefaultConfigPath returns the standard OpenSSH config file path. -func DefaultConfigPath() string { - home, err := os.UserHomeDir() - if err != nil { - return "" - } - return filepath.Join(home, ".ssh", "config") -} - -// ToProfiles converts parsed Host blocks into Alchementary Link profiles. -// Each block produces one profile; the first alias becomes the display name. -func ToProfiles(blocks []HostBlock, folder string) []models.Profile { - profiles := make([]models.Profile, 0, len(blocks)) - - for _, b := range blocks { - if len(b.Aliases) == 0 { - continue - } - - host := b.HostName - if host == "" { - host = b.Aliases[0] - } - - displayName := b.Aliases[0] - username := b.User - - sshConf := &models.SSHConfig{ - AuthMethods: []string{"publickey", "password"}, - SecretMode: models.SecretModeAskEveryTime, - HostKeyPolicy: models.HostKeyAsk, - TerminalType: "xterm-256color", - AgentAuth: b.ForwardAgent, - AgentForwarding: b.ForwardAgent, - Compression: b.Compression, - } - - if b.IdentityFile != "" { - sshConf.PrivateKeyPath = b.IdentityFile - } - - // Parse ProxyJump into a jump host. - if b.ProxyJump != "" && b.ProxyJump != "none" { - jh := parseProxyJump(b.ProxyJump) - if jh != nil { - sshConf.JumpHosts = []models.JumpHost{*jh} - } - } - - profile := models.NewProfile(models.ProtocolSSH) - profile.ID = uuid.New().String() - profile.DisplayName = displayName - profile.Host = host - profile.Port = b.Port - profile.Username = username - profile.Folder = folder - profile.SSH = sshConf - profile.Tags = []string{"ssh-config-import"} - - profiles = append(profiles, profile) - } - - return profiles -} - -// parseProxyJump parses a simple ProxyJump value (user@host:port format). -// Only the first hop is parsed; chained jumps are future work. -func parseProxyJump(value string) *models.JumpHost { - // Take only the first hop if comma-separated. - if idx := strings.Index(value, ","); idx >= 0 { - value = value[:idx] - } - value = strings.TrimSpace(value) - if value == "" || value == "none" { - return nil - } - - jh := &models.JumpHost{ - Port: 22, - AuthMethod: "publickey", - SecretMode: models.SecretModeAskEveryTime, - } - - // Extract user@ prefix. - if at := strings.Index(value, "@"); at >= 0 { - jh.Username = value[:at] - value = value[at+1:] - } - - // Extract :port suffix. - if colon := strings.LastIndex(value, ":"); colon >= 0 { - if p, err := strconv.Atoi(value[colon+1:]); err == nil { - jh.Port = p - value = value[:colon] - } - } - - jh.Host = value - return jh -} - -// splitDirective splits a line into key and value, handling both -// "Key Value" and "Key=Value" formats. -func splitDirective(line string) (string, string) { - // Try = separator first. - if idx := strings.Index(line, "="); idx >= 0 { - return strings.TrimSpace(line[:idx]), strings.TrimSpace(line[idx+1:]) - } - // Fall back to whitespace separator. - parts := strings.SplitN(line, " ", 2) - if len(parts) == 1 { - parts = strings.SplitN(line, "\t", 2) - } - if len(parts) < 2 { - return parts[0], "" - } - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]) -} - -// expandTilde replaces a leading ~ with the user's home directory. -func expandTilde(path string) string { - if !strings.HasPrefix(path, "~/") { - return path - } - home, err := os.UserHomeDir() - if err != nil { - return path - } - return filepath.Join(home, path[2:]) -} - -// isTrueish returns true for "yes", "true", "on", and "1". -func isTrueish(val string) bool { - switch strings.ToLower(val) { - case "yes", "true", "on", "1": - return true - } - return false -} diff --git a/internal/sshconfig/sshconfig_test.go b/internal/sshconfig/sshconfig_test.go deleted file mode 100644 index 564e003..0000000 --- a/internal/sshconfig/sshconfig_test.go +++ /dev/null @@ -1,218 +0,0 @@ -package sshconfig - -import ( - "os" - "path/filepath" - "strconv" - "strings" - "testing" -) - -const testConfig = `# Test SSH config - -Host * - ServerAliveInterval 60 - -Host webserver - HostName web.example.com - Port 2222 - User deploy - IdentityFile ~/.ssh/deploy_key - ForwardAgent yes - Compression yes - -Host dbserver - HostName db.internal.example.com - User postgres - ProxyJump bastion@jump.example.com:2200 - -Host simple - HostName 10.0.0.5 - -Host wildcard-* - User admin -` - -func TestParseFile(t *testing.T) { - dir := t.TempDir() - path := filepath.Join(dir, "config") - if err := os.WriteFile(path, []byte(testConfig), 0600); err != nil { - t.Fatal(err) - } - - blocks, err := ParseFile(path) - if err != nil { - t.Fatalf("ParseFile failed: %v", err) - } - - // Should have 3 blocks: webserver, dbserver, simple. - // "Host *" and "Host wildcard-*" should be skipped. - if len(blocks) != 3 { - t.Fatalf("expected 3 blocks, got %d", len(blocks)) - } - - // Verify webserver block. - wb := blocks[0] - if wb.Aliases[0] != "webserver" { - t.Errorf("expected alias 'webserver', got %q", wb.Aliases[0]) - } - if wb.HostName != "web.example.com" { - t.Errorf("expected HostName 'web.example.com', got %q", wb.HostName) - } - if wb.Port != 2222 { - t.Errorf("expected port 2222, got %d", wb.Port) - } - if wb.User != "deploy" { - t.Errorf("expected user 'deploy', got %q", wb.User) - } - if !wb.ForwardAgent { - t.Error("expected ForwardAgent to be true") - } - if !wb.Compression { - t.Error("expected Compression to be true") - } - - // Verify dbserver block with ProxyJump. - db := blocks[1] - if db.Aliases[0] != "dbserver" { - t.Errorf("expected alias 'dbserver', got %q", db.Aliases[0]) - } - if db.ProxyJump != "bastion@jump.example.com:2200" { - t.Errorf("unexpected ProxyJump: %q", db.ProxyJump) - } - - // Verify simple block. - sb := blocks[2] - if sb.HostName != "10.0.0.5" { - t.Errorf("expected HostName '10.0.0.5', got %q", sb.HostName) - } - if sb.Port != 22 { - t.Errorf("expected default port 22, got %d", sb.Port) - } -} - -func TestParseFileRejectsOversizedFile(t *testing.T) { - path := filepath.Join(t.TempDir(), "config") - f, err := os.Create(path) - if err != nil { - t.Fatal(err) - } - if err := f.Truncate(maxConfigFileBytes + 1); err != nil { - f.Close() - t.Fatal(err) - } - if err := f.Close(); err != nil { - t.Fatal(err) - } - - _, err = ParseFile(path) - if err == nil { - t.Fatal("expected oversized config file to be rejected") - } - if !strings.Contains(err.Error(), "too large") { - t.Fatalf("error = %q, want too large", err.Error()) - } -} - -func TestParseFileRejectsTooManyHostBlocks(t *testing.T) { - path := filepath.Join(t.TempDir(), "config") - var b strings.Builder - for i := 0; i < maxHostBlocks+1; i++ { - b.WriteString("Host host") - b.WriteString(strconv.Itoa(i)) - b.WriteString("\n HostName example.com\n") - } - if err := os.WriteFile(path, []byte(b.String()), 0600); err != nil { - t.Fatal(err) - } - - _, err := ParseFile(path) - if err == nil { - t.Fatal("expected excessive Host blocks to be rejected") - } - if !strings.Contains(err.Error(), "too many Host blocks") { - t.Fatalf("error = %q, want too many Host blocks", err.Error()) - } -} - -func TestToProfiles(t *testing.T) { - blocks := []HostBlock{ - { - Aliases: []string{"myserver"}, - HostName: "srv.example.com", - Port: 22, - User: "admin", - ForwardAgent: true, - }, - { - Aliases: []string{"jumpbox"}, - HostName: "jump.example.com", - Port: 2200, - User: "jumper", - ProxyJump: "gateway@gw.example.com", - }, - } - - profiles := ToProfiles(blocks, "Imported") - if len(profiles) != 2 { - t.Fatalf("expected 2 profiles, got %d", len(profiles)) - } - - p := profiles[0] - if p.DisplayName != "myserver" { - t.Errorf("expected DisplayName 'myserver', got %q", p.DisplayName) - } - if p.Host != "srv.example.com" { - t.Errorf("expected host 'srv.example.com', got %q", p.Host) - } - if p.SSH == nil { - t.Fatal("expected SSH config to be set") - } - if !p.SSH.AgentAuth { - t.Error("expected AgentAuth to be true for ForwardAgent host") - } - - jp := profiles[1] - if jp.SSH == nil || len(jp.SSH.JumpHosts) == 0 { - t.Fatal("expected jump host from ProxyJump") - } - jh := jp.SSH.JumpHosts[0] - if jh.Host != "gw.example.com" { - t.Errorf("expected jump host 'gw.example.com', got %q", jh.Host) - } - if jh.Username != "gateway" { - t.Errorf("expected jump user 'gateway', got %q", jh.Username) - } -} - -func TestParseProxyJump(t *testing.T) { - tests := []struct { - input string - wantHost string - wantUser string - wantPort int - }{ - {"bastion.example.com", "bastion.example.com", "", 22}, - {"user@bastion.example.com", "bastion.example.com", "user", 22}, - {"user@bastion.example.com:2200", "bastion.example.com", "user", 2200}, - {"host:443", "host", "", 443}, - } - - for _, tt := range tests { - t.Run(tt.input, func(t *testing.T) { - jh := parseProxyJump(tt.input) - if jh == nil { - t.Fatal("expected non-nil result") - } - if jh.Host != tt.wantHost { - t.Errorf("host: got %q, want %q", jh.Host, tt.wantHost) - } - if jh.Username != tt.wantUser { - t.Errorf("user: got %q, want %q", jh.Username, tt.wantUser) - } - if jh.Port != tt.wantPort { - t.Errorf("port: got %d, want %d", jh.Port, tt.wantPort) - } - }) - } -} diff --git a/internal/storage/migrations/001_initial.sql b/internal/storage/migrations/001_initial.sql deleted file mode 100644 index 6b42d59..0000000 --- a/internal/storage/migrations/001_initial.sql +++ /dev/null @@ -1,63 +0,0 @@ --- Initial schema for Alchementary Link --- Creates the core tables for profiles, known hosts, preferences, and audit log. - -CREATE TABLE IF NOT EXISTS profiles ( - id TEXT PRIMARY KEY, - display_name TEXT NOT NULL, - description TEXT DEFAULT '', - protocol TEXT NOT NULL CHECK(protocol IN ('ssh', 'telnet', 'raw')), - host TEXT NOT NULL, - port INTEGER NOT NULL, - username TEXT DEFAULT '', - tags TEXT DEFAULT '[]', - folder TEXT DEFAULT '', - environment TEXT DEFAULT '', - notes TEXT DEFAULT '', - favourite INTEGER DEFAULT 0, - last_connected TEXT, - connect_timeout INTEGER DEFAULT 30, - read_timeout INTEGER DEFAULT 0, - terminal_font_family TEXT DEFAULT '', - terminal_font_size INTEGER DEFAULT 0, - terminal_scrollback INTEGER DEFAULT 0, - terminal_cursor_style TEXT DEFAULT '', - ssh_config TEXT, - telnet_config TEXT, - raw_config TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS known_hosts ( - id TEXT PRIMARY KEY, - host TEXT NOT NULL, - port INTEGER NOT NULL, - key_type TEXT NOT NULL, - fingerprint TEXT NOT NULL, - public_key TEXT NOT NULL, - first_seen TEXT NOT NULL, - last_seen TEXT NOT NULL, - trusted INTEGER DEFAULT 1, - UNIQUE(host, port, key_type) -); - -CREATE TABLE IF NOT EXISTS preferences ( - key TEXT PRIMARY KEY, - value TEXT NOT NULL -); - -CREATE TABLE IF NOT EXISTS audit_log ( - id TEXT PRIMARY KEY, - timestamp TEXT NOT NULL, - action TEXT NOT NULL, - profile_id TEXT DEFAULT '', - session_id TEXT DEFAULT '', - details TEXT DEFAULT '' -); - -CREATE TABLE IF NOT EXISTS schema_version ( - version INTEGER PRIMARY KEY, - applied_at TEXT NOT NULL -); - -INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (1, datetime('now')); diff --git a/internal/storage/migrations/002_github_protocol.sql b/internal/storage/migrations/002_github_protocol.sql deleted file mode 100644 index c13e807..0000000 --- a/internal/storage/migrations/002_github_protocol.sql +++ /dev/null @@ -1,59 +0,0 @@ --- Migration 002: Add GitHub protocol support --- Adds the 'github' value to the protocol CHECK constraint and a github_config column. - --- SQLite does not support ALTER TABLE to modify CHECK constraints or add columns --- with complex defaults atomically. We recreate the profiles table. - --- Step 1: Create the new table with the updated constraint. -CREATE TABLE IF NOT EXISTS profiles_new ( - id TEXT PRIMARY KEY, - display_name TEXT NOT NULL, - description TEXT DEFAULT '', - protocol TEXT NOT NULL CHECK(protocol IN ('ssh', 'telnet', 'raw', 'github')), - host TEXT NOT NULL, - port INTEGER NOT NULL, - username TEXT DEFAULT '', - tags TEXT DEFAULT '[]', - folder TEXT DEFAULT '', - environment TEXT DEFAULT '', - notes TEXT DEFAULT '', - favourite INTEGER DEFAULT 0, - last_connected TEXT, - connect_timeout INTEGER DEFAULT 30, - read_timeout INTEGER DEFAULT 0, - terminal_font_family TEXT DEFAULT '', - terminal_font_size INTEGER DEFAULT 14, - terminal_scrollback INTEGER DEFAULT 10000, - terminal_cursor_style TEXT DEFAULT 'block', - ssh_config TEXT, - telnet_config TEXT, - raw_config TEXT, - github_config TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL -); - --- Step 2: Copy existing data (github_config will be NULL for legacy rows). -INSERT OR IGNORE INTO profiles_new ( - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, - created_at, updated_at -) -SELECT - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, NULL, - created_at, updated_at -FROM profiles; - --- Step 3: Drop old table and rename. -DROP TABLE profiles; -ALTER TABLE profiles_new RENAME TO profiles; - --- Step 4: Record the migration version. -INSERT INTO schema_version (version, applied_at) VALUES (2, datetime('now')); diff --git a/internal/storage/migrations/003_snippets.sql b/internal/storage/migrations/003_snippets.sql deleted file mode 100644 index 9145df0..0000000 --- a/internal/storage/migrations/003_snippets.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Migration 003: Add snippets table for the command snippet library. --- Stores reusable commands that can be sent to active sessions. - -CREATE TABLE IF NOT EXISTS snippets ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - command TEXT NOT NULL, - description TEXT NOT NULL DEFAULT '', - folder TEXT NOT NULL DEFAULT '', - tags TEXT NOT NULL DEFAULT '[]', - created_at TEXT NOT NULL DEFAULT (datetime('now')), - updated_at TEXT NOT NULL DEFAULT (datetime('now')) -); - -INSERT INTO schema_version (version, applied_at) VALUES (3, datetime('now')); diff --git a/internal/storage/migrations/004_vnc_protocol.sql b/internal/storage/migrations/004_vnc_protocol.sql deleted file mode 100644 index 6710d49..0000000 --- a/internal/storage/migrations/004_vnc_protocol.sql +++ /dev/null @@ -1,105 +0,0 @@ --- Migration 004: Add VNC protocol support --- Adds the 'vnc' value to the protocol CHECK constraint and a vnc_config column. --- Step 1: Create the new table with the updated constraint. -CREATE TABLE IF NOT EXISTS profiles_new ( - id TEXT PRIMARY KEY, - display_name TEXT NOT NULL, - description TEXT DEFAULT '', - protocol TEXT NOT NULL CHECK( - protocol IN ('ssh', 'telnet', 'raw', 'github', 'vnc') - ), - host TEXT NOT NULL, - port INTEGER NOT NULL, - username TEXT DEFAULT '', - tags TEXT DEFAULT '[]', - folder TEXT DEFAULT '', - environment TEXT DEFAULT '', - notes TEXT DEFAULT '', - favourite INTEGER DEFAULT 0, - last_connected TEXT, - connect_timeout INTEGER DEFAULT 30, - read_timeout INTEGER DEFAULT 0, - terminal_font_family TEXT DEFAULT '', - terminal_font_size INTEGER DEFAULT 14, - terminal_scrollback INTEGER DEFAULT 10000, - terminal_cursor_style TEXT DEFAULT 'block', - ssh_config TEXT, - telnet_config TEXT, - raw_config TEXT, - github_config TEXT, - vnc_config TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL -); - --- Step 2: Copy existing data (vnc_config will be NULL for legacy rows). -INSERT - OR IGNORE INTO profiles_new ( - id, - display_name, - description, - protocol, - host, - port, - username, - tags, - folder, - environment, - notes, - favourite, - last_connected, - connect_timeout, - read_timeout, - terminal_font_family, - terminal_font_size, - terminal_scrollback, - terminal_cursor_style, - ssh_config, - telnet_config, - raw_config, - github_config, - vnc_config, - created_at, - updated_at - ) -SELECT - id, - display_name, - description, - protocol, - host, - port, - username, - tags, - folder, - environment, - notes, - favourite, - last_connected, - connect_timeout, - read_timeout, - terminal_font_family, - terminal_font_size, - terminal_scrollback, - terminal_cursor_style, - ssh_config, - telnet_config, - raw_config, - github_config, - NULL, - created_at, - updated_at -FROM - profiles; - --- Step 3: Drop old table and rename. -DROP TABLE profiles; - -ALTER TABLE - profiles_new RENAME TO profiles; - --- Step 4: Record the migration version. -INSERT INTO - schema_version (version, applied_at) -VALUES - (4, datetime('now')); \ No newline at end of file diff --git a/internal/storage/migrations/005_irc_protocol.sql b/internal/storage/migrations/005_irc_protocol.sql deleted file mode 100644 index d86aea2..0000000 --- a/internal/storage/migrations/005_irc_protocol.sql +++ /dev/null @@ -1,105 +0,0 @@ --- Migration 005: Add IRC protocol support --- Adds the 'irc' value to the protocol CHECK constraint and an irc_config column. - -CREATE TABLE IF NOT EXISTS profiles_new ( - id TEXT PRIMARY KEY, - display_name TEXT NOT NULL, - description TEXT DEFAULT '', - protocol TEXT NOT NULL CHECK( - protocol IN ('ssh', 'telnet', 'raw', 'github', 'vnc', 'irc') - ), - host TEXT NOT NULL, - port INTEGER NOT NULL, - username TEXT DEFAULT '', - tags TEXT DEFAULT '[]', - folder TEXT DEFAULT '', - environment TEXT DEFAULT '', - notes TEXT DEFAULT '', - favourite INTEGER DEFAULT 0, - last_connected TEXT, - connect_timeout INTEGER DEFAULT 30, - read_timeout INTEGER DEFAULT 0, - terminal_font_family TEXT DEFAULT '', - terminal_font_size INTEGER DEFAULT 14, - terminal_scrollback INTEGER DEFAULT 10000, - terminal_cursor_style TEXT DEFAULT 'block', - ssh_config TEXT, - telnet_config TEXT, - raw_config TEXT, - github_config TEXT, - vnc_config TEXT, - irc_config TEXT, - created_at TEXT NOT NULL, - updated_at TEXT NOT NULL -); - -INSERT - OR IGNORE INTO profiles_new ( - id, - display_name, - description, - protocol, - host, - port, - username, - tags, - folder, - environment, - notes, - favourite, - last_connected, - connect_timeout, - read_timeout, - terminal_font_family, - terminal_font_size, - terminal_scrollback, - terminal_cursor_style, - ssh_config, - telnet_config, - raw_config, - github_config, - vnc_config, - irc_config, - created_at, - updated_at - ) -SELECT - id, - display_name, - description, - protocol, - host, - port, - username, - tags, - folder, - environment, - notes, - favourite, - last_connected, - connect_timeout, - read_timeout, - terminal_font_family, - terminal_font_size, - terminal_scrollback, - terminal_cursor_style, - ssh_config, - telnet_config, - raw_config, - github_config, - vnc_config, - NULL, - created_at, - updated_at -FROM - profiles; - -DROP TABLE profiles; - -ALTER TABLE - profiles_new RENAME TO profiles; - -INSERT INTO - schema_version (version, applied_at) -VALUES - (5, datetime('now')); diff --git a/internal/storage/migrations/006_irc_channel_state.sql b/internal/storage/migrations/006_irc_channel_state.sql deleted file mode 100644 index 08c71f7..0000000 --- a/internal/storage/migrations/006_irc_channel_state.sql +++ /dev/null @@ -1,15 +0,0 @@ --- Migration 006: Remember the channels open for each saved IRC profile. - -CREATE TABLE IF NOT EXISTS irc_channel_state ( - profile_id TEXT NOT NULL, - room_name TEXT NOT NULL, - position INTEGER NOT NULL DEFAULT 0, - updated_at TEXT NOT NULL, - PRIMARY KEY (profile_id, room_name) -); - -CREATE INDEX IF NOT EXISTS idx_irc_channel_state_profile_position - ON irc_channel_state (profile_id, position); - -INSERT INTO schema_version (version, applied_at) -VALUES (6, datetime('now')); diff --git a/internal/storage/migrations/migrations.go b/internal/storage/migrations/migrations.go deleted file mode 100644 index 32e1ec3..0000000 --- a/internal/storage/migrations/migrations.go +++ /dev/null @@ -1,10 +0,0 @@ -// Package migrations provides embedded SQL migration files for the storage layer. -package migrations - -import "embed" - -// MigrationFS contains all embedded SQL migration files. -// Each migration is numbered sequentially (e.g. 001_initial.sql). -// -//go:embed *.sql -var MigrationFS embed.FS diff --git a/internal/storage/storage_test.go b/internal/storage/storage_test.go deleted file mode 100644 index 6b7a686..0000000 --- a/internal/storage/storage_test.go +++ /dev/null @@ -1,695 +0,0 @@ -package storage - -import ( - "encoding/json" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/andydixon/link/internal/models" - "github.com/google/uuid" -) - -// setupTestStore creates an isolated Store backed by a temporary SQLite database. -func setupTestStore(t *testing.T) *Store { - t.Helper() - dir := t.TempDir() - dbPath := filepath.Join(dir, "test.db") - store, err := NewStore(dbPath) - if err != nil { - t.Fatalf("NewStore() error = %v", err) - } - t.Cleanup(func() { store.Close() }) - return store -} - -// makeSSHProfile returns a minimal valid SSH profile for testing. -func makeSSHProfile(name, host string) *models.Profile { - p := models.NewProfile(models.ProtocolSSH) - p.DisplayName = name - p.Host = host - p.Username = "testuser" - return &p -} - -func makeIRCProfile(name, host string) *models.Profile { - p := models.NewProfile(models.ProtocolIRC) - p.DisplayName = name - p.Host = host - p.Username = "link" - p.IRC.Nick = "link" - return &p -} - -// --------------------------------------------------------------------------- -// Profile CRUD -// --------------------------------------------------------------------------- - -func TestCreateAndGetProfile(t *testing.T) { - store := setupTestStore(t) - p := makeSSHProfile("My Server", "10.0.0.1") - - if err := store.CreateProfile(p); err != nil { - t.Fatalf("CreateProfile() error = %v", err) - } - - got, err := store.GetProfile(p.ID) - if err != nil { - t.Fatalf("GetProfile() error = %v", err) - } - if got.DisplayName != "My Server" { - t.Errorf("DisplayName = %q, want %q", got.DisplayName, "My Server") - } - if got.Host != "10.0.0.1" { - t.Errorf("Host = %q, want %q", got.Host, "10.0.0.1") - } - if got.Protocol != models.ProtocolSSH { - t.Errorf("Protocol = %q, want %q", got.Protocol, models.ProtocolSSH) - } -} - -func TestUpdateProfile(t *testing.T) { - store := setupTestStore(t) - p := makeSSHProfile("Original", "host.example.com") - _ = store.CreateProfile(p) - - p.DisplayName = "Updated" - p.Host = "new-host.example.com" - p.UpdatedAt = time.Now() - if err := store.UpdateProfile(p); err != nil { - t.Fatalf("UpdateProfile() error = %v", err) - } - - got, err := store.GetProfile(p.ID) - if err != nil { - t.Fatalf("GetProfile() error = %v", err) - } - if got.DisplayName != "Updated" { - t.Errorf("DisplayName = %q, want %q", got.DisplayName, "Updated") - } - if got.Host != "new-host.example.com" { - t.Errorf("Host = %q, want %q", got.Host, "new-host.example.com") - } -} - -func TestDeleteProfile(t *testing.T) { - store := setupTestStore(t) - p := makeSSHProfile("ToDelete", "host.local") - _ = store.CreateProfile(p) - - if err := store.DeleteProfile(p.ID); err != nil { - t.Fatalf("DeleteProfile() error = %v", err) - } - if _, err := store.GetProfile(p.ID); err == nil { - t.Error("expected error when getting deleted profile") - } -} - -func TestDeleteProfileNotFound(t *testing.T) { - store := setupTestStore(t) - if err := store.DeleteProfile("nonexistent"); err == nil { - t.Error("expected error when deleting non-existent profile") - } -} - -func TestIRCLastRoomsRoundTripReplaceAndDelete(t *testing.T) { - store := setupTestStore(t) - p := makeIRCProfile("IRC Network", "irc.example.com") - if err := store.CreateProfile(p); err != nil { - t.Fatalf("CreateProfile() error = %v", err) - } - - if err := store.SaveIRCLastRooms(p.ID, []string{" #Go-Nuts ", "#link", "#go-nuts", ""}); err != nil { - t.Fatalf("SaveIRCLastRooms() error = %v", err) - } - rooms, err := store.GetIRCLastRooms(p.ID) - if err != nil { - t.Fatalf("GetIRCLastRooms() error = %v", err) - } - if got, want := strings.Join(rooms, ","), "#Go-Nuts,#link"; got != want { - t.Fatalf("GetIRCLastRooms() = %q, want %q", got, want) - } - - if err := store.SaveIRCLastRooms(p.ID, []string{"#new-room"}); err != nil { - t.Fatalf("SaveIRCLastRooms(replace) error = %v", err) - } - rooms, err = store.GetIRCLastRooms(p.ID) - if err != nil { - t.Fatalf("GetIRCLastRooms(replaced) error = %v", err) - } - if got, want := strings.Join(rooms, ","), "#new-room"; got != want { - t.Fatalf("GetIRCLastRooms(replaced) = %q, want %q", got, want) - } - - if err := store.DeleteProfile(p.ID); err != nil { - t.Fatalf("DeleteProfile() error = %v", err) - } - rooms, err = store.GetIRCLastRooms(p.ID) - if err != nil { - t.Fatalf("GetIRCLastRooms(after delete) error = %v", err) - } - if len(rooms) != 0 { - t.Fatalf("GetIRCLastRooms(after delete) = %v, want empty", rooms) - } -} - -func TestSaveIRCLastRoomsRejectsEmptyProfileID(t *testing.T) { - store := setupTestStore(t) - if err := store.SaveIRCLastRooms(" ", []string{"#room"}); err == nil { - t.Fatal("SaveIRCLastRooms() accepted an empty profile ID") - } -} - -// --------------------------------------------------------------------------- -// List and search -// --------------------------------------------------------------------------- - -func TestListProfiles(t *testing.T) { - store := setupTestStore(t) - _ = store.CreateProfile(makeSSHProfile("Alpha", "a.example.com")) - _ = store.CreateProfile(makeSSHProfile("Beta", "b.example.com")) - - profiles, err := store.ListProfiles() - if err != nil { - t.Fatalf("ListProfiles() error = %v", err) - } - if len(profiles) != 2 { - t.Errorf("ListProfiles() returned %d profiles, want 2", len(profiles)) - } -} - -func TestSearchProfiles(t *testing.T) { - store := setupTestStore(t) - _ = store.CreateProfile(makeSSHProfile("Production Web", "web.prod.com")) - _ = store.CreateProfile(makeSSHProfile("Staging DB", "db.staging.com")) - - results, err := store.SearchProfiles("prod") - if err != nil { - t.Fatalf("SearchProfiles() error = %v", err) - } - if len(results) != 1 { - t.Fatalf("SearchProfiles(\"prod\") returned %d results, want 1", len(results)) - } - if results[0].DisplayName != "Production Web" { - t.Errorf("DisplayName = %q, want %q", results[0].DisplayName, "Production Web") - } -} - -func TestSearchProfilesNoResults(t *testing.T) { - store := setupTestStore(t) - _ = store.CreateProfile(makeSSHProfile("Foo", "foo.local")) - - results, err := store.SearchProfiles("zzz-no-match") - if err != nil { - t.Fatalf("SearchProfiles() error = %v", err) - } - if len(results) != 0 { - t.Errorf("expected 0 results, got %d", len(results)) - } -} - -func TestListProfilesByProtocol(t *testing.T) { - store := setupTestStore(t) - - ssh := makeSSHProfile("SSH Server", "ssh.local") - _ = store.CreateProfile(ssh) - - telnet := models.NewProfile(models.ProtocolTelnet) - telnet.DisplayName = "Telnet Switch" - telnet.Host = "switch.local" - _ = store.CreateProfile(&telnet) - - sshResults, err := store.ListProfilesByProtocol("ssh") - if err != nil { - t.Fatalf("ListProfilesByProtocol(ssh) error = %v", err) - } - if len(sshResults) != 1 { - t.Errorf("expected 1 SSH profile, got %d", len(sshResults)) - } - - telnetResults, err := store.ListProfilesByProtocol("telnet") - if err != nil { - t.Fatalf("ListProfilesByProtocol(telnet) error = %v", err) - } - if len(telnetResults) != 1 { - t.Errorf("expected 1 Telnet profile, got %d", len(telnetResults)) - } -} - -// --------------------------------------------------------------------------- -// Favourites -// --------------------------------------------------------------------------- - -func TestFavourites(t *testing.T) { - store := setupTestStore(t) - p := makeSSHProfile("Fav Server", "fav.local") - _ = store.CreateProfile(p) - - // Initially no favourites. - favs, err := store.ListFavourites() - if err != nil { - t.Fatalf("ListFavourites() error = %v", err) - } - if len(favs) != 0 { - t.Fatalf("expected 0 favourites, got %d", len(favs)) - } - - // Mark as favourite. - if err := store.SetFavourite(p.ID, true); err != nil { - t.Fatalf("SetFavourite(true) error = %v", err) - } - favs, _ = store.ListFavourites() - if len(favs) != 1 { - t.Fatalf("expected 1 favourite, got %d", len(favs)) - } - - // Unmark. - if err := store.SetFavourite(p.ID, false); err != nil { - t.Fatalf("SetFavourite(false) error = %v", err) - } - favs, _ = store.ListFavourites() - if len(favs) != 0 { - t.Errorf("expected 0 favourites after unmark, got %d", len(favs)) - } -} - -// --------------------------------------------------------------------------- -// Known hosts -// --------------------------------------------------------------------------- - -func TestKnownHostCRUD(t *testing.T) { - store := setupTestStore(t) - now := time.Now() - - kh := &models.KnownHost{ - ID: uuid.New().String(), - Host: "server.example.com", - Port: 22, - KeyType: "ssh-ed25519", - Fingerprint: "SHA256:abcdef1234567890", - PublicKey: "AAAAC3NzaC1lZDI1NTE5...", - FirstSeen: now, - LastSeen: now, - Trusted: true, - } - - // Save and retrieve. - if err := store.SaveKnownHost(kh); err != nil { - t.Fatalf("SaveKnownHost() error = %v", err) - } - got, err := store.GetKnownHost("server.example.com", 22) - if err != nil { - t.Fatalf("GetKnownHost() error = %v", err) - } - if got == nil { - t.Fatal("GetKnownHost() returned nil") - } - if got.Fingerprint != kh.Fingerprint { - t.Errorf("Fingerprint = %q, want %q", got.Fingerprint, kh.Fingerprint) - } - - // List should contain it. - hosts, err := store.ListKnownHosts() - if err != nil { - t.Fatalf("ListKnownHosts() error = %v", err) - } - if len(hosts) != 1 { - t.Fatalf("expected 1 known host, got %d", len(hosts)) - } - - // Delete. - if err := store.DeleteKnownHost(kh.ID); err != nil { - t.Fatalf("DeleteKnownHost() error = %v", err) - } - hosts, _ = store.ListKnownHosts() - if len(hosts) != 0 { - t.Errorf("expected 0 known hosts after delete, got %d", len(hosts)) - } -} - -func TestGetKnownHostNotFound(t *testing.T) { - store := setupTestStore(t) - got, err := store.GetKnownHost("no-such-host", 22) - if err != nil { - t.Fatalf("GetKnownHost() error = %v", err) - } - if got != nil { - t.Error("expected nil for unknown host") - } -} - -// --------------------------------------------------------------------------- -// Preferences -// --------------------------------------------------------------------------- - -func TestPreferencesDefaults(t *testing.T) { - store := setupTestStore(t) - - // No preferences stored yet — should return nil. - prefs, err := store.GetPreferences() - if err != nil { - t.Fatalf("GetPreferences() error = %v", err) - } - if prefs != nil { - t.Error("expected nil preferences on empty database") - } -} - -func TestSavePreferencesDoesNotError(t *testing.T) { - store := setupTestStore(t) - - prefs := models.DefaultAppPreferences() - prefs.ConfirmOnClose = true - prefs.DefaultFontSize = 16 - prefs.Theme = "custom-dark" - prefs.LeviathanEnabled = true - - if err := store.SavePreferences(&prefs); err != nil { - t.Fatalf("SavePreferences() error = %v", err) - } - - got, err := store.GetPreferences() - if err != nil { - t.Fatalf("GetPreferences() error = %v", err) - } - if got == nil { - t.Fatal("GetPreferences() returned nil after save") - } - if got.ConfirmOnClose != true { - t.Errorf("ConfirmOnClose = %v, want true", got.ConfirmOnClose) - } - if got.DefaultFontSize != 16 { - t.Errorf("DefaultFontSize = %d, want 16", got.DefaultFontSize) - } - if got.Theme != "custom-dark" { - t.Errorf("Theme = %q, want %q", got.Theme, "custom-dark") - } - if !got.LeviathanEnabled { - t.Error("LeviathanEnabled = false, want true") - } -} - -// --------------------------------------------------------------------------- -// Audit log -// --------------------------------------------------------------------------- - -func TestAuditEntries(t *testing.T) { - store := setupTestStore(t) - - entry := &models.AuditEntry{ - ID: uuid.New().String(), - Timestamp: time.Now(), - Action: "connect", - ProfileID: "profile-1", - SessionID: "session-1", - Details: "Connected via SSH to 10.0.0.1", - } - - if err := store.AddAuditEntry(entry); err != nil { - t.Fatalf("AddAuditEntry() error = %v", err) - } - - entries, err := store.ListAuditEntries(10) - if err != nil { - t.Fatalf("ListAuditEntries() error = %v", err) - } - if len(entries) != 1 { - t.Fatalf("expected 1 audit entry, got %d", len(entries)) - } - if entries[0].Action != "connect" { - t.Errorf("Action = %q, want %q", entries[0].Action, "connect") - } - if entries[0].Details != "Connected via SSH to 10.0.0.1" { - t.Errorf("Details = %q, want %q", entries[0].Details, "Connected via SSH to 10.0.0.1") - } -} - -func TestAuditEntriesLimit(t *testing.T) { - store := setupTestStore(t) - - for i := 0; i < 5; i++ { - _ = store.AddAuditEntry(&models.AuditEntry{ - ID: uuid.New().String(), - Timestamp: time.Now(), - Action: "test", - Details: "entry", - }) - } - - entries, err := store.ListAuditEntries(3) - if err != nil { - t.Fatalf("ListAuditEntries() error = %v", err) - } - if len(entries) != 3 { - t.Errorf("expected 3 audit entries with limit, got %d", len(entries)) - } -} - -// --------------------------------------------------------------------------- -// UpdateLastConnected -// --------------------------------------------------------------------------- - -func TestUpdateLastConnected(t *testing.T) { - store := setupTestStore(t) - p := makeSSHProfile("Connectable", "conn.local") - _ = store.CreateProfile(p) - - // Initially last_connected should be nil. - got, err := store.GetProfile(p.ID) - if err != nil { - t.Fatalf("GetProfile() error = %v", err) - } - if got.LastConnected != nil { - t.Error("expected LastConnected to be nil initially") - } - - // Update last connected. - if err := store.UpdateLastConnected(p.ID); err != nil { - t.Fatalf("UpdateLastConnected() error = %v", err) - } - - got, err = store.GetProfile(p.ID) - if err != nil { - t.Fatalf("GetProfile() after update error = %v", err) - } - if got.LastConnected == nil { - t.Fatal("expected LastConnected to be set after update") - } - // Should be within the last few seconds. - elapsed := time.Since(*got.LastConnected) - if elapsed > 5*time.Second { - t.Errorf("LastConnected is too old: %v ago", elapsed) - } -} - -func TestUpdateLastConnectedNotFound(t *testing.T) { - store := setupTestStore(t) - if err := store.UpdateLastConnected("nonexistent"); err == nil { - t.Error("expected error when updating non-existent profile") - } -} - -// --------------------------------------------------------------------------- -// Export / Import -// --------------------------------------------------------------------------- - -func TestExportProfiles(t *testing.T) { - store := setupTestStore(t) - _ = store.CreateProfile(makeSSHProfile("Export A", "a.example.com")) - _ = store.CreateProfile(makeSSHProfile("Export B", "b.example.com")) - - data, err := store.ExportProfiles(false) - if err != nil { - t.Fatalf("ExportProfiles() error = %v", err) - } - if len(data) == 0 { - t.Fatal("ExportProfiles() returned empty data") - } - - // The exported JSON should be parseable as a slice of profiles. - var profiles []*models.Profile - if err := json.Unmarshal(data, &profiles); err != nil { - t.Fatalf("failed to unmarshal exported data: %v", err) - } - if len(profiles) != 2 { - t.Errorf("expected 2 exported profiles, got %d", len(profiles)) - } -} - -func TestExportProfilesRedactsInlineSSHSecrets(t *testing.T) { - store := setupTestStore(t) - profile := makeSSHProfile("Export Secret", "secret.example.com") - profile.SSH.ProxyPassword = "proxy-secret" - profile.SSH.TOTPSecret = "totp-secret" - if err := store.CreateProfile(profile); err != nil { - t.Fatalf("CreateProfile() error = %v", err) - } - - data, err := store.ExportProfiles(false) - if err != nil { - t.Fatalf("ExportProfiles(false) error = %v", err) - } - if strings.Contains(string(data), "proxy-secret") || strings.Contains(string(data), "totp-secret") { - t.Fatalf("ExportProfiles(false) leaked inline SSH secrets: %s", string(data)) - } - - data, err = store.ExportProfiles(true) - if err != nil { - t.Fatalf("ExportProfiles(true) error = %v", err) - } - if !strings.Contains(string(data), "proxy-secret") || !strings.Contains(string(data), "totp-secret") { - t.Fatalf("ExportProfiles(true) did not include inline SSH secrets: %s", string(data)) - } -} - -func TestImportProfiles(t *testing.T) { - store := setupTestStore(t) - p := makeSSHProfile("Import Me", "import.local") - _ = store.CreateProfile(p) - - // Export from source store. - data, err := store.ExportProfiles(false) - if err != nil { - t.Fatalf("ExportProfiles() error = %v", err) - } - - // Import into a fresh store. - store2 := setupTestStore(t) - n, err := store2.ImportProfiles(data, "skip") - if err != nil { - t.Fatalf("ImportProfiles() error = %v", err) - } - if n != 1 { - t.Errorf("expected 1 imported profile, got %d", n) - } - - profiles, _ := store2.ListProfiles() - if len(profiles) != 1 { - t.Fatalf("expected 1 profile in target store, got %d", len(profiles)) - } - if profiles[0].DisplayName != "Import Me" { - t.Errorf("DisplayName = %q, want %q", profiles[0].DisplayName, "Import Me") - } -} - -func TestImportDuplicateHandling(t *testing.T) { - store := setupTestStore(t) - p := makeSSHProfile("Original", "orig.local") - _ = store.CreateProfile(p) - - data, err := store.ExportProfiles(false) - if err != nil { - t.Fatalf("ExportProfiles() error = %v", err) - } - - // "skip" strategy — importing same profile again should import 0. - n, err := store.ImportProfiles(data, "skip") - if err != nil { - t.Fatalf("ImportProfiles(skip) error = %v", err) - } - if n != 0 { - t.Errorf("skip strategy: expected 0 imports, got %d", n) - } - - // "overwrite" strategy — should overwrite the existing profile. - n, err = store.ImportProfiles(data, "overwrite") - if err != nil { - t.Fatalf("ImportProfiles(overwrite) error = %v", err) - } - if n != 1 { - t.Errorf("overwrite strategy: expected 1 import, got %d", n) - } - // Total profiles should still be 1. - all, _ := store.ListProfiles() - if len(all) != 1 { - t.Errorf("after overwrite: expected 1 profile, got %d", len(all)) - } - - // "duplicate" strategy — should create a new entry with a fresh ID. - n, err = store.ImportProfiles(data, "duplicate") - if err != nil { - t.Fatalf("ImportProfiles(duplicate) error = %v", err) - } - if n != 1 { - t.Errorf("duplicate strategy: expected 1 import, got %d", n) - } - all, _ = store.ListProfiles() - if len(all) != 2 { - t.Errorf("after duplicate: expected 2 profiles, got %d", len(all)) - } -} - -func TestImportInvalidStrategy(t *testing.T) { - store := setupTestStore(t) - data := []byte(`[]`) - _, err := store.ImportProfiles(data, "badstrategy") - if err == nil { - t.Error("expected error for invalid import strategy") - } -} - -func TestImportInvalidJSON(t *testing.T) { - store := setupTestStore(t) - _, err := store.ImportProfiles([]byte("not json"), "skip") - if err == nil { - t.Error("expected error for invalid JSON import data") - } -} - -func TestImportRejectsNullProfileEntry(t *testing.T) { - store := setupTestStore(t) - _, err := store.ImportProfiles([]byte(`[null]`), "skip") - if err == nil { - t.Fatal("expected error for null profile entry") - } - if !strings.Contains(err.Error(), "entry 0 is null") { - t.Fatalf("error = %q, want null entry detail", err.Error()) - } -} - -// --------------------------------------------------------------------------- -// Migration idempotency -// --------------------------------------------------------------------------- - -func TestMigrateIdempotent(t *testing.T) { - // NewStore already runs migrate() internally. Creating a second Store - // against the same database verifies that migrations are idempotent. - dir := t.TempDir() - dbPath := filepath.Join(dir, "migrate-test.db") - - s1, err := NewStore(dbPath) - if err != nil { - t.Fatalf("first NewStore() error = %v", err) - } - s1.Close() - - s2, err := NewStore(dbPath) - if err != nil { - t.Fatalf("second NewStore() error = %v (migration not idempotent?)", err) - } - s2.Close() -} - -func TestMigrateCreatesTablesForCRUD(t *testing.T) { - // Verify that a fresh store can perform basic CRUD — proving that - // migration created the required tables. - store := setupTestStore(t) - - p := makeSSHProfile("Migration Test", "mig.local") - if err := store.CreateProfile(p); err != nil { - t.Fatalf("CreateProfile after migration failed: %v", err) - } - if _, err := store.GetProfile(p.ID); err != nil { - t.Fatalf("GetProfile after migration failed: %v", err) - } - if _, err := store.ListKnownHosts(); err != nil { - t.Fatalf("ListKnownHosts after migration failed: %v", err) - } - if _, err := store.ListAuditEntries(10); err != nil { - t.Fatalf("ListAuditEntries after migration failed: %v", err) - } - if _, err := store.GetPreferences(); err != nil { - t.Fatalf("GetPreferences after migration failed: %v", err) - } -} diff --git a/internal/storage/store.go b/internal/storage/store.go deleted file mode 100644 index 54f7097..0000000 --- a/internal/storage/store.go +++ /dev/null @@ -1,939 +0,0 @@ -// Package storage provides a SQLite-backed persistence layer for profiles, -// known hosts, audit entries, and application preferences. -package storage - -import ( - "database/sql" - "encoding/json" - "fmt" - "strings" - "time" - - _ "modernc.org/sqlite" - - "github.com/andydixon/link/internal/models" - "github.com/andydixon/link/internal/storage/migrations" - "github.com/google/uuid" -) - -// Store wraps a SQLite database and exposes typed CRUD methods for -// all application entities. -type Store struct { - db *sql.DB -} - -// NewStore opens (or creates) the SQLite database at dbPath, applies any -// pending migrations, and returns an initialised Store. -func NewStore(dbPath string) (*Store, error) { - db, err := sql.Open("sqlite", dbPath) - if err != nil { - return nil, fmt.Errorf("storage: failed to open database: %w", err) - } - if err := db.Ping(); err != nil { - return nil, fmt.Errorf("storage: database not reachable: %w", err) - } - if err := configureSQLite(db); err != nil { - _ = db.Close() - return nil, err - } - - s := &Store{db: db} - if err := s.migrate(); err != nil { - return nil, fmt.Errorf("storage: migration failed: %w", err) - } - return s, nil -} - -func configureSQLite(db *sql.DB) error { - db.SetMaxOpenConns(1) - - if _, err := db.Exec("PRAGMA busy_timeout = 5000"); err != nil { - return fmt.Errorf("storage: failed to set SQLite busy timeout: %w", err) - } - if _, err := db.Exec("PRAGMA journal_mode = WAL"); err != nil { - return fmt.Errorf("storage: failed to enable SQLite WAL mode: %w", err) - } - return nil -} - -// Close shuts down the database connection. -func (s *Store) Close() error { - return s.db.Close() -} - -// --------------------------------------------------------------------------- -// Migrations -// --------------------------------------------------------------------------- - -// migrate reads embedded SQL files and applies them in order. -func (s *Store) migrate() error { - // Ensure the schema_version table exists before querying it. - _, err := s.db.Exec(`CREATE TABLE IF NOT EXISTS schema_version ( - version INTEGER PRIMARY KEY, - applied_at TEXT NOT NULL - )`) - if err != nil { - return fmt.Errorf("failed to create schema_version table: %w", err) - } - - var current int - row := s.db.QueryRow("SELECT COALESCE(MAX(version), 0) FROM schema_version") - if err := row.Scan(¤t); err != nil { - return fmt.Errorf("failed to read schema version: %w", err) - } - - entries, err := migrations.MigrationFS.ReadDir(".") - if err != nil { - return fmt.Errorf("failed to read migration files: %w", err) - } - - for _, entry := range entries { - if entry.IsDir() { - continue - } - name := entry.Name() - if !strings.HasSuffix(name, ".sql") { - continue - } - // Extract version number from filename prefix (e.g. "001_initial.sql" → 1). - var ver int - if _, err := fmt.Sscanf(name, "%03d_", &ver); err != nil { - continue - } - if ver <= current { - continue - } - - data, err := migrations.MigrationFS.ReadFile(name) - if err != nil { - return fmt.Errorf("failed to read migration %s: %w", name, err) - } - if _, err := s.db.Exec(string(data)); err != nil { - return fmt.Errorf("failed to apply migration %s: %w", name, err) - } - } - return nil -} - -// --------------------------------------------------------------------------- -// Profile operations -// --------------------------------------------------------------------------- - -// CreateProfile inserts a new profile into the database. -func (s *Store) CreateProfile(p *models.Profile) error { - tagsJSON, _ := json.Marshal(p.Tags) - sshJSON, _ := json.Marshal(p.SSH) - telnetJSON, _ := json.Marshal(p.Telnet) - rawJSON, _ := json.Marshal(p.RAW) - githubJSON, _ := json.Marshal(p.GitHub) - vncJSON, _ := json.Marshal(p.VNC) - ircJSON, _ := json.Marshal(p.IRC) - - _, err := s.db.Exec(`INSERT INTO profiles ( - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, vnc_config, irc_config, - created_at, updated_at - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, - p.ID, p.DisplayName, p.Description, string(p.Protocol), - p.Host, p.Port, p.Username, - string(tagsJSON), p.Folder, p.Environment, p.Notes, - boolToInt(p.Favourite), timeToStr(p.LastConnected), - p.ConnectTimeout, p.ReadTimeout, - p.TerminalFontFamily, p.TerminalFontSize, p.TerminalScrollback, p.TerminalCursorStyle, - nullableJSON(p.SSH, sshJSON), nullableJSON(p.Telnet, telnetJSON), nullableJSON(p.RAW, rawJSON), - nullableJSON(p.GitHub, githubJSON), nullableJSON(p.VNC, vncJSON), nullableJSON(p.IRC, ircJSON), - p.CreatedAt.Format(time.RFC3339), p.UpdatedAt.Format(time.RFC3339), - ) - if err != nil { - return fmt.Errorf("storage: failed to create profile: %w", err) - } - return nil -} - -// GetProfile retrieves a single profile by ID. -func (s *Store) GetProfile(id string) (*models.Profile, error) { - row := s.db.QueryRow(`SELECT - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, vnc_config, irc_config, - created_at, updated_at - FROM profiles WHERE id = ?`, id) - - p, err := scanProfile(row) - if err == sql.ErrNoRows { - return nil, fmt.Errorf("storage: profile %q not found", id) - } - if err != nil { - return nil, fmt.Errorf("storage: failed to get profile: %w", err) - } - return p, nil -} - -// UpdateProfile overwrites an existing profile. -func (s *Store) UpdateProfile(p *models.Profile) error { - tagsJSON, _ := json.Marshal(p.Tags) - sshJSON, _ := json.Marshal(p.SSH) - telnetJSON, _ := json.Marshal(p.Telnet) - rawJSON, _ := json.Marshal(p.RAW) - githubJSON, _ := json.Marshal(p.GitHub) - vncJSON, _ := json.Marshal(p.VNC) - ircJSON, _ := json.Marshal(p.IRC) - - res, err := s.db.Exec(`UPDATE profiles SET - display_name=?, description=?, protocol=?, host=?, port=?, username=?, - tags=?, folder=?, environment=?, notes=?, favourite=?, last_connected=?, - connect_timeout=?, read_timeout=?, - terminal_font_family=?, terminal_font_size=?, terminal_scrollback=?, terminal_cursor_style=?, - ssh_config=?, telnet_config=?, raw_config=?, github_config=?, vnc_config=?, irc_config=?, - updated_at=? - WHERE id=?`, - p.DisplayName, p.Description, string(p.Protocol), - p.Host, p.Port, p.Username, - string(tagsJSON), p.Folder, p.Environment, p.Notes, - boolToInt(p.Favourite), timeToStr(p.LastConnected), - p.ConnectTimeout, p.ReadTimeout, - p.TerminalFontFamily, p.TerminalFontSize, p.TerminalScrollback, p.TerminalCursorStyle, - nullableJSON(p.SSH, sshJSON), nullableJSON(p.Telnet, telnetJSON), nullableJSON(p.RAW, rawJSON), - nullableJSON(p.GitHub, githubJSON), nullableJSON(p.VNC, vncJSON), nullableJSON(p.IRC, ircJSON), - p.UpdatedAt.Format(time.RFC3339), - p.ID, - ) - if err != nil { - return fmt.Errorf("storage: failed to update profile: %w", err) - } - n, _ := res.RowsAffected() - if n == 0 { - return fmt.Errorf("storage: profile %q not found", p.ID) - } - return nil -} - -// DeleteProfile removes a profile by ID. -func (s *Store) DeleteProfile(id string) error { - tx, err := s.db.Begin() - if err != nil { - return fmt.Errorf("storage: failed to begin profile deletion: %w", err) - } - defer func() { _ = tx.Rollback() }() - - if _, err := tx.Exec("DELETE FROM irc_channel_state WHERE profile_id = ?", id); err != nil { - return fmt.Errorf("storage: failed to delete IRC channel state: %w", err) - } - res, err := tx.Exec("DELETE FROM profiles WHERE id = ?", id) - if err != nil { - return fmt.Errorf("storage: failed to delete profile: %w", err) - } - n, _ := res.RowsAffected() - if n == 0 { - return fmt.Errorf("storage: profile %q not found", id) - } - if err := tx.Commit(); err != nil { - return fmt.Errorf("storage: failed to commit profile deletion: %w", err) - } - return nil -} - -// ListProfiles returns all profiles ordered by display name. -func (s *Store) ListProfiles() ([]*models.Profile, error) { - rows, err := s.db.Query(`SELECT - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, vnc_config, irc_config, - created_at, updated_at - FROM profiles ORDER BY display_name`) - if err != nil { - return nil, fmt.Errorf("storage: failed to list profiles: %w", err) - } - defer rows.Close() - return scanProfiles(rows) -} - -// SearchProfiles returns profiles whose display name, host, or tags contain -// the query string (case-insensitive). -func (s *Store) SearchProfiles(query string) ([]*models.Profile, error) { - q := "%" + query + "%" - rows, err := s.db.Query(`SELECT - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, vnc_config, irc_config, - created_at, updated_at - FROM profiles - WHERE display_name LIKE ? OR host LIKE ? OR tags LIKE ? - ORDER BY display_name`, q, q, q) - if err != nil { - return nil, fmt.Errorf("storage: failed to search profiles: %w", err) - } - defer rows.Close() - return scanProfiles(rows) -} - -// ListProfilesByProtocol returns profiles filtered by protocol. -func (s *Store) ListProfilesByProtocol(protocol string) ([]*models.Profile, error) { - rows, err := s.db.Query(`SELECT - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, vnc_config, irc_config, - created_at, updated_at - FROM profiles WHERE protocol = ? ORDER BY display_name`, protocol) - if err != nil { - return nil, fmt.Errorf("storage: failed to list profiles by protocol: %w", err) - } - defer rows.Close() - return scanProfiles(rows) -} - -// ListProfilesByTag returns profiles that contain the specified tag. -func (s *Store) ListProfilesByTag(tag string) ([]*models.Profile, error) { - q := `%"` + tag + `"%` - rows, err := s.db.Query(`SELECT - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, vnc_config, irc_config, - created_at, updated_at - FROM profiles WHERE tags LIKE ? ORDER BY display_name`, q) - if err != nil { - return nil, fmt.Errorf("storage: failed to list profiles by tag: %w", err) - } - defer rows.Close() - return scanProfiles(rows) -} - -// ListProfilesByFolder returns profiles in the specified folder. -func (s *Store) ListProfilesByFolder(folder string) ([]*models.Profile, error) { - rows, err := s.db.Query(`SELECT - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, vnc_config, irc_config, - created_at, updated_at - FROM profiles WHERE folder = ? ORDER BY display_name`, folder) - if err != nil { - return nil, fmt.Errorf("storage: failed to list profiles by folder: %w", err) - } - defer rows.Close() - return scanProfiles(rows) -} - -// ListFavourites returns all profiles marked as favourites. -func (s *Store) ListFavourites() ([]*models.Profile, error) { - rows, err := s.db.Query(`SELECT - id, display_name, description, protocol, host, port, username, - tags, folder, environment, notes, favourite, last_connected, - connect_timeout, read_timeout, - terminal_font_family, terminal_font_size, terminal_scrollback, terminal_cursor_style, - ssh_config, telnet_config, raw_config, github_config, vnc_config, irc_config, - created_at, updated_at - FROM profiles WHERE favourite = 1 ORDER BY display_name`) - if err != nil { - return nil, fmt.Errorf("storage: failed to list favourites: %w", err) - } - defer rows.Close() - return scanProfiles(rows) -} - -// SetFavourite updates the favourite flag on a profile. -func (s *Store) SetFavourite(id string, favourite bool) error { - res, err := s.db.Exec("UPDATE profiles SET favourite = ?, updated_at = ? WHERE id = ?", - boolToInt(favourite), time.Now().Format(time.RFC3339), id) - if err != nil { - return fmt.Errorf("storage: failed to update favourite: %w", err) - } - n, _ := res.RowsAffected() - if n == 0 { - return fmt.Errorf("storage: profile %q not found", id) - } - return nil -} - -// UpdateLastConnected sets the last_connected timestamp for the given profile -// to the current time. -func (s *Store) UpdateLastConnected(id string) error { - now := time.Now().Format(time.RFC3339) - res, err := s.db.Exec("UPDATE profiles SET last_connected = ?, updated_at = ? WHERE id = ?", - now, now, id) - if err != nil { - return fmt.Errorf("storage: failed to update last_connected: %w", err) - } - n, _ := res.RowsAffected() - if n == 0 { - return fmt.Errorf("storage: profile %q not found", id) - } - return nil -} - -// ExportProfiles serialises all profiles as JSON. If includeSecrets is false -// the export omits inline credential-related fields. -func (s *Store) ExportProfiles(includeSecrets bool) ([]byte, error) { - profiles, err := s.ListProfiles() - if err != nil { - return nil, fmt.Errorf("storage: export failed: %w", err) - } - if !includeSecrets { - profiles = redactProfileSecrets(profiles) - } - return json.MarshalIndent(profiles, "", " ") -} - -// ImportProfiles deserialises profiles from JSON and inserts them according to -// the given strategy: "skip" ignores duplicates, "overwrite" replaces them, -// and "duplicate" creates new entries with fresh IDs. Returns the number of -// profiles imported. -func (s *Store) ImportProfiles(data []byte, strategy string) (int, error) { - switch strategy { - case "skip", "overwrite", "duplicate": - default: - return 0, fmt.Errorf("storage: unknown import strategy %q", strategy) - } - - var profiles []*models.Profile - if err := json.Unmarshal(data, &profiles); err != nil { - return 0, fmt.Errorf("storage: invalid import data: %w", err) - } - - imported := 0 - for i, p := range profiles { - if p == nil { - return imported, fmt.Errorf("storage: import profile entry %d is null", i) - } - - existing, _ := s.GetProfile(p.ID) - - switch strategy { - case "skip": - if existing != nil { - continue - } - case "overwrite": - if existing != nil { - if err := s.UpdateProfile(p); err != nil { - return imported, err - } - imported++ - continue - } - case "duplicate": - if existing != nil { - p.ID = uuid.New().String() - p.CreatedAt = time.Now() - p.UpdatedAt = time.Now() - } - } - - if err := s.CreateProfile(p); err != nil { - return imported, err - } - imported++ - } - return imported, nil -} - -// --------------------------------------------------------------------------- -// Known hosts -// --------------------------------------------------------------------------- - -// GetKnownHost retrieves a known host entry by host, port, and key type. -func (s *Store) GetKnownHost(host string, port int) (*models.KnownHost, error) { - row := s.db.QueryRow(`SELECT id, host, port, key_type, fingerprint, public_key, first_seen, last_seen, trusted - FROM known_hosts WHERE host = ? AND port = ?`, host, port) - - kh := &models.KnownHost{} - var firstSeen, lastSeen string - var trusted int - if err := row.Scan(&kh.ID, &kh.Host, &kh.Port, &kh.KeyType, &kh.Fingerprint, - &kh.PublicKey, &firstSeen, &lastSeen, &trusted); err != nil { - if err == sql.ErrNoRows { - return nil, nil - } - return nil, fmt.Errorf("storage: failed to get known host: %w", err) - } - kh.FirstSeen, _ = time.Parse(time.RFC3339, firstSeen) - kh.LastSeen, _ = time.Parse(time.RFC3339, lastSeen) - kh.Trusted = trusted == 1 - return kh, nil -} - -// SaveKnownHost inserts or updates a known host entry. -func (s *Store) SaveKnownHost(kh *models.KnownHost) error { - if kh.ID == "" { - kh.ID = uuid.New().String() - } - _, err := s.db.Exec(`INSERT INTO known_hosts (id, host, port, key_type, fingerprint, public_key, first_seen, last_seen, trusted) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(host, port, key_type) DO UPDATE SET - fingerprint=excluded.fingerprint, public_key=excluded.public_key, - last_seen=excluded.last_seen, trusted=excluded.trusted`, - kh.ID, kh.Host, kh.Port, kh.KeyType, kh.Fingerprint, kh.PublicKey, - kh.FirstSeen.Format(time.RFC3339), kh.LastSeen.Format(time.RFC3339), - boolToInt(kh.Trusted)) - if err != nil { - return fmt.Errorf("storage: failed to save known host: %w", err) - } - return nil -} - -// ListKnownHosts returns all stored known host entries. -func (s *Store) ListKnownHosts() ([]*models.KnownHost, error) { - rows, err := s.db.Query(`SELECT id, host, port, key_type, fingerprint, public_key, first_seen, last_seen, trusted - FROM known_hosts ORDER BY host, port`) - if err != nil { - return nil, fmt.Errorf("storage: failed to list known hosts: %w", err) - } - defer rows.Close() - - var hosts []*models.KnownHost - for rows.Next() { - kh := &models.KnownHost{} - var firstSeen, lastSeen string - var trusted int - if err := rows.Scan(&kh.ID, &kh.Host, &kh.Port, &kh.KeyType, &kh.Fingerprint, - &kh.PublicKey, &firstSeen, &lastSeen, &trusted); err != nil { - return nil, fmt.Errorf("storage: failed to scan known host: %w", err) - } - kh.FirstSeen, _ = time.Parse(time.RFC3339, firstSeen) - kh.LastSeen, _ = time.Parse(time.RFC3339, lastSeen) - kh.Trusted = trusted == 1 - hosts = append(hosts, kh) - } - return hosts, rows.Err() -} - -// DeleteKnownHost removes a known host entry by ID. -func (s *Store) DeleteKnownHost(id string) error { - res, err := s.db.Exec("DELETE FROM known_hosts WHERE id = ?", id) - if err != nil { - return fmt.Errorf("storage: failed to delete known host: %w", err) - } - n, _ := res.RowsAffected() - if n == 0 { - return fmt.Errorf("storage: known host %q not found", id) - } - return nil -} - -// --------------------------------------------------------------------------- -// Audit log -// --------------------------------------------------------------------------- - -// AddAuditEntry inserts an audit log entry. -func (s *Store) AddAuditEntry(entry *models.AuditEntry) error { - _, err := s.db.Exec(`INSERT INTO audit_log (id, timestamp, action, profile_id, session_id, details) - VALUES (?, ?, ?, ?, ?, ?)`, - entry.ID, entry.Timestamp.Format(time.RFC3339), entry.Action, - entry.ProfileID, entry.SessionID, entry.Details) - if err != nil { - return fmt.Errorf("storage: failed to add audit entry: %w", err) - } - return nil -} - -// ListAuditEntries returns the most recent audit entries, limited to count. -func (s *Store) ListAuditEntries(limit int) ([]*models.AuditEntry, error) { - rows, err := s.db.Query(`SELECT id, timestamp, action, profile_id, session_id, details - FROM audit_log ORDER BY timestamp DESC LIMIT ?`, limit) - if err != nil { - return nil, fmt.Errorf("storage: failed to list audit entries: %w", err) - } - defer rows.Close() - - var entries []*models.AuditEntry - for rows.Next() { - e := &models.AuditEntry{} - var ts string - if err := rows.Scan(&e.ID, &ts, &e.Action, &e.ProfileID, &e.SessionID, &e.Details); err != nil { - return nil, fmt.Errorf("storage: failed to scan audit entry: %w", err) - } - e.Timestamp, _ = time.Parse(time.RFC3339, ts) - entries = append(entries, e) - } - return entries, rows.Err() -} - -// --------------------------------------------------------------------------- -// IRC channel state -// --------------------------------------------------------------------------- - -// GetIRCLastRooms returns the channels that were open the last time the given -// saved IRC profile was connected. -func (s *Store) GetIRCLastRooms(profileID string) ([]string, error) { - rows, err := s.db.Query(`SELECT room_name FROM irc_channel_state - WHERE profile_id = ? ORDER BY position, room_name`, profileID) - if err != nil { - return nil, fmt.Errorf("storage: failed to get IRC channel state: %w", err) - } - defer rows.Close() - - rooms := make([]string, 0) - for rows.Next() { - var room string - if err := rows.Scan(&room); err != nil { - return nil, fmt.Errorf("storage: failed to scan IRC channel state: %w", err) - } - rooms = append(rooms, room) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("storage: failed to read IRC channel state: %w", err) - } - return rooms, nil -} - -// SaveIRCLastRooms atomically replaces the remembered channel set for a saved -// IRC profile. Empty names and case-insensitive duplicates are discarded. -func (s *Store) SaveIRCLastRooms(profileID string, rooms []string) error { - profileID = strings.TrimSpace(profileID) - if profileID == "" { - return fmt.Errorf("storage: IRC channel state requires a profile ID") - } - - normalized := make([]string, 0, len(rooms)) - seen := make(map[string]struct{}, len(rooms)) - for _, room := range rooms { - room = strings.TrimSpace(room) - if room == "" { - continue - } - key := strings.ToLower(room) - if _, exists := seen[key]; exists { - continue - } - seen[key] = struct{}{} - normalized = append(normalized, room) - } - - tx, err := s.db.Begin() - if err != nil { - return fmt.Errorf("storage: failed to begin IRC channel state update: %w", err) - } - defer func() { _ = tx.Rollback() }() - - if _, err := tx.Exec("DELETE FROM irc_channel_state WHERE profile_id = ?", profileID); err != nil { - return fmt.Errorf("storage: failed to clear IRC channel state: %w", err) - } - now := time.Now().Format(time.RFC3339) - for position, room := range normalized { - if _, err := tx.Exec(`INSERT INTO irc_channel_state - (profile_id, room_name, position, updated_at) VALUES (?, ?, ?, ?)`, - profileID, room, position, now); err != nil { - return fmt.Errorf("storage: failed to save IRC channel state: %w", err) - } - } - if err := tx.Commit(); err != nil { - return fmt.Errorf("storage: failed to commit IRC channel state: %w", err) - } - return nil -} - -// --------------------------------------------------------------------------- -// Preferences (key-value) -// --------------------------------------------------------------------------- - -// GetPreferences reads the stored preferences JSON blob and deserialises it -// into an AppPreferences struct. Returns nil if no preferences have been saved. -func (s *Store) GetPreferences() (*models.AppPreferences, error) { - var value string - err := s.db.QueryRow("SELECT value FROM preferences WHERE key = ?", "_all").Scan(&value) - if err != nil { - // No preferences saved yet — return nil to signal defaults should be used. - return nil, nil - } - - prefs := &models.AppPreferences{} - if err := json.Unmarshal([]byte(value), prefs); err != nil { - return nil, fmt.Errorf("storage: failed to unmarshal preferences: %w", err) - } - return prefs, nil -} - -// SavePreferences serialises the AppPreferences as a JSON blob and persists -// it to the preferences table under a single key. -func (s *Store) SavePreferences(prefs *models.AppPreferences) error { - data, err := json.Marshal(prefs) - if err != nil { - return fmt.Errorf("storage: failed to marshal preferences: %w", err) - } - - _, err = s.db.Exec(`INSERT INTO preferences (key, value) VALUES (?, ?) - ON CONFLICT(key) DO UPDATE SET value = excluded.value`, "_all", string(data)) - if err != nil { - return fmt.Errorf("storage: failed to save preferences: %w", err) - } - return nil -} - -// --------------------------------------------------------------------------- -// Internal helpers -// --------------------------------------------------------------------------- - -// scanProfile reads a single profile row from the given scanner. -func scanProfile(row *sql.Row) (*models.Profile, error) { - p := &models.Profile{} - var ( - protocol, tagsJSON string - sshJSON, telnetJSON, rawJSON, githubJSON, vncJSON sql.NullString - ircJSON sql.NullString - lastConn sql.NullString - favourite int - createdAt, updatedAt string - ) - - err := row.Scan( - &p.ID, &p.DisplayName, &p.Description, &protocol, - &p.Host, &p.Port, &p.Username, - &tagsJSON, &p.Folder, &p.Environment, &p.Notes, - &favourite, &lastConn, - &p.ConnectTimeout, &p.ReadTimeout, - &p.TerminalFontFamily, &p.TerminalFontSize, &p.TerminalScrollback, &p.TerminalCursorStyle, - &sshJSON, &telnetJSON, &rawJSON, &githubJSON, &vncJSON, &ircJSON, - &createdAt, &updatedAt, - ) - if err != nil { - return nil, err - } - - p.Protocol = models.Protocol(protocol) - p.Favourite = favourite == 1 - p.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) - p.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt) - if lastConn.Valid && lastConn.String != "" { - t, err := time.Parse(time.RFC3339, lastConn.String) - if err == nil { - p.LastConnected = &t - } - } - - _ = json.Unmarshal([]byte(tagsJSON), &p.Tags) - if p.Tags == nil { - p.Tags = []string{} - } - - if sshJSON.Valid && sshJSON.String != "" { - p.SSH = &models.SSHConfig{} - _ = json.Unmarshal([]byte(sshJSON.String), p.SSH) - } - if telnetJSON.Valid && telnetJSON.String != "" { - p.Telnet = &models.TelnetConfig{} - _ = json.Unmarshal([]byte(telnetJSON.String), p.Telnet) - } - if rawJSON.Valid && rawJSON.String != "" { - p.RAW = &models.RAWConfig{} - _ = json.Unmarshal([]byte(rawJSON.String), p.RAW) - } - if githubJSON.Valid && githubJSON.String != "" { - p.GitHub = &models.GitHubConfig{} - _ = json.Unmarshal([]byte(githubJSON.String), p.GitHub) - } - if vncJSON.Valid && vncJSON.String != "" { - p.VNC = &models.VNCConfig{} - _ = json.Unmarshal([]byte(vncJSON.String), p.VNC) - } - if ircJSON.Valid && ircJSON.String != "" { - p.IRC = &models.IRCConfig{} - _ = json.Unmarshal([]byte(ircJSON.String), p.IRC) - } - - return p, nil -} - -// scanProfiles iterates over rows and returns a slice of profiles. -func scanProfiles(rows *sql.Rows) ([]*models.Profile, error) { - var result []*models.Profile - for rows.Next() { - p := &models.Profile{} - var ( - protocol, tagsJSON string - sshJSON, telnetJSON, rawJSON, githubJSON, vncJSON sql.NullString - ircJSON sql.NullString - lastConn sql.NullString - favourite int - createdAt, updatedAt string - ) - - err := rows.Scan( - &p.ID, &p.DisplayName, &p.Description, &protocol, - &p.Host, &p.Port, &p.Username, - &tagsJSON, &p.Folder, &p.Environment, &p.Notes, - &favourite, &lastConn, - &p.ConnectTimeout, &p.ReadTimeout, - &p.TerminalFontFamily, &p.TerminalFontSize, &p.TerminalScrollback, &p.TerminalCursorStyle, - &sshJSON, &telnetJSON, &rawJSON, &githubJSON, &vncJSON, &ircJSON, - &createdAt, &updatedAt, - ) - if err != nil { - return nil, fmt.Errorf("storage: failed to scan profile row: %w", err) - } - - p.Protocol = models.Protocol(protocol) - p.Favourite = favourite == 1 - p.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) - p.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt) - if lastConn.Valid && lastConn.String != "" { - t, err := time.Parse(time.RFC3339, lastConn.String) - if err == nil { - p.LastConnected = &t - } - } - - _ = json.Unmarshal([]byte(tagsJSON), &p.Tags) - if p.Tags == nil { - p.Tags = []string{} - } - - if sshJSON.Valid && sshJSON.String != "" { - p.SSH = &models.SSHConfig{} - _ = json.Unmarshal([]byte(sshJSON.String), p.SSH) - } - if telnetJSON.Valid && telnetJSON.String != "" { - p.Telnet = &models.TelnetConfig{} - _ = json.Unmarshal([]byte(telnetJSON.String), p.Telnet) - } - if rawJSON.Valid && rawJSON.String != "" { - p.RAW = &models.RAWConfig{} - _ = json.Unmarshal([]byte(rawJSON.String), p.RAW) - } - if githubJSON.Valid && githubJSON.String != "" { - p.GitHub = &models.GitHubConfig{} - _ = json.Unmarshal([]byte(githubJSON.String), p.GitHub) - } - if vncJSON.Valid && vncJSON.String != "" { - p.VNC = &models.VNCConfig{} - _ = json.Unmarshal([]byte(vncJSON.String), p.VNC) - } - if ircJSON.Valid && ircJSON.String != "" { - p.IRC = &models.IRCConfig{} - _ = json.Unmarshal([]byte(ircJSON.String), p.IRC) - } - - result = append(result, p) - } - return result, rows.Err() -} - -func boolToInt(b bool) int { - if b { - return 1 - } - return 0 -} - -func timeToStr(t *time.Time) interface{} { - if t == nil { - return nil - } - return t.Format(time.RFC3339) -} - -// nullableJSON returns nil when the source object pointer is nil, -// otherwise the marshalled JSON bytes as a string. -func nullableJSON(ptr interface{}, data []byte) interface{} { - if ptr == nil || string(data) == "null" { - return nil - } - return string(data) -} - -func redactProfileSecrets(profiles []*models.Profile) []*models.Profile { - redacted := make([]*models.Profile, len(profiles)) - for i, profile := range profiles { - cp := *profile - if cp.SSH != nil { - sshCfg := *cp.SSH - sshCfg.ProxyPassword = "" - sshCfg.TOTPSecret = "" - cp.SSH = &sshCfg - } - redacted[i] = &cp - } - return redacted -} - -// --------------------------------------------------------------------------- -// Snippet CRUD -// --------------------------------------------------------------------------- - -// CreateSnippet inserts a new snippet into the database. -func (s *Store) CreateSnippet(snippet *models.Snippet) error { - tagsJSON, _ := json.Marshal(snippet.Tags) - _, err := s.db.Exec(`INSERT INTO snippets (id, name, command, description, folder, tags, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, - snippet.ID, snippet.Name, snippet.Command, snippet.Description, - snippet.Folder, string(tagsJSON), - snippet.CreatedAt.Format(time.RFC3339), - snippet.UpdatedAt.Format(time.RFC3339), - ) - return err -} - -// UpdateSnippet updates an existing snippet. -func (s *Store) UpdateSnippet(snippet *models.Snippet) error { - tagsJSON, _ := json.Marshal(snippet.Tags) - _, err := s.db.Exec(`UPDATE snippets SET name=?, command=?, description=?, folder=?, tags=?, updated_at=? WHERE id=?`, - snippet.Name, snippet.Command, snippet.Description, - snippet.Folder, string(tagsJSON), - snippet.UpdatedAt.Format(time.RFC3339), - snippet.ID, - ) - return err -} - -// DeleteSnippet removes a snippet by ID. -func (s *Store) DeleteSnippet(id string) error { - _, err := s.db.Exec(`DELETE FROM snippets WHERE id = ?`, id) - return err -} - -// ListSnippets returns all snippets ordered by name. -func (s *Store) ListSnippets() ([]*models.Snippet, error) { - rows, err := s.db.Query(`SELECT id, name, command, description, folder, tags, created_at, updated_at FROM snippets ORDER BY name ASC`) - if err != nil { - return nil, err - } - defer rows.Close() - - var snippets []*models.Snippet - for rows.Next() { - sn := &models.Snippet{} - var tagsStr, createdAt, updatedAt string - if err := rows.Scan(&sn.ID, &sn.Name, &sn.Command, &sn.Description, &sn.Folder, &tagsStr, &createdAt, &updatedAt); err != nil { - return nil, err - } - _ = json.Unmarshal([]byte(tagsStr), &sn.Tags) - if sn.Tags == nil { - sn.Tags = []string{} - } - sn.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) - sn.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt) - snippets = append(snippets, sn) - } - return snippets, rows.Err() -} - -// GetSnippet returns a single snippet by ID. -func (s *Store) GetSnippet(id string) (*models.Snippet, error) { - sn := &models.Snippet{} - var tagsStr, createdAt, updatedAt string - err := s.db.QueryRow(`SELECT id, name, command, description, folder, tags, created_at, updated_at FROM snippets WHERE id = ?`, id). - Scan(&sn.ID, &sn.Name, &sn.Command, &sn.Description, &sn.Folder, &tagsStr, &createdAt, &updatedAt) - if err != nil { - return nil, err - } - _ = json.Unmarshal([]byte(tagsStr), &sn.Tags) - if sn.Tags == nil { - sn.Tags = []string{} - } - sn.CreatedAt, _ = time.Parse(time.RFC3339, createdAt) - sn.UpdatedAt, _ = time.Parse(time.RFC3339, updatedAt) - return sn, nil -} diff --git a/internal/telnetclient/telnetclient.go b/internal/telnetclient/telnetclient.go deleted file mode 100644 index 546313e..0000000 --- a/internal/telnetclient/telnetclient.go +++ /dev/null @@ -1,603 +0,0 @@ -package telnetclient - -import ( - "context" - "encoding/binary" - "fmt" - "net" - "strings" - "sync" - "time" - - "github.com/andydixon/link/internal/models" - "golang.org/x/text/encoding/charmap" -) - -// Telnet command bytes (RFC 854/855). -const ( - IAC byte = 255 // Interpret As Command - DONT byte = 254 - DO byte = 253 - WONT byte = 252 - WILL byte = 251 - SB byte = 250 // Sub-negotiation Begin - SE byte = 240 // Sub-negotiation End - - // Common options. - OptEcho byte = 1 - OptSGA byte = 3 // Suppress Go Ahead - OptTermType byte = 24 // Terminal Type - OptNAWS byte = 31 // Negotiate About Window Size - OptLineMode byte = 34 -) - -// Sub-negotiation constants. -const ( - subIS byte = 0 // IS (used in terminal-type sub-negotiation) - subSEND byte = 1 // SEND (server requests terminal type) -) - -// Client manages a single Telnet session. -// It handles Telnet option negotiation (RFC 854/855), terminal-type -// sub-negotiation, window-size negotiation (NAWS), and data framing. -type Client struct { - mu sync.Mutex - config *models.Profile - conn net.Conn - connected bool - onData func(data []byte) - onError func(err error) - onClose func() - onDebug func(msg string) - ctx context.Context - cancel context.CancelFunc - localEcho bool - encoding string - cols int - rows int - - // Track which options we have agreed to support. - willOptions map[byte]bool - doOptions map[byte]bool -} - -// NewClient creates a new Telnet client bound to the given profile. -func NewClient(profile *models.Profile) *Client { - ctx, cancel := context.WithCancel(context.Background()) - - localEcho := false - enc := "utf-8" - if profile.Telnet != nil { - localEcho = profile.Telnet.LocalEcho - if profile.Telnet.Encoding != "" { - enc = profile.Telnet.Encoding - } - } - - return &Client{ - config: profile, - ctx: ctx, - cancel: cancel, - localEcho: localEcho, - encoding: enc, - cols: 80, - rows: 24, - willOptions: make(map[byte]bool), - doOptions: make(map[byte]bool), - } -} - -// SetCallbacks sets the data, error, close, and debug callbacks. -// Must be called before Connect. -func (c *Client) SetCallbacks(onData func([]byte), onError func(error), onClose func(), onDebug func(string)) { - c.mu.Lock() - defer c.mu.Unlock() - c.onData = onData - c.onError = onError - c.onClose = onClose - c.onDebug = onDebug -} - -// telnetConf returns the Telnet-specific configuration, or a zero value -// if nil. -func (c *Client) telnetConf() models.TelnetConfig { - if c.config.Telnet != nil { - return *c.config.Telnet - } - return models.TelnetConfig{} -} - -// Connect establishes the Telnet connection and starts the read loop. -func (c *Client) Connect() error { - c.mu.Lock() - if c.connected { - c.mu.Unlock() - return fmt.Errorf("already connected") - } - c.mu.Unlock() - - timeout := time.Duration(c.config.ConnectTimeout) * time.Second - if timeout == 0 { - timeout = 30 * time.Second - } - - port := c.config.Port - if port == 0 { - port = 23 - } - addr := net.JoinHostPort(c.config.Host, fmt.Sprintf("%d", port)) - - conn, err := net.DialTimeout("tcp", addr, timeout) - if err != nil { - return fmt.Errorf("Telnet connection to %s failed: %w", addr, err) - } - - c.mu.Lock() - c.conn = conn - c.connected = true - c.mu.Unlock() - - go c.readLoop() - return nil -} - -// Write sends data to the Telnet session, applying line-ending conversion -// and escaping any literal IAC bytes in the payload. -func (c *Client) Write(data []byte) error { - c.mu.Lock() - if !c.connected || c.conn == nil { - c.mu.Unlock() - return fmt.Errorf("not connected") - } - conn := c.conn - c.mu.Unlock() - - converted := c.convertLineEnding(data) - escaped := c.escapeIAC(converted) - encoded := c.encodeOutgoing(escaped) - - _, err := conn.Write(encoded) - return err -} - -// Resize sends a NAWS (Negotiate About Window Size) update to the remote host. -func (c *Client) Resize(cols, rows int) error { - c.mu.Lock() - if !c.connected || c.conn == nil { - c.mu.Unlock() - return fmt.Errorf("not connected") - } - c.cols = cols - c.rows = rows - conn := c.conn - c.mu.Unlock() - - return c.sendNAWS(conn, cols, rows) -} - -// Close shuts down the Telnet connection and invokes the onClose callback. -func (c *Client) Close() error { - c.mu.Lock() - if !c.connected { - c.mu.Unlock() - return nil - } - c.connected = false - conn := c.conn - onClose := c.onClose - c.mu.Unlock() - - c.cancel() - - var err error - if conn != nil { - err = conn.Close() - } - if onClose != nil { - onClose() - } - return err -} - -// IsConnected returns the connection state. -func (c *Client) IsConnected() bool { - c.mu.Lock() - defer c.mu.Unlock() - return c.connected -} - -// SendBreak transmits a Telnet break signal (IAC BRK). -func (c *Client) SendBreak() error { - c.mu.Lock() - if !c.connected || c.conn == nil { - c.mu.Unlock() - return fmt.Errorf("not connected") - } - conn := c.conn - c.mu.Unlock() - - _, err := conn.Write([]byte{IAC, 243}) // 243 = BRK - return err -} - -// readLoop reads from the connection, processes IAC sequences, and dispatches -// plain data to the onData callback. -func (c *Client) readLoop() { - buf := make([]byte, 4096) - var dataBuf []byte - - for { - select { - case <-c.ctx.Done(): - return - default: - } - - c.mu.Lock() - conn := c.conn - connected := c.connected - c.mu.Unlock() - - if !connected || conn == nil { - return - } - - n, err := conn.Read(buf) - if n > 0 { - raw := buf[:n] - dataBuf = dataBuf[:0] - - i := 0 - for i < len(raw) { - if raw[i] == IAC { - if len(dataBuf) > 0 { - c.dispatchData(dataBuf) - dataBuf = dataBuf[:0] - } - consumed := c.handleIAC(raw[i:]) - if consumed == 0 { - break - } - i += consumed - } else { - dataBuf = append(dataBuf, raw[i]) - i++ - } - } - - if len(dataBuf) > 0 { - c.dispatchData(dataBuf) - } - } - - if err != nil { - c.mu.Lock() - onError := c.onError - c.mu.Unlock() - if onError != nil { - onError(fmt.Errorf("read error: %w", err)) - } - c.Close() - return - } - } -} - -// handleIAC processes a Telnet IAC sequence starting at data[0]. -// Returns the number of bytes consumed. -func (c *Client) handleIAC(data []byte) int { - if len(data) < 2 { - return 0 - } - - // IAC IAC = escaped 0xFF byte. - if data[1] == IAC { - c.dispatchData([]byte{IAC}) - return 2 - } - - switch data[1] { - case DO: - if len(data) < 3 { - return 0 - } - c.handleDO(data[2]) - return 3 - - case DONT: - if len(data) < 3 { - return 0 - } - c.handleDONT(data[2]) - return 3 - - case WILL: - if len(data) < 3 { - return 0 - } - c.handleWILL(data[2]) - return 3 - - case WONT: - if len(data) < 3 { - return 0 - } - c.handleWONT(data[2]) - return 3 - - case SB: - return c.handleSubnegotiation(data) - - default: - c.debug("received IAC command: %d", data[1]) - return 2 - } -} - -// handleDO processes a DO request from the server. -func (c *Client) handleDO(option byte) { - c.debug("received DO %d", option) - - switch option { - case OptTermType: - c.negotiate(WILL, option) - c.mu.Lock() - c.willOptions[option] = true - c.mu.Unlock() - - case OptNAWS: - c.negotiate(WILL, option) - c.mu.Lock() - c.willOptions[option] = true - cols := c.cols - rows := c.rows - conn := c.conn - c.mu.Unlock() - if conn != nil { - _ = c.sendNAWS(conn, cols, rows) - } - - case OptSGA: - c.negotiate(WILL, option) - c.mu.Lock() - c.willOptions[option] = true - c.mu.Unlock() - - default: - c.negotiate(WONT, option) - } -} - -// handleDONT processes a DONT request from the server. -func (c *Client) handleDONT(option byte) { - c.debug("received DONT %d", option) - c.mu.Lock() - delete(c.willOptions, option) - c.mu.Unlock() - c.negotiate(WONT, option) -} - -// handleWILL processes a WILL offer from the server. -func (c *Client) handleWILL(option byte) { - c.debug("received WILL %d", option) - - switch option { - case OptEcho: - c.negotiate(DO, option) - c.mu.Lock() - c.doOptions[option] = true - c.localEcho = false - c.mu.Unlock() - - case OptSGA: - c.negotiate(DO, option) - c.mu.Lock() - c.doOptions[option] = true - c.mu.Unlock() - - default: - c.negotiate(DONT, option) - } -} - -// handleWONT processes a WONT refusal from the server. -func (c *Client) handleWONT(option byte) { - c.debug("received WONT %d", option) - - if option == OptEcho { - c.mu.Lock() - c.localEcho = true - c.mu.Unlock() - } - - c.mu.Lock() - delete(c.doOptions, option) - c.mu.Unlock() - c.negotiate(DONT, option) -} - -// handleSubnegotiation processes an SB...SE sub-negotiation block. -// Returns the total number of bytes consumed (including IAC SB and IAC SE). -func (c *Client) handleSubnegotiation(data []byte) int { - for i := 2; i < len(data)-1; i++ { - if data[i] == IAC && data[i+1] == SE { - payload := data[2:i] - c.processSubnegotiation(payload) - return i + 2 - } - } - return 0 -} - -// processSubnegotiation handles the payload between SB and SE. -func (c *Client) processSubnegotiation(payload []byte) { - if len(payload) < 1 { - return - } - - option := payload[0] - c.debug("sub-negotiation for option %d", option) - - switch option { - case OptTermType: - if len(payload) >= 2 && payload[1] == subSEND { - c.sendTermType() - } - } -} - -// sendTermType responds to a terminal-type SEND request with an IS response. -func (c *Client) sendTermType() { - tc := c.telnetConf() - termType := tc.TerminalType - if termType == "" { - termType = "xterm-256color" - } - - c.debug("sending terminal type: %s", termType) - - resp := []byte{IAC, SB, OptTermType, subIS} - resp = append(resp, []byte(strings.ToUpper(termType))...) - resp = append(resp, IAC, SE) - - c.mu.Lock() - conn := c.conn - c.mu.Unlock() - if conn != nil { - _, _ = conn.Write(resp) - } -} - -// negotiate sends a WILL/WONT/DO/DONT response for the given option. -func (c *Client) negotiate(command, option byte) { - c.debug("sending %s %d", commandName(command), option) - - c.mu.Lock() - conn := c.conn - c.mu.Unlock() - if conn != nil { - _, _ = conn.Write([]byte{IAC, command, option}) - } -} - -// sendNAWS sends the current window size via NAWS sub-negotiation. -// Columns and rows are each sent as big-endian uint16 values. -func (c *Client) sendNAWS(conn net.Conn, cols, rows int) error { - c.debug("sending NAWS: %dx%d", cols, rows) - - var buf [9]byte - buf[0] = IAC - buf[1] = SB - buf[2] = OptNAWS - binary.BigEndian.PutUint16(buf[3:5], uint16(cols)) - binary.BigEndian.PutUint16(buf[5:7], uint16(rows)) - buf[7] = IAC - buf[8] = SE - - _, err := conn.Write(buf[:]) - return err -} - -// convertLineEnding applies the configured line-ending conversion on -// outgoing data. -func (c *Client) convertLineEnding(data []byte) []byte { - tc := c.telnetConf() - le := tc.LineEnding - if le == "" { - le = models.LineEndingCRLF - } - if le == models.LineEndingNone { - return data - } - - s := string(data) - - // Normalise all line endings to LF first. - s = strings.ReplaceAll(s, "\r\n", "\n") - s = strings.ReplaceAll(s, "\r", "\n") - - switch le { - case models.LineEndingCR: - s = strings.ReplaceAll(s, "\n", "\r") - case models.LineEndingCRLF: - s = strings.ReplaceAll(s, "\n", "\r\n") - case models.LineEndingLF: - // Already LF. - } - return []byte(s) -} - -// escapeIAC doubles any literal 0xFF bytes in the data so they are not -// misinterpreted as IAC commands. -func (c *Client) escapeIAC(data []byte) []byte { - count := 0 - for _, b := range data { - if b == IAC { - count++ - } - } - if count == 0 { - return data - } - escaped := make([]byte, 0, len(data)+count) - for _, b := range data { - escaped = append(escaped, b) - if b == IAC { - escaped = append(escaped, IAC) - } - } - return escaped -} - -// encodeOutgoing converts outgoing text from UTF-8 to the configured encoding. -func (c *Client) encodeOutgoing(data []byte) []byte { - switch strings.ToLower(c.encoding) { - case "iso-8859-1", "latin1", "latin-1": - encoded, err := charmap.ISO8859_1.NewEncoder().Bytes(data) - if err != nil { - return data - } - return encoded - default: - return data - } -} - -// dispatchData sends received data to the onData callback. -func (c *Client) dispatchData(data []byte) { - out := make([]byte, len(data)) - copy(out, data) - - c.mu.Lock() - cb := c.onData - c.mu.Unlock() - if cb != nil { - cb(out) - } -} - -// debug emits a formatted debug message if a debug callback is registered. -func (c *Client) debug(format string, args ...interface{}) { - c.mu.Lock() - cb := c.onDebug - c.mu.Unlock() - if cb != nil { - cb(fmt.Sprintf(format, args...)) - } -} - -// commandName returns a human-readable name for a Telnet negotiation command. -func commandName(cmd byte) string { - switch cmd { - case WILL: - return "WILL" - case WONT: - return "WONT" - case DO: - return "DO" - case DONT: - return "DONT" - default: - return fmt.Sprintf("CMD(%d)", cmd) - } -} diff --git a/internal/terminalenv/terminalenv.go b/internal/terminalenv/terminalenv.go deleted file mode 100644 index 8a1f2b2..0000000 --- a/internal/terminalenv/terminalenv.go +++ /dev/null @@ -1,137 +0,0 @@ -// Package terminalenv prepares environment variables for terminal sessions. -package terminalenv - -import ( - "runtime" - "sort" - "strings" -) - -const ( - envLANG = "LANG" - envLCAll = "LC_ALL" - envLCType = "LC_CTYPE" - envTerm = "TERM" - defaultTerm = "xterm-256color" -) - -// LocalShellEnv returns an environment suitable for a local interactive PTY. -func LocalShellEnv(env []string) []string { - env = setEnv(env, envTerm, defaultTerm) - return ensureUTF8CharacterLocale(env, runtime.GOOS) -} - -// SSHEnvVars returns environment variables to request for an SSH session. -// Profile locale values are preserved; otherwise a UTF-8 character locale is -// requested by default. -func SSHEnvVars(profileEnv map[string]string) map[string]string { - result := map[string]string{} - for key, value := range profileEnv { - key = strings.TrimSpace(key) - if key == "" { - continue - } - result[key] = value - } - - if hasAnyLocaleMap(result) { - return result - } - result[envLCType] = DefaultUTF8Locale(runtime.GOOS) - return result -} - -// SortedEnvKeys returns stable keys for deterministic environment requests. -func SortedEnvKeys(env map[string]string) []string { - keys := make([]string, 0, len(env)) - for key := range env { - keys = append(keys, key) - } - sort.Strings(keys) - return keys -} - -// DefaultUTF8Locale returns a locale name broadly available on the target OS. -func DefaultUTF8Locale(goos string) string { - if goos == "darwin" { - return "en_US.UTF-8" - } - return "C.UTF-8" -} - -func ensureUTF8CharacterLocale(env []string, goos string) []string { - if hasEffectiveUTF8Locale(env) { - return env - } - - locale := DefaultUTF8Locale(goos) - if hasEnv(env, envLCAll) { - return setEnv(env, envLCAll, locale) - } - if hasEnv(env, envLCType) { - return setEnv(env, envLCType, locale) - } - if hasEnv(env, envLANG) { - return setEnv(env, envLCType, locale) - } - return setEnv(env, envLANG, locale) -} - -func hasEffectiveUTF8Locale(env []string) bool { - values := envToMap(env) - if value, ok := values[envLCAll]; ok { - return isUTF8Locale(value) - } - if value, ok := values[envLCType]; ok { - return isUTF8Locale(value) - } - if value, ok := values[envLANG]; ok { - return isUTF8Locale(value) - } - return false -} - -func hasAnyLocaleMap(env map[string]string) bool { - _, hasLCAll := env[envLCAll] - _, hasLCType := env[envLCType] - _, hasLANG := env[envLANG] - return hasLCAll || hasLCType || hasLANG -} - -func isUTF8Locale(value string) bool { - normalized := strings.ToLower(strings.ReplaceAll(value, "-", "")) - return strings.Contains(normalized, "utf8") -} - -func setEnv(env []string, key, value string) []string { - prefix := key + "=" - result := make([]string, 0, len(env)+1) - for _, item := range env { - if strings.HasPrefix(item, prefix) { - continue - } - result = append(result, item) - } - return append(result, prefix+value) -} - -func hasEnv(env []string, key string) bool { - prefix := key + "=" - for _, item := range env { - if strings.HasPrefix(item, prefix) { - return true - } - } - return false -} - -func envToMap(env []string) map[string]string { - result := map[string]string{} - for _, item := range env { - key, value, ok := strings.Cut(item, "=") - if ok { - result[key] = value - } - } - return result -} diff --git a/internal/terminalenv/terminalenv_test.go b/internal/terminalenv/terminalenv_test.go deleted file mode 100644 index 1c3b90c..0000000 --- a/internal/terminalenv/terminalenv_test.go +++ /dev/null @@ -1,85 +0,0 @@ -package terminalenv - -import "testing" - -func TestLocalShellEnvSetsTermAndUTF8WhenMissing(t *testing.T) { - got := LocalShellEnv([]string{"SHELL=/bin/zsh"}) - - if value := envValue(got, envTerm); value != defaultTerm { - t.Fatalf("TERM = %q, want %q", value, defaultTerm) - } - if value := envValue(got, envLANG); value == "" || !isUTF8Locale(value) { - t.Fatalf("LANG = %q, want UTF-8 locale", value) - } -} - -func TestLocalShellEnvUsesLCTypeWhenLangIsNonUTF8(t *testing.T) { - got := ensureUTF8CharacterLocale([]string{"LANG=C"}, "linux") - - if value := envValue(got, envLANG); value != "C" { - t.Fatalf("LANG = %q, want C", value) - } - if value := envValue(got, envLCType); value != "C.UTF-8" { - t.Fatalf("LC_CTYPE = %q, want C.UTF-8", value) - } -} - -func TestLocalShellEnvOverridesNonUTF8LCAll(t *testing.T) { - got := ensureUTF8CharacterLocale([]string{"LC_ALL=C", "LANG=C"}, "darwin") - - if value := envValue(got, envLCAll); value != "en_US.UTF-8" { - t.Fatalf("LC_ALL = %q, want en_US.UTF-8", value) - } -} - -func TestSSHEnvVarsPreservesProfileUTF8Locale(t *testing.T) { - got := SSHEnvVars(map[string]string{ - "LANG": "de_DE.UTF-8", - "FOO": "bar", - }) - - if got["LANG"] != "de_DE.UTF-8" { - t.Fatalf("LANG = %q, want profile value", got["LANG"]) - } - if got["FOO"] != "bar" { - t.Fatalf("FOO = %q, want bar", got["FOO"]) - } - if _, ok := got[envLCType]; ok { - t.Fatalf("unexpected LC_CTYPE default when LANG is already UTF-8") - } -} - -func TestSSHEnvVarsPreservesProfileNonUTF8Locale(t *testing.T) { - got := SSHEnvVars(map[string]string{ - envLANG: "C", - "FOO": "bar", - }) - - if got[envLANG] != "C" { - t.Fatalf("LANG = %q, want C", got[envLANG]) - } - if _, ok := got[envLCType]; ok { - t.Fatalf("unexpected LC_CTYPE default when profile sets locale") - } -} - -func TestSSHEnvVarsAddsUTF8LCTypeByDefault(t *testing.T) { - got := SSHEnvVars(map[string]string{"FOO": "bar"}) - - if got["FOO"] != "bar" { - t.Fatalf("FOO = %q, want bar", got["FOO"]) - } - if value := got[envLCType]; value == "" || !isUTF8Locale(value) { - t.Fatalf("LC_CTYPE = %q, want UTF-8 locale", value) - } -} - -func envValue(env []string, key string) string { - prefix := key + "=" - for _, item := range env { - if len(item) >= len(prefix) && item[:len(prefix)] == prefix { - return item[len(prefix):] - } - } - return "" -} diff --git a/internal/updater/updater.go b/internal/updater/updater.go deleted file mode 100644 index 3b3af18..0000000 --- a/internal/updater/updater.go +++ /dev/null @@ -1,303 +0,0 @@ -// Package updater checks for new application releases by querying the -// GitHub Releases API. It compares the latest published tag against the -// current build version and reports whether an update is available. -// -// The check is non-blocking and opt-in — it runs in the background and -// does not download or install anything automatically. -package updater - -import ( - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "time" -) - -const ( - // defaultOwner and defaultRepo identify the GitHub repository to check. - // These should be overridden via SetRepository if the project is hosted - // elsewhere. - defaultOwner = "alchementary-link" - defaultRepo = "alchementary-link" - - // requestTimeout limits the HTTP request duration. - requestTimeout = 10 * time.Second - - // maxUpdateResponseBytes bounds release and manifest responses before - // parsing them into memory. - maxUpdateResponseBytes = 1 << 20 -) - -// Release describes a GitHub release with the fields we care about. -type Release struct { - TagName string `json:"tag_name"` - Name string `json:"name"` - HTMLURL string `json:"html_url"` - PublishedAt string `json:"published_at"` - Body string `json:"body"` - Prerelease bool `json:"prerelease"` - Draft bool `json:"draft"` -} - -// UpdateInfo carries the result of an update check. -type UpdateInfo struct { - Available bool `json:"available"` - CurrentVersion string `json:"currentVersion"` - LatestVersion string `json:"latestVersion"` - ReleaseURL string `json:"releaseURL"` - ReleaseName string `json:"releaseName"` - ReleaseNotes string `json:"releaseNotes"` - PublishedAt string `json:"publishedAt"` -} - -// Checker performs version checks against GitHub Releases. -type Checker struct { - owner string - repo string - current string - client *http.Client -} - -// Manifest describes a self-hosted update manifest. -type Manifest struct { - LatestVersion string `json:"latestVersion"` - Version string `json:"version"` - ReleaseURL string `json:"releaseURL"` - ReleaseName string `json:"releaseName"` - ReleaseNotes string `json:"releaseNotes"` - PublishedAt string `json:"publishedAt"` -} - -// URLChecker performs version checks against a self-hosted update manifest. -type URLChecker struct { - url string - current string - client *http.Client -} - -// NewChecker creates an update checker for the given current version. -// The version string should be a semver tag (e.g. "v1.2.3" or "1.2.3"). -func NewChecker(currentVersion string) *Checker { - return &Checker{ - owner: defaultOwner, - repo: defaultRepo, - current: normaliseVersion(currentVersion), - client: &http.Client{ - Timeout: requestTimeout, - }, - } -} - -// NewURLChecker creates a checker for a self-hosted update manifest URL. -func NewURLChecker(url string, currentVersion string) *URLChecker { - return &URLChecker{ - url: url, - current: normaliseVersion(currentVersion), - client: &http.Client{ - Timeout: requestTimeout, - }, - } -} - -// SetRepository overrides the GitHub repository to check. -func (c *Checker) SetRepository(owner, repo string) { - c.owner = owner - c.repo = repo -} - -// Check queries the GitHub API for the latest release and compares it to -// the current version. Returns UpdateInfo describing the result. -func (c *Checker) Check(ctx context.Context) (*UpdateInfo, error) { - url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", c.owner, c.repo) - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) - if err != nil { - return nil, fmt.Errorf("updater: failed to create request: %w", err) - } - req.Header.Set("Accept", "application/vnd.github+json") - req.Header.Set("User-Agent", "AlchementaryLink-UpdateChecker") - - resp, err := c.client.Do(req) - if err != nil { - return nil, fmt.Errorf("updater: request failed: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNotFound { - // No releases exist yet — not an error. - return &UpdateInfo{ - Available: false, - CurrentVersion: c.current, - LatestVersion: c.current, - }, nil - } - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("updater: GitHub API returned %d", resp.StatusCode) - } - - body, err := readLimitedUpdateBody(resp.Body) - if err != nil { - return nil, fmt.Errorf("updater: failed to read response: %w", err) - } - - var release Release - if err := json.Unmarshal(body, &release); err != nil { - return nil, fmt.Errorf("updater: failed to decode response: %w", err) - } - - // Skip drafts and pre-releases. - if release.Draft || release.Prerelease { - return &UpdateInfo{ - Available: false, - CurrentVersion: c.current, - LatestVersion: normaliseVersion(release.TagName), - }, nil - } - - latest := normaliseVersion(release.TagName) - available := isNewer(latest, c.current) - - return &UpdateInfo{ - Available: available, - CurrentVersion: c.current, - LatestVersion: latest, - ReleaseURL: release.HTMLURL, - ReleaseName: release.Name, - ReleaseNotes: truncateNotes(release.Body, 500), - PublishedAt: release.PublishedAt, - }, nil -} - -// Check queries the update manifest URL and compares it to the current version. -func (c *URLChecker) Check(ctx context.Context) (*UpdateInfo, error) { - if c.url == "" { - return nil, fmt.Errorf("updater: update manifest URL is empty") - } - - req, err := http.NewRequestWithContext(ctx, http.MethodGet, c.url, nil) - if err != nil { - return nil, fmt.Errorf("updater: failed to create request: %w", err) - } - req.Header.Set("Accept", "application/json, text/plain") - req.Header.Set("User-Agent", "AlchementaryLink-UpdateChecker") - - resp, err := c.client.Do(req) - if err != nil { - return nil, fmt.Errorf("updater: request failed: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("updater: update server returned %d", resp.StatusCode) - } - - body, err := readLimitedUpdateBody(resp.Body) - if err != nil { - return nil, fmt.Errorf("updater: failed to read response: %w", err) - } - - info, ok := parseManifest(body, c.current) - if ok { - return info, nil - } - - latest := normaliseVersion(strings.TrimSpace(string(body))) - if latest == "" { - return nil, fmt.Errorf("updater: update manifest is empty") - } - - return &UpdateInfo{ - Available: isNewer(latest, c.current), - CurrentVersion: c.current, - LatestVersion: latest, - }, nil -} - -// normaliseVersion strips a leading "v" and trims whitespace. -func normaliseVersion(v string) string { - v = strings.TrimSpace(v) - v = strings.TrimPrefix(v, "v") - return v -} - -func readLimitedUpdateBody(r io.Reader) ([]byte, error) { - body, err := io.ReadAll(io.LimitReader(r, maxUpdateResponseBytes+1)) - if err != nil { - return nil, err - } - if len(body) > maxUpdateResponseBytes { - return nil, fmt.Errorf("response exceeds %d bytes", maxUpdateResponseBytes) - } - return body, nil -} - -func parseManifest(body []byte, current string) (*UpdateInfo, bool) { - var manifest Manifest - if err := json.Unmarshal(body, &manifest); err != nil { - return nil, false - } - - latest := manifest.LatestVersion - if latest == "" { - latest = manifest.Version - } - latest = normaliseVersion(latest) - if latest == "" { - return nil, false - } - - return &UpdateInfo{ - Available: isNewer(latest, current), - CurrentVersion: current, - LatestVersion: latest, - ReleaseURL: manifest.ReleaseURL, - ReleaseName: manifest.ReleaseName, - ReleaseNotes: manifest.ReleaseNotes, - PublishedAt: manifest.PublishedAt, - }, true -} - -// isNewer performs a simple string comparison of dot-separated version parts. -// Returns true if latest > current (basic semver, no pre-release handling). -func isNewer(latest, current string) bool { - if current == "" || current == "dev" { - return false // dev builds never trigger update prompts - } - - lParts := strings.Split(latest, ".") - cParts := strings.Split(current, ".") - - maxLen := len(lParts) - if len(cParts) > maxLen { - maxLen = len(cParts) - } - - for i := 0; i < maxLen; i++ { - var lv, cv int - if i < len(lParts) { - fmt.Sscanf(lParts[i], "%d", &lv) - } - if i < len(cParts) { - fmt.Sscanf(cParts[i], "%d", &cv) - } - if lv > cv { - return true - } - if lv < cv { - return false - } - } - return false -} - -// truncateNotes trims release notes to a maximum length. -func truncateNotes(body string, maxLen int) string { - if len(body) <= maxLen { - return body - } - return body[:maxLen] + "…" -} diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go deleted file mode 100644 index f26df66..0000000 --- a/internal/updater/updater_test.go +++ /dev/null @@ -1,123 +0,0 @@ -package updater - -import ( - "context" - "net/http" - "net/http/httptest" - "strings" - "testing" -) - -func TestNormaliseVersion(t *testing.T) { - tests := []struct { - input string - want string - }{ - {"v1.2.3", "1.2.3"}, - {"1.2.3", "1.2.3"}, - {" v0.5.0 ", "0.5.0"}, - {"dev", "dev"}, - } - for _, tt := range tests { - got := normaliseVersion(tt.input) - if got != tt.want { - t.Errorf("normaliseVersion(%q) = %q, want %q", tt.input, got, tt.want) - } - } -} - -func TestIsNewer(t *testing.T) { - tests := []struct { - latest string - current string - want bool - }{ - {"1.1.0", "1.0.0", true}, - {"2.0.0", "1.9.9", true}, - {"1.0.1", "1.0.0", true}, - {"1.0.0", "1.0.0", false}, - {"0.9.0", "1.0.0", false}, - {"1.0.0", "dev", false}, // dev builds ignored - {"1.0.0", "", false}, // empty current ignored - {"1.2", "1.1.0", true}, // fewer parts - {"1.0.0", "1.0.0.1", false}, - } - for _, tt := range tests { - t.Run(tt.latest+" vs "+tt.current, func(t *testing.T) { - got := isNewer(tt.latest, tt.current) - if got != tt.want { - t.Errorf("isNewer(%q, %q) = %v, want %v", tt.latest, tt.current, got, tt.want) - } - }) - } -} - -func TestTruncateNotes(t *testing.T) { - short := "Short note" - if got := truncateNotes(short, 100); got != short { - t.Errorf("expected %q, got %q", short, got) - } - - long := strings.Repeat("x", 600) - got := truncateNotes(long, 500) - if !strings.HasSuffix(got, "…") { - t.Error("expected truncated notes to end with ellipsis") - } - if len([]rune(got)) != 501 { // 500 runes + "…" - t.Errorf("expected 501 runes, got %d", len([]rune(got))) - } -} - -func TestURLCheckerManifestJSON(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - _, _ = w.Write([]byte(`{"latestVersion":"1.2.3","releaseURL":"https://example.com/release"}`)) - })) - defer server.Close() - - checker := NewURLChecker(server.URL, "1.2.0") - info, err := checker.Check(context.Background()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !info.Available { - t.Fatalf("expected update to be available") - } - if info.LatestVersion != "1.2.3" { - t.Fatalf("unexpected latest version: %s", info.LatestVersion) - } -} - -func TestURLCheckerPlainText(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") - _, _ = w.Write([]byte("2.0.0")) - })) - defer server.Close() - - checker := NewURLChecker(server.URL, "1.9.0") - info, err := checker.Check(context.Background()) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - if !info.Available { - t.Fatalf("expected update to be available") - } -} - -func TestURLCheckerRejectsOversizedManifest(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "text/plain") - _, _ = w.Write([]byte(strings.Repeat("x", maxUpdateResponseBytes+1))) - })) - defer server.Close() - - checker := NewURLChecker(server.URL, "1.9.0") - _, err := checker.Check(context.Background()) - if err == nil { - t.Fatal("expected oversized manifest to be rejected") - } - if !strings.Contains(err.Error(), "response exceeds") { - t.Fatalf("unexpected error: %v", err) - } -} diff --git a/internal/validation/validation.go b/internal/validation/validation.go deleted file mode 100644 index 97d6046..0000000 --- a/internal/validation/validation.go +++ /dev/null @@ -1,518 +0,0 @@ -// Package validation provides thorough validation for connection profiles -// and their protocol-specific configurations. -package validation - -import ( - "fmt" - "net" - "net/url" - "strings" - - "github.com/andydixon/link/internal/models" - "github.com/lrstanley/girc" -) - -// ValidationError describes a single field-level validation failure. -type ValidationError struct { - Field string `json:"field"` - Message string `json:"message"` -} - -// Error satisfies the error interface. -func (e *ValidationError) Error() string { - return fmt.Sprintf("%s: %s", e.Field, e.Message) -} - -// ValidationErrors collects multiple validation failures into one error. -type ValidationErrors struct { - Errors []ValidationError `json:"errors"` -} - -// Error satisfies the error interface, returning a summary of all failures. -func (e *ValidationErrors) Error() string { - msgs := make([]string, len(e.Errors)) - for i, err := range e.Errors { - msgs[i] = err.Error() - } - return fmt.Sprintf("validation failed: %s", strings.Join(msgs, "; ")) -} - -// HasErrors reports whether any validation errors have been collected. -func (e *ValidationErrors) HasErrors() bool { - return len(e.Errors) > 0 -} - -// Add appends a validation error to the collection. -func (e *ValidationErrors) Add(field, message string) { - e.Errors = append(e.Errors, ValidationError{Field: field, Message: message}) -} - -// ValidateProfile validates all fields of a connection profile, including -// protocol-specific configuration blocks. -func ValidateProfile(p *models.Profile) error { - errs := &ValidationErrors{} - - if p == nil { - return &ValidationError{Field: "profile", Message: "profile must not be nil"} - } - - if strings.TrimSpace(p.ID) == "" { - errs.Add("id", "profile ID must not be empty") - } - if strings.TrimSpace(p.DisplayName) == "" { - errs.Add("displayName", "display name must not be empty") - } - - switch p.Protocol { - case models.ProtocolSSH: - validateProfileHostPort(errs, p.Host, p.Port) - if strings.TrimSpace(p.Username) == "" { - errs.Add("username", "username is required for SSH connections") - } else if err := ValidateUsername(p.Username); err != nil { - errs.Add("username", err.(*ValidationError).Message) - } - if p.SSH == nil { - errs.Add("ssh", "SSH configuration is required for SSH protocol") - } else if err := ValidateSSHConfig(p.SSH); err != nil { - if ve, ok := err.(*ValidationErrors); ok { - errs.Errors = append(errs.Errors, ve.Errors...) - } - } - case models.ProtocolTelnet: - validateProfileHostPort(errs, p.Host, p.Port) - if p.Telnet == nil { - errs.Add("telnet", "Telnet configuration is required for Telnet protocol") - } else if err := ValidateTelnetConfig(p.Telnet); err != nil { - if ve, ok := err.(*ValidationErrors); ok { - errs.Errors = append(errs.Errors, ve.Errors...) - } - } - case models.ProtocolRAW: - validateProfileHostPort(errs, p.Host, p.Port) - if p.RAW == nil { - errs.Add("raw", "RAW configuration is required for RAW protocol") - } else if err := ValidateRAWConfig(p.RAW); err != nil { - if ve, ok := err.(*ValidationErrors); ok { - errs.Errors = append(errs.Errors, ve.Errors...) - } - } - case models.ProtocolGitHub: - if p.GitHub == nil { - errs.Add("github", "GitHub configuration is required for GitHub protocol") - } else if err := ValidateGitHubConfig(p.GitHub); err != nil { - if ve, ok := err.(*ValidationErrors); ok { - errs.Errors = append(errs.Errors, ve.Errors...) - } - } - case models.ProtocolVNC: - if p.VNC == nil { - errs.Add("vnc", "VNC configuration is required for VNC protocol") - } else if err := ValidateVNCConfig(p.Host, p.VNC); err != nil { - if ve, ok := err.(*ValidationErrors); ok { - errs.Errors = append(errs.Errors, ve.Errors...) - } - } - case models.ProtocolIRC: - validateProfileHostPort(errs, p.Host, p.Port) - if p.IRC == nil { - errs.Add("irc", "IRC configuration is required for IRC protocol") - } else if err := ValidateIRCConfig(p.IRC); err != nil { - if ve, ok := err.(*ValidationErrors); ok { - errs.Errors = append(errs.Errors, ve.Errors...) - } - } - default: - errs.Add("protocol", fmt.Sprintf("unsupported protocol %q", p.Protocol)) - } - - if p.ConnectTimeout < 0 { - errs.Add("connectTimeout", "connect timeout must not be negative") - } - if p.ReadTimeout < 0 { - errs.Add("readTimeout", "read timeout must not be negative") - } - if p.TerminalFontSize < 1 || p.TerminalFontSize > 72 { - errs.Add("terminalFontSize", "font size must be between 1 and 72") - } - if p.TerminalScrollback < 0 { - errs.Add("terminalScrollback", "scrollback must not be negative") - } - - if errs.HasErrors() { - return errs - } - return nil -} - -func validateProfileHostPort(errs *ValidationErrors, host string, port int) { - if err := ValidateHost(host); err != nil { - errs.Add("host", err.(*ValidationError).Message) - } - if err := ValidatePort(port); err != nil { - errs.Add("port", err.(*ValidationError).Message) - } -} - -// ValidateHost ensures the host string is non-empty and syntactically valid. -func ValidateHost(host string) error { - h := strings.TrimSpace(host) - if h == "" { - return &ValidationError{Field: "host", Message: "host must not be empty"} - } - // Reject hosts that contain whitespace. - if strings.ContainsAny(h, " \t\n\r") { - return &ValidationError{Field: "host", Message: "host must not contain whitespace"} - } - // Reject hosts that contain a scheme (e.g. "ssh://"). - if strings.Contains(h, "://") { - return &ValidationError{Field: "host", Message: "host must not include a URI scheme"} - } - // If it looks like an IP address, parse it properly. - if ip := net.ParseIP(h); ip == nil { - // Not a plain IP — verify it is a plausible hostname. - if strings.HasPrefix(h, "-") { - return &ValidationError{Field: "host", Message: "host must not begin with a hyphen"} - } - } - return nil -} - -// ValidatePort checks that the port number is within the valid TCP range. -func ValidatePort(port int) error { - if port < 1 || port > 65535 { - return &ValidationError{Field: "port", Message: "port must be between 1 and 65535"} - } - return nil -} - -// ValidateUsername checks that the username is non-empty and free of control characters. -func ValidateUsername(username string) error { - u := strings.TrimSpace(username) - if u == "" { - return &ValidationError{Field: "username", Message: "username must not be empty"} - } - for _, r := range u { - if r < 0x20 || r == 0x7f { - return &ValidationError{Field: "username", Message: "username must not contain control characters"} - } - } - return nil -} - -// ValidateSSHConfig validates the SSH-specific configuration block. -func ValidateSSHConfig(cfg *models.SSHConfig) error { - errs := &ValidationErrors{} - - if len(cfg.AuthMethods) == 0 { - errs.Add("ssh.authMethods", "at least one authentication method is required") - } - validAuth := map[string]bool{ - "password": true, "publickey": true, "keyboard-interactive": true, - } - for _, m := range cfg.AuthMethods { - if !validAuth[m] { - errs.Add("ssh.authMethods", fmt.Sprintf("unsupported authentication method %q", m)) - } - } - - validSecret := map[models.SecretMode]bool{ - models.SecretModeSaved: true, models.SecretModeSessionOnly: true, models.SecretModeAskEveryTime: true, - } - if !validSecret[cfg.SecretMode] { - errs.Add("ssh.secretMode", fmt.Sprintf("unsupported secret mode %q", cfg.SecretMode)) - } - if !validSecret[cfg.KeySecretMode] { - errs.Add("ssh.keySecretMode", fmt.Sprintf("unsupported key secret mode %q", cfg.KeySecretMode)) - } - - validPolicy := map[models.HostKeyPolicy]bool{ - models.HostKeyStrict: true, models.HostKeyAsk: true, - models.HostKeyAcceptNew: true, models.HostKeyInsecure: true, - } - if !validPolicy[cfg.HostKeyPolicy] { - errs.Add("ssh.hostKeyPolicy", fmt.Sprintf("unsupported host key policy %q", cfg.HostKeyPolicy)) - } - - if cfg.KeepaliveInterval < 0 { - errs.Add("ssh.keepaliveInterval", "keepalive interval must not be negative") - } - if cfg.KeepaliveCountMax < 0 { - errs.Add("ssh.keepaliveCountMax", "keepalive count max must not be negative") - } - - // Validate each jump host entry. - for i, jh := range cfg.JumpHosts { - prefix := fmt.Sprintf("ssh.jumpHosts[%d]", i) - if strings.TrimSpace(jh.Host) == "" { - errs.Add(prefix+".host", "jump host address must not be empty") - } - if jh.Port < 1 || jh.Port > 65535 { - errs.Add(prefix+".port", "jump host port must be between 1 and 65535") - } - if strings.TrimSpace(jh.Username) == "" { - errs.Add(prefix+".username", "jump host username must not be empty") - } - } - - // Validate each port-forwarding rule. - for i, pf := range cfg.PortForwards { - prefix := fmt.Sprintf("ssh.portForwards[%d]", i) - if pf.Direction != models.PortForwardLocal && pf.Direction != models.PortForwardRemote { - errs.Add(prefix+".direction", fmt.Sprintf("unsupported forwarding direction %q", pf.Direction)) - } - if pf.BindPort < 1 || pf.BindPort > 65535 { - errs.Add(prefix+".bindPort", "bind port must be between 1 and 65535") - } - if pf.DestPort < 1 || pf.DestPort > 65535 { - errs.Add(prefix+".destPort", "destination port must be between 1 and 65535") - } - if strings.TrimSpace(pf.DestHost) == "" { - errs.Add(prefix+".destHost", "destination host must not be empty") - } - } - - if errs.HasErrors() { - return errs - } - return nil -} - -// ValidateTelnetConfig validates the Telnet-specific configuration block. -func ValidateTelnetConfig(cfg *models.TelnetConfig) error { - errs := &ValidationErrors{} - - validEnding := map[models.LineEnding]bool{ - models.LineEndingLF: true, models.LineEndingCR: true, - models.LineEndingCRLF: true, models.LineEndingNone: true, - } - if !validEnding[cfg.LineEnding] { - errs.Add("telnet.lineEnding", fmt.Sprintf("unsupported line ending %q", cfg.LineEnding)) - } - - if strings.TrimSpace(cfg.Encoding) == "" { - errs.Add("telnet.encoding", "encoding must not be empty") - } - - if errs.HasErrors() { - return errs - } - return nil -} - -// ValidateRAWConfig validates the RAW TCP-specific configuration block. -func ValidateRAWConfig(cfg *models.RAWConfig) error { - errs := &ValidationErrors{} - - validEnding := map[models.LineEnding]bool{ - models.LineEndingLF: true, models.LineEndingCR: true, - models.LineEndingCRLF: true, models.LineEndingNone: true, - } - if !validEnding[cfg.LineEnding] { - errs.Add("raw.lineEnding", fmt.Sprintf("unsupported line ending %q", cfg.LineEnding)) - } - - if strings.TrimSpace(cfg.Encoding) == "" { - errs.Add("raw.encoding", "encoding must not be empty") - } - - validFormat := map[string]bool{"text": true, "hex": true, "escaped": true} - for i, preset := range cfg.PayloadPresets { - prefix := fmt.Sprintf("raw.payloadPresets[%d]", i) - if strings.TrimSpace(preset.Name) == "" { - errs.Add(prefix+".name", "payload preset name must not be empty") - } - if !validFormat[preset.Format] { - errs.Add(prefix+".format", fmt.Sprintf("unsupported payload format %q", preset.Format)) - } - } - - if errs.HasErrors() { - return errs - } - return nil -} - -// ValidateGitHubConfig validates GitHub API settings before tokens are sent. -func ValidateGitHubConfig(cfg *models.GitHubConfig) error { - errs := &ValidationErrors{} - - if cfg == nil { - return &ValidationError{Field: "github", Message: "GitHub configuration must not be nil"} - } - - u, err := url.Parse(strings.TrimSpace(cfg.BaseURL)) - if err != nil || u.Scheme == "" || u.Host == "" { - errs.Add("github.baseURL", "API base URL must be an absolute HTTPS URL") - } else { - if u.User != nil { - errs.Add("github.baseURL", "API base URL must not include credentials") - } - if u.Scheme != "https" && !(u.Scheme == "http" && isLoopbackHost(u.Hostname())) { - errs.Add("github.baseURL", "API base URL must use HTTPS unless it points to loopback") - } - } - - validSecret := map[models.SecretMode]bool{ - models.SecretModeSaved: true, models.SecretModeSessionOnly: true, models.SecretModeAskEveryTime: true, - } - if !validSecret[cfg.SecretMode] { - errs.Add("github.secretMode", fmt.Sprintf("unsupported secret mode %q", cfg.SecretMode)) - } - if cfg.RefreshIntervalSecs != 0 && cfg.RefreshIntervalSecs < 60 { - errs.Add("github.refreshIntervalSecs", "refresh interval must be at least 60 seconds") - } - - if errs.HasErrors() { - return errs - } - return nil -} - -// ValidateVNCConfig validates VNC connection settings. -func ValidateVNCConfig(profileHost string, cfg *models.VNCConfig) error { - errs := &ValidationErrors{} - - if cfg == nil { - return &ValidationError{Field: "vnc", Message: "VNC configuration must not be nil"} - } - - vncHost := strings.TrimSpace(cfg.VNCHost) - if vncHost == "" { - vncHost = strings.TrimSpace(profileHost) - } - if err := ValidateHost(vncHost); err != nil { - errs.Add("vnc.vncHost", err.(*ValidationError).Message) - } - if err := ValidatePort(cfg.VNCPort); err != nil { - errs.Add("vnc.vncPort", err.(*ValidationError).Message) - } - if cfg.Quality < 0 || cfg.Quality > 9 { - errs.Add("vnc.quality", "quality must be between 0 and 9") - } - - if cfg.SSHTunnel { - if err := ValidateHost(cfg.SSHHost); err != nil { - errs.Add("vnc.sshHost", err.(*ValidationError).Message) - } - if err := ValidatePort(cfg.SSHPort); err != nil { - errs.Add("vnc.sshPort", err.(*ValidationError).Message) - } - if strings.TrimSpace(cfg.SSHUsername) == "" { - errs.Add("vnc.sshUsername", "SSH username is required when tunnelling") - } else if err := ValidateUsername(cfg.SSHUsername); err != nil { - errs.Add("vnc.sshUsername", err.(*ValidationError).Message) - } - if cfg.SSHAuthMethod != "password" && cfg.SSHAuthMethod != "publickey" { - errs.Add("vnc.sshAuthMethod", fmt.Sprintf("unsupported SSH auth method %q", cfg.SSHAuthMethod)) - } - } - - if errs.HasErrors() { - return errs - } - return nil -} - -// ValidateIRCConfig validates IRC connection settings. -func ValidateIRCConfig(cfg *models.IRCConfig) error { - errs := &ValidationErrors{} - - if cfg == nil { - return &ValidationError{Field: "irc", Message: "IRC configuration must not be nil"} - } - - nick := strings.TrimSpace(cfg.Nick) - if nick == "" { - errs.Add("irc.nick", "nickname is required") - } else if !girc.IsValidNick(nick) { - errs.Add("irc.nick", "nickname is not valid for IRC") - } - - if strings.ContainsAny(cfg.RealName, "\r\n\x00") { - errs.Add("irc.realName", "real name must not contain control characters") - } - - if !isValidSecretMode(cfg.ServerPasswordMode) { - errs.Add("irc.serverPasswordMode", fmt.Sprintf("unsupported secret mode %q", cfg.ServerPasswordMode)) - } - if !isValidSecretMode(cfg.NickServMode) { - errs.Add("irc.nickServMode", fmt.Sprintf("unsupported secret mode %q", cfg.NickServMode)) - } - if cfg.NickServIdentify && !isValidNickServAccount(cfg.NickServAccount) { - errs.Add("irc.nickServAccount", "NickServ account must not contain control characters") - } - - seenRooms := map[string]bool{} - for i, room := range cfg.AutoJoinRooms { - field := fmt.Sprintf("irc.autoJoinRooms[%d]", i) - name := strings.TrimSpace(room) - if name == "" { - errs.Add(field, "room name must not be empty") - continue - } - if !girc.IsValidChannel(name) { - errs.Add(field, "room name must start with a valid IRC channel prefix") - } - key := strings.ToLower(name) - if seenRooms[key] { - errs.Add(field, "room is listed more than once") - } - seenRooms[key] = true - } - - if cfg.SSHTunnel { - if err := ValidateHost(cfg.SSHHost); err != nil { - errs.Add("irc.sshHost", err.(*ValidationError).Message) - } - if err := ValidatePort(cfg.SSHPort); err != nil { - errs.Add("irc.sshPort", err.(*ValidationError).Message) - } - if strings.TrimSpace(cfg.SSHUsername) == "" { - errs.Add("irc.sshUsername", "SSH username is required when tunnelling") - } else if err := ValidateUsername(cfg.SSHUsername); err != nil { - errs.Add("irc.sshUsername", err.(*ValidationError).Message) - } - if cfg.SSHAuthMethod != "password" && cfg.SSHAuthMethod != "publickey" { - errs.Add("irc.sshAuthMethod", fmt.Sprintf("unsupported SSH auth method %q", cfg.SSHAuthMethod)) - } - if !isValidSecretMode(cfg.SSHSecretMode) { - errs.Add("irc.sshSecretMode", fmt.Sprintf("unsupported secret mode %q", cfg.SSHSecretMode)) - } - } - - if cfg.MaxReconnectTries < 0 { - errs.Add("irc.maxReconnectTries", "max reconnect attempts must not be negative") - } - - if errs.HasErrors() { - return errs - } - return nil -} - -func isValidSecretMode(mode models.SecretMode) bool { - switch mode { - case models.SecretModeSaved, models.SecretModeSessionOnly, models.SecretModeAskEveryTime: - return true - default: - return false - } -} - -func isValidNickServAccount(account string) bool { - for _, r := range account { - if r < 0x20 || r == 0x7f { - return false - } - } - return true -} - -func isLoopbackHost(host string) bool { - h := strings.ToLower(strings.TrimSpace(host)) - if h == "localhost" { - return true - } - ip := net.ParseIP(h) - return ip != nil && ip.IsLoopback() -} diff --git a/internal/validation/validation_test.go b/internal/validation/validation_test.go deleted file mode 100644 index f115cd6..0000000 --- a/internal/validation/validation_test.go +++ /dev/null @@ -1,539 +0,0 @@ -package validation - -import ( - "testing" - - "github.com/andydixon/link/internal/models" -) - -// --------------------------------------------------------------------------- -// ValidateHost -// --------------------------------------------------------------------------- - -func TestValidateHost(t *testing.T) { - tests := []struct { - name string - host string - wantErr bool - }{ - {name: "valid hostname", host: "example.com", wantErr: false}, - {name: "valid IP address", host: "192.168.1.1", wantErr: false}, - {name: "valid IPv6 address", host: "::1", wantErr: false}, - {name: "empty string", host: "", wantErr: true}, - {name: "only spaces", host: " ", wantErr: true}, - {name: "contains spaces", host: "host name", wantErr: true}, - {name: "contains tab", host: "host\tname", wantErr: true}, - {name: "URI scheme present", host: "ssh://example.com", wantErr: true}, - {name: "leading hyphen", host: "-bad-host", wantErr: true}, - {name: "subdomain hostname", host: "server.internal.corp", wantErr: false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateHost(tt.host) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateHost(%q) error = %v, wantErr %v", tt.host, err, tt.wantErr) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ValidatePort -// --------------------------------------------------------------------------- - -func TestValidatePort(t *testing.T) { - tests := []struct { - name string - port int - wantErr bool - }{ - {name: "port 1 is valid", port: 1, wantErr: false}, - {name: "port 22 is valid", port: 22, wantErr: false}, - {name: "port 443 is valid", port: 443, wantErr: false}, - {name: "port 65535 is valid", port: 65535, wantErr: false}, - {name: "port 0 is invalid", port: 0, wantErr: true}, - {name: "negative port is invalid", port: -1, wantErr: true}, - {name: "port 65536 is invalid", port: 65536, wantErr: true}, - {name: "very large port is invalid", port: 99999, wantErr: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidatePort(tt.port) - if (err != nil) != tt.wantErr { - t.Errorf("ValidatePort(%d) error = %v, wantErr %v", tt.port, err, tt.wantErr) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ValidateUsername -// --------------------------------------------------------------------------- - -func TestValidateUsername(t *testing.T) { - tests := []struct { - name string - username string - wantErr bool - }{ - {name: "valid username", username: "admin", wantErr: false}, - {name: "empty string is invalid", username: "", wantErr: true}, - {name: "only whitespace is invalid", username: " ", wantErr: true}, - {name: "username with spaces is allowed", username: "my user", wantErr: false}, - {name: "very long username is valid", username: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789", wantErr: false}, - {name: "contains control character", username: "bad\x01user", wantErr: true}, - {name: "contains DEL character", username: "bad\x7fuser", wantErr: true}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateUsername(tt.username) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateUsername(%q) error = %v, wantErr %v", tt.username, err, tt.wantErr) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ValidateProfile -// --------------------------------------------------------------------------- - -func TestValidateProfile(t *testing.T) { - // Helper to build a valid SSH profile. - validSSH := func() *models.Profile { - p := models.NewProfile(models.ProtocolSSH) - p.DisplayName = "Test SSH" - p.Host = "example.com" - p.Username = "root" - return &p - } - - // Helper to build a valid Telnet profile. - validTelnet := func() *models.Profile { - p := models.NewProfile(models.ProtocolTelnet) - p.DisplayName = "Test Telnet" - p.Host = "switch.local" - return &p - } - - // Helper to build a valid RAW profile. - validRAW := func() *models.Profile { - p := models.NewProfile(models.ProtocolRAW) - p.DisplayName = "Test RAW" - p.Host = "device.local" - return &p - } - - validVNC := func() *models.Profile { - p := models.NewProfile(models.ProtocolVNC) - p.DisplayName = "Test VNC" - p.Host = "" - p.VNC.VNCHost = "desktop.local" - return &p - } - - tests := []struct { - name string - profile *models.Profile - wantErr bool - }{ - {name: "full valid SSH profile", profile: validSSH(), wantErr: false}, - {name: "full valid Telnet profile", profile: validTelnet(), wantErr: false}, - {name: "full valid RAW profile", profile: validRAW(), wantErr: false}, - {name: "valid VNC profile with VNC-specific host", profile: validVNC(), wantErr: false}, - {name: "nil profile", profile: nil, wantErr: true}, - { - name: "missing host", - profile: func() *models.Profile { - p := validSSH() - p.Host = "" - return p - }(), - wantErr: true, - }, - { - name: "missing port (zero)", - profile: func() *models.Profile { - p := validSSH() - p.Port = 0 - return p - }(), - wantErr: true, - }, - { - name: "invalid protocol", - profile: func() *models.Profile { - p := validSSH() - p.Protocol = "ftp" - return p - }(), - wantErr: true, - }, - { - name: "SSH missing username", - profile: func() *models.Profile { - p := validSSH() - p.Username = "" - return p - }(), - wantErr: true, - }, - { - name: "SSH nil config", - profile: func() *models.Profile { - p := validSSH() - p.SSH = nil - return p - }(), - wantErr: true, - }, - { - name: "negative connect timeout", - profile: func() *models.Profile { - p := validSSH() - p.ConnectTimeout = -1 - return p - }(), - wantErr: true, - }, - { - name: "font size out of range", - profile: func() *models.Profile { - p := validSSH() - p.TerminalFontSize = 0 - return p - }(), - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateProfile(tt.profile) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateProfile() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ValidateGitHubConfig -// --------------------------------------------------------------------------- - -func TestValidateGitHubConfig(t *testing.T) { - tests := []struct { - name string - cfg *models.GitHubConfig - wantErr bool - }{ - { - name: "valid github.com API URL", - cfg: &models.GitHubConfig{ - BaseURL: "https://api.github.com", - SecretMode: models.SecretModeSaved, - RefreshIntervalSecs: 300, - }, - wantErr: false, - }, - { - name: "valid loopback HTTP URL", - cfg: &models.GitHubConfig{ - BaseURL: "http://localhost:3000/api/v3", - SecretMode: models.SecretModeAskEveryTime, - RefreshIntervalSecs: 60, - }, - wantErr: false, - }, - { - name: "rejects non-HTTPS remote URL", - cfg: &models.GitHubConfig{ - BaseURL: "http://evil.example/api/v3", - SecretMode: models.SecretModeSaved, - RefreshIntervalSecs: 300, - }, - wantErr: true, - }, - { - name: "rejects URL credentials", - cfg: &models.GitHubConfig{ - BaseURL: "https://token@example.com/api/v3", - SecretMode: models.SecretModeSaved, - RefreshIntervalSecs: 300, - }, - wantErr: true, - }, - { - name: "rejects aggressive refresh interval", - cfg: &models.GitHubConfig{ - BaseURL: "https://api.github.com", - SecretMode: models.SecretModeSaved, - RefreshIntervalSecs: 1, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateGitHubConfig(tt.cfg) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateGitHubConfig() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ValidateVNCConfig -// --------------------------------------------------------------------------- - -func TestValidateVNCConfig(t *testing.T) { - tests := []struct { - name string - profileHost string - cfg *models.VNCConfig - wantErr bool - }{ - { - name: "uses VNC-specific host", - profileHost: "", - cfg: &models.VNCConfig{ - VNCHost: "desktop.local", - VNCPort: 5900, - Quality: 6, - }, - wantErr: false, - }, - { - name: "uses profile host fallback", - profileHost: "desktop.local", - cfg: &models.VNCConfig{ - VNCPort: 5900, - Quality: 6, - }, - wantErr: false, - }, - { - name: "rejects missing host", - profileHost: "", - cfg: &models.VNCConfig{ - VNCPort: 5900, - Quality: 6, - }, - wantErr: true, - }, - { - name: "rejects invalid SSH tunnel auth method", - profileHost: "desktop.local", - cfg: &models.VNCConfig{ - VNCPort: 5900, - Quality: 6, - SSHTunnel: true, - SSHHost: "jump.local", - SSHPort: 22, - SSHUsername: "admin", - SSHAuthMethod: "token", - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateVNCConfig(tt.profileHost, tt.cfg) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateVNCConfig() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ValidateSSHConfig -// --------------------------------------------------------------------------- - -func TestValidateSSHConfig(t *testing.T) { - validCfg := func() *models.SSHConfig { - return &models.SSHConfig{ - AuthMethods: []string{"password"}, - SecretMode: models.SecretModeSaved, - KeySecretMode: models.SecretModeAskEveryTime, - HostKeyPolicy: models.HostKeyAsk, - KeepaliveInterval: 30, - KeepaliveCountMax: 3, - JumpHosts: []models.JumpHost{}, - } - } - - tests := []struct { - name string - cfg *models.SSHConfig - wantErr bool - }{ - {name: "valid config", cfg: validCfg(), wantErr: false}, - { - name: "empty auth methods", - cfg: func() *models.SSHConfig { - c := validCfg() - c.AuthMethods = nil - return c - }(), - wantErr: true, - }, - { - name: "invalid auth method", - cfg: func() *models.SSHConfig { - c := validCfg() - c.AuthMethods = []string{"oauth"} - return c - }(), - wantErr: true, - }, - { - name: "invalid secret mode", - cfg: func() *models.SSHConfig { - c := validCfg() - c.SecretMode = "wrong" - return c - }(), - wantErr: true, - }, - { - name: "invalid host key policy", - cfg: func() *models.SSHConfig { - c := validCfg() - c.HostKeyPolicy = "yolo" - return c - }(), - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateSSHConfig(tt.cfg) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateSSHConfig() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ValidateTelnetConfig -// --------------------------------------------------------------------------- - -func TestValidateTelnetConfig(t *testing.T) { - tests := []struct { - name string - cfg *models.TelnetConfig - wantErr bool - }{ - { - name: "valid config", - cfg: &models.TelnetConfig{ - LineEnding: models.LineEndingCRLF, - Encoding: "utf-8", - }, - wantErr: false, - }, - { - name: "invalid line ending", - cfg: &models.TelnetConfig{ - LineEnding: "bad", - Encoding: "utf-8", - }, - wantErr: true, - }, - { - name: "empty encoding", - cfg: &models.TelnetConfig{ - LineEnding: models.LineEndingLF, - Encoding: "", - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateTelnetConfig(tt.cfg) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateTelnetConfig() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -// --------------------------------------------------------------------------- -// ValidateRAWConfig -// --------------------------------------------------------------------------- - -func TestValidateRAWConfig(t *testing.T) { - tests := []struct { - name string - cfg *models.RAWConfig - wantErr bool - }{ - { - name: "valid config", - cfg: &models.RAWConfig{ - LineEnding: models.LineEndingCRLF, - Encoding: "utf-8", - PayloadPresets: []models.PayloadPreset{}, - }, - wantErr: false, - }, - { - name: "invalid line ending", - cfg: &models.RAWConfig{ - LineEnding: "bad", - Encoding: "utf-8", - }, - wantErr: true, - }, - { - name: "empty encoding", - cfg: &models.RAWConfig{ - LineEnding: models.LineEndingLF, - Encoding: "", - }, - wantErr: true, - }, - { - name: "preset with invalid format", - cfg: &models.RAWConfig{ - LineEnding: models.LineEndingCRLF, - Encoding: "utf-8", - PayloadPresets: []models.PayloadPreset{ - {Name: "ping", Format: "binary"}, - }, - }, - wantErr: true, - }, - { - name: "preset with empty name", - cfg: &models.RAWConfig{ - LineEnding: models.LineEndingCRLF, - Encoding: "utf-8", - PayloadPresets: []models.PayloadPreset{ - {Name: "", Format: "text"}, - }, - }, - wantErr: true, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := ValidateRAWConfig(tt.cfg) - if (err != nil) != tt.wantErr { - t.Errorf("ValidateRAWConfig() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/internal/vncclient/vncclient.go b/internal/vncclient/vncclient.go deleted file mode 100644 index ce54347..0000000 --- a/internal/vncclient/vncclient.go +++ /dev/null @@ -1,340 +0,0 @@ -// Package vncclient provides a WebSocket-to-TCP proxy for VNC connections, -// with optional SSH tunnel support. -package vncclient - -import ( - "context" - "crypto/rand" - "encoding/hex" - "fmt" - "net" - "net/http" - "net/url" - "strconv" - "strings" - "sync" - "time" - - "github.com/gorilla/websocket" - "golang.org/x/crypto/ssh" - "golang.org/x/crypto/ssh/agent" -) - -// Client manages a VNC session, optionally tunnelled through SSH. -type Client struct { - mu sync.Mutex - ctx context.Context - cancel context.CancelFunc - listener net.Listener - sshClient *ssh.Client - wsURL string - connected bool - onError func(error) -} - -// Config holds the configuration for a VNC connection. -type Config struct { - // VNC server address - VNCHost string - VNCPort int - - // SSH tunnel settings - SSHTunnel bool - SSHHost string - SSHPort int - SSHUsername string - SSHAuthMethod string // "password", "publickey" - SSHPassword string - SSHPrivateKey []byte - SSHPassphrase string - SSHAgentAuth bool - SSHAgentConn net.Conn - SSHHostKeyCallback ssh.HostKeyCallback - - // Callbacks - OnError func(error) -} - -var upgrader = websocket.Upgrader{ - CheckOrigin: allowProxyOrigin, - ReadBufferSize: 65536, - WriteBufferSize: 65536, -} - -const maxWebSocketMessageBytes = 8 << 20 - -// NewClient creates a new VNC proxy client. -func NewClient(cfg Config) *Client { - ctx, cancel := context.WithCancel(context.Background()) - return &Client{ - ctx: ctx, - cancel: cancel, - onError: cfg.OnError, - } -} - -// Connect establishes the VNC connection (with optional SSH tunnel) and starts -// a local WebSocket proxy. Returns the WebSocket URL for the frontend. -func (c *Client) Connect(cfg Config) (string, error) { - c.mu.Lock() - defer c.mu.Unlock() - - if c.connected { - return c.wsURL, nil - } - - var vncConn net.Conn - var err error - - vncAddr := net.JoinHostPort(cfg.VNCHost, strconv.Itoa(cfg.VNCPort)) - - if cfg.SSHTunnel { - // Establish SSH connection first - sshAddr := net.JoinHostPort(cfg.SSHHost, strconv.Itoa(cfg.SSHPort)) - if cfg.SSHHostKeyCallback == nil { - return "", fmt.Errorf("SSH host key callback is required") - } - sshConfig := &ssh.ClientConfig{ - User: cfg.SSHUsername, - HostKeyCallback: cfg.SSHHostKeyCallback, - Timeout: 15 * time.Second, - } - - // Configure auth methods - var authMethods []ssh.AuthMethod - if cfg.SSHAgentAuth && cfg.SSHAgentConn != nil { - agent := newSSHAgent(cfg.SSHAgentConn) - if agent != nil { - authMethods = append(authMethods, agent) - } - } - if len(cfg.SSHPrivateKey) > 0 { - var signer ssh.Signer - if cfg.SSHPassphrase != "" { - signer, err = ssh.ParsePrivateKeyWithPassphrase(cfg.SSHPrivateKey, []byte(cfg.SSHPassphrase)) - } else { - signer, err = ssh.ParsePrivateKey(cfg.SSHPrivateKey) - } - if err == nil { - authMethods = append(authMethods, ssh.PublicKeys(signer)) - } - } - if cfg.SSHPassword != "" { - authMethods = append(authMethods, ssh.Password(cfg.SSHPassword)) - } - sshConfig.Auth = authMethods - - c.sshClient, err = ssh.Dial("tcp", sshAddr, sshConfig) - if err != nil { - return "", fmt.Errorf("SSH connection failed: %w", err) - } - - // Dial VNC through the SSH tunnel - // When tunnelling, VNC address is relative to the SSH server - tunnelAddr := vncAddr - if cfg.VNCHost == "" || cfg.VNCHost == cfg.SSHHost { - tunnelAddr = fmt.Sprintf("127.0.0.1:%d", cfg.VNCPort) - } - vncConn, err = c.sshClient.Dial("tcp", tunnelAddr) - if err != nil { - c.sshClient.Close() - c.sshClient = nil - return "", fmt.Errorf("VNC tunnel dial failed: %w", err) - } - } else { - // Direct TCP connection to VNC server - vncConn, err = net.DialTimeout("tcp", vncAddr, 15*time.Second) - if err != nil { - return "", fmt.Errorf("VNC connection failed: %w", err) - } - } - - // Start local WebSocket proxy - wsURL, err := c.startProxy(vncConn) - if err != nil { - vncConn.Close() - if c.sshClient != nil { - c.sshClient.Close() - c.sshClient = nil - } - return "", fmt.Errorf("failed to start WebSocket proxy: %w", err) - } - - c.wsURL = wsURL - c.connected = true - return wsURL, nil -} - -// startProxy creates a local HTTP server that upgrades to WebSocket and bridges -// traffic to the VNC TCP connection. -func (c *Client) startProxy(vncConn net.Conn) (string, error) { - // Listen on a random available port on localhost - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return "", err - } - token, err := newProxyToken() - if err != nil { - listener.Close() - return "", err - } - c.listener = listener - - port := listener.Addr().(*net.TCPAddr).Port - proxyPath := "/" + token - wsURL := fmt.Sprintf("ws://127.0.0.1:%d%s", port, proxyPath) - - mux := http.NewServeMux() - mux.HandleFunc(proxyPath, func(w http.ResponseWriter, r *http.Request) { - ws, err := upgrader.Upgrade(w, r, nil) - if err != nil { - return - } - defer ws.Close() - ws.SetReadLimit(maxWebSocketMessageBytes) - - // Bridge WebSocket ↔ VNC TCP in both directions - done := make(chan struct{}, 2) - - // VNC → WebSocket - go func() { - defer func() { done <- struct{}{} }() - buf := make([]byte, 65536) - for { - n, err := vncConn.Read(buf) - if n > 0 { - if writeErr := ws.WriteMessage(websocket.BinaryMessage, buf[:n]); writeErr != nil { - return - } - } - if err != nil { - return - } - } - }() - - // WebSocket → VNC - go func() { - defer func() { done <- struct{}{} }() - for { - _, msg, err := ws.ReadMessage() - if err != nil { - return - } - if _, err := vncConn.Write(msg); err != nil { - return - } - } - }() - - // Wait for either direction to close - select { - case <-done: - case <-c.ctx.Done(): - } - }) - - server := &http.Server{ - Handler: mux, - ReadTimeout: 0, // VNC is long-lived - WriteTimeout: 0, - } - - go func() { - if err := server.Serve(listener); err != nil && err != http.ErrServerClosed { - if c.onError != nil { - c.onError(fmt.Errorf("VNC proxy error: %w", err)) - } - } - }() - - // Close server when context is cancelled - go func() { - <-c.ctx.Done() - server.Close() - vncConn.Close() - }() - - return wsURL, nil -} - -func allowProxyOrigin(r *http.Request) bool { - origin := r.Header.Get("Origin") - if origin == "" { - return true - } - u, err := url.Parse(origin) - if err != nil { - return false - } - switch strings.ToLower(u.Scheme) { - case "wails": - return true - case "http", "https": - return isLoopbackHost(u.Hostname()) - default: - return false - } -} - -func isLoopbackHost(host string) bool { - h := strings.ToLower(strings.TrimSpace(host)) - if h == "localhost" { - return true - } - ip := net.ParseIP(h) - return ip != nil && ip.IsLoopback() -} - -func newProxyToken() (string, error) { - token := make([]byte, 16) - if _, err := rand.Read(token); err != nil { - return "", fmt.Errorf("generate proxy token: %w", err) - } - return hex.EncodeToString(token), nil -} - -// Close shuts down the VNC proxy and any SSH tunnel. -func (c *Client) Close() { - c.mu.Lock() - defer c.mu.Unlock() - - c.cancel() - c.connected = false - - if c.listener != nil { - c.listener.Close() - c.listener = nil - } - if c.sshClient != nil { - c.sshClient.Close() - c.sshClient = nil - } -} - -// IsConnected returns whether the VNC session is active. -func (c *Client) IsConnected() bool { - c.mu.Lock() - defer c.mu.Unlock() - return c.connected -} - -// WSURL returns the WebSocket URL for the frontend to connect to. -func (c *Client) WSURL() string { - c.mu.Lock() - defer c.mu.Unlock() - return c.wsURL -} - -// newSSHAgent creates an SSH agent auth method from the given connection. -func newSSHAgent(conn net.Conn) ssh.AuthMethod { - if conn == nil { - return nil - } - ag := agent.NewClient(conn) - signers, err := ag.Signers() - if err != nil || len(signers) == 0 { - return nil - } - return ssh.PublicKeys(signers...) -} diff --git a/internal/vncclient/vncclient_test.go b/internal/vncclient/vncclient_test.go deleted file mode 100644 index 11a6f28..0000000 --- a/internal/vncclient/vncclient_test.go +++ /dev/null @@ -1,61 +0,0 @@ -package vncclient - -import ( - "net/http" - "testing" -) - -func TestNewProxyTokenIsRandomHex(t *testing.T) { - first, err := newProxyToken() - if err != nil { - t.Fatalf("newProxyToken() error = %v", err) - } - second, err := newProxyToken() - if err != nil { - t.Fatalf("newProxyToken() second error = %v", err) - } - - if len(first) != 32 { - t.Fatalf("token length = %d, want 32", len(first)) - } - if first == second { - t.Fatal("expected separate tokens to differ") - } - for _, ch := range first { - if !((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f')) { - t.Fatalf("token contains non-hex character %q", ch) - } - } -} - -func TestAllowProxyOrigin(t *testing.T) { - tests := []struct { - name string - origin string - want bool - }{ - {name: "missing origin", origin: "", want: true}, - {name: "wails origin", origin: "wails://wails", want: true}, - {name: "localhost http", origin: "http://localhost:34115", want: true}, - {name: "loopback ipv4", origin: "http://127.0.0.1:34115", want: true}, - {name: "loopback ipv6", origin: "http://[::1]:34115", want: true}, - {name: "remote http", origin: "http://example.com", want: false}, - {name: "lookalike host", origin: "http://127.0.0.1.example.com", want: false}, - {name: "bad origin", origin: "://bad", want: false}, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - req, err := http.NewRequest(http.MethodGet, "http://127.0.0.1/ws", nil) - if err != nil { - t.Fatal(err) - } - if tt.origin != "" { - req.Header.Set("Origin", tt.origin) - } - if got := allowProxyOrigin(req); got != tt.want { - t.Fatalf("allowProxyOrigin(%q) = %v, want %v", tt.origin, got, tt.want) - } - }) - } -} diff --git a/main.go b/main.go deleted file mode 100644 index f1a0906..0000000 --- a/main.go +++ /dev/null @@ -1,79 +0,0 @@ -// Alchementary Link – a desktop SSH, Telnet and RAW TCP client built with Wails v2. -// This is the main entry point for the application. -package main - -import ( - "log" - - "github.com/wailsapp/wails/v2" - "github.com/wailsapp/wails/v2/pkg/menu" - "github.com/wailsapp/wails/v2/pkg/menu/keys" - "github.com/wailsapp/wails/v2/pkg/options" - "github.com/wailsapp/wails/v2/pkg/options/assetserver" - "github.com/wailsapp/wails/v2/pkg/options/linux" - "github.com/wailsapp/wails/v2/pkg/options/mac" - "github.com/wailsapp/wails/v2/pkg/options/windows" - - "github.com/andydixon/link/frontend" - application "github.com/andydixon/link/internal/app" -) - -func buildAppMenu(app *application.App) *menu.Menu { - mainMenu := menu.NewMenu() - - appMenu := menu.NewMenu() - appMenu.AddText("About Alchementary Link", nil, func(_ *menu.CallbackData) { - app.EmitOpenAbout() - }) - appMenu.AddSeparator() - appMenu.AddText("Quit Alchementary Link", keys.CmdOrCtrl("q"), func(_ *menu.CallbackData) { - app.RequestQuit() - }) - - helpMenu := menu.NewMenu() - helpMenu.AddText("About Alchementary Link", nil, func(_ *menu.CallbackData) { - app.EmitOpenAbout() - }) - - mainMenu.Append(menu.SubMenu("Alchementary Link", appMenu)) - mainMenu.Append(menu.EditMenu()) - mainMenu.Append(menu.WindowMenu()) - mainMenu.Append(menu.SubMenu("Help", helpMenu)) - - return mainMenu -} - -func main() { - link := application.NewApp() - - // The API service is created during Startup once storage is initialised. - // We bind the App itself so Wails can call lifecycle hooks, and use - // OnStartup to wire everything, then bind the API service separately. - err := wails.Run(&options.App{ - Title: "Alchementary Link", - Width: 1400, - Height: 900, - MinWidth: 1024, - MinHeight: 600, - AssetServer: &assetserver.Options{ - Assets: frontend.Assets, - }, - BackgroundColour: &options.RGBA{R: 26, G: 26, B: 46, A: 255}, // #1a1a2e - OnStartup: link.Startup, - OnShutdown: link.Shutdown, - OnDomReady: link.DomReady, - Menu: buildAppMenu(link), - // macOS: enable the green fullscreen traffic-light button. - Mac: &mac.Options{}, - // Windows: default decorations include maximise; fullscreen via F11. - Windows: &windows.Options{}, - // Linux: use the default window manager decorations (fullscreen via WM). - Linux: &linux.Options{}, - Bind: []interface{}{ - link.APIService(), - }, - }) - if err != nil { - log.Fatalf("failed to start Alchementary Link: %v", err) - } -} diff --git a/release.sh b/release.sh index 1c3330c..7ed7888 100755 --- a/release.sh +++ b/release.sh @@ -1,458 +1,3 @@ #!/usr/bin/env bash -# release.sh — Build and package release artefacts for Alchementary Link. -# Produces per-platform zips, manifests, and version files for every supported -# 64-bit desktop OS/architecture combination. -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" -source "$SCRIPT_DIR/scripts/build-common.sh" - -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -DEFAULT_UPDATE_URL="https://alchementary.co.uk/download/link/manifest-{platform}-{arch}.json" -DEFAULT_RELEASE_BASE_URL="https://alchementary.co.uk/download/link" -WAILS_VERSION="$(link_wails_version go.mod)" -GO_TOOLCHAIN_VERSION="$( - awk ' - $1 == "go" { go_version = $2 } - $1 == "toolchain" { - sub(/^go/, "", $2) - toolchain_version = $2 - } - END { - if (toolchain_version != "") { - print toolchain_version - } else { - print go_version - } - } - ' go.mod -)" -DEFAULT_LINUX_BUILD_IMAGE="docker.io/library/golang:${GO_TOOLCHAIN_VERSION:-1.26}-bookworm" -DEFAULT_NODE_VERSION="20.19.4" - -log() { echo -e "${GREEN}[link]${NC} $1"; } -warn() { echo -e "${YELLOW}[link]${NC} $1"; } -err() { echo -e "${RED}[link]${NC} $1" >&2; } - -host_os() { - case "$(uname -s)" in - Darwin) echo "darwin" ;; - Linux) echo "linux" ;; - MINGW*|MSYS*|CYGWIN*) echo "windows" ;; - *) echo "unknown" ;; - esac -} - -build_tags() { - link_build_tags -} - -default_targets() { - printf '%s\n' \ - "darwin/amd64" \ - "darwin/arm64" \ - "linux/amd64" \ - "linux/arm64" \ - "windows/amd64" \ - "windows/arm64" -} - -release_targets() { - if [ -n "${LINK_RELEASE_TARGETS:-}" ]; then - echo "$LINK_RELEASE_TARGETS" | tr ',' '\n' | sed '/^[[:space:]]*$/d' - return - fi - - default_targets -} - -check_deps() { - command -v go >/dev/null 2>&1 || { err "Go is not installed"; exit 1; } - export PATH="$PATH:$(go env GOPATH)/bin" - command -v node >/dev/null 2>&1 || { err "Node.js is not installed"; exit 1; } - command -v npm >/dev/null 2>&1 || { err "npm is not installed"; exit 1; } - command -v zip >/dev/null 2>&1 || { err "zip is not installed"; exit 1; } - command -v shasum >/dev/null 2>&1 || { err "shasum is not installed"; exit 1; } - - if ! command -v wails >/dev/null 2>&1; then - if [ -f "$HOME/go/bin/wails" ]; then - export PATH="$PATH:$HOME/go/bin" - else - if [ -z "$WAILS_VERSION" ]; then - err "Wails is not installed and no pinned Wails version was found in go.mod" - exit 1 - fi - log "Installing pinned Wails CLI ${WAILS_VERSION}..." - go install "github.com/wailsapp/wails/v2/cmd/wails@${WAILS_VERSION}" - export PATH="$PATH:$(go env GOPATH)/bin" - fi - fi - - ensure_upx -} - -install_deps() { - log "Installing Go dependencies..." - go mod download - - log "Installing frontend dependencies..." - if [ -f frontend/package-lock.json ]; then - (cd frontend && npm ci) - else - (cd frontend && npm install) - fi -} - -build_version() { - link_build_version -} - -build_ldflags() { - link_build_ldflags "$1" -} - -container_engine() { - local candidate - for candidate in podman docker; do - if command -v "$candidate" >/dev/null 2>&1 \ - && "$candidate" info >/dev/null 2>&1; then - echo "$candidate" - return 0 - fi - done - return 1 -} - -ensure_upx() { - if ! link_bool_enabled "${LINK_COMPRESS:-0}"; then - return - fi - local requires_upx=0 - local target - while IFS= read -r target; do - [ -n "$target" ] || continue - local target_os="${target%%/*}" - if link_use_upx_for_target "$target_os"; then - requires_upx=1 - break - fi - done < <(release_targets) - if [ "$requires_upx" -eq 0 ]; then - return - fi - if command -v upx >/dev/null 2>&1; then - return - fi - err "LINK_COMPRESS=1 requires UPX. Install it first, for example: brew install upx" - exit 1 -} - -build_linux_target_in_container() { - local target="$1" - local version="$2" - local out_dir="$3" - local target_os="${target%%/*}" - local target_arch="${target##*/}" - - local engine - if ! engine="$(container_engine)"; then - warn "Linux targets require a running Podman or Docker engine on $(host_os)." - return 1 - fi - - local node_arch - case "$target_arch" in - amd64) node_arch="x64" ;; - arm64) node_arch="arm64" ;; - *) - warn "No Node.js Linux archive mapping for ${target_arch}." - return 1 - ;; - esac - - mkdir -p "$SCRIPT_DIR/build" - local container_release_dir - container_release_dir="$(mktemp -d "$SCRIPT_DIR/build/.release-linux.XXXXXX")" - local container_release_root="build/$(basename "$container_release_dir")" - local image="${LINK_LINUX_BUILD_IMAGE:-$DEFAULT_LINUX_BUILD_IMAGE}" - local node_version="${LINK_NODE_VERSION:-$DEFAULT_NODE_VERSION}" - local builder_image="link-linux-release:${GO_TOOLCHAIN_VERSION:-1.26}-${node_version}-${target_arch}" - - log "Preparing the ${engine} Linux/${target_arch} release builder..." - if ! "$engine" build \ - --platform "linux/${target_arch}" \ - --build-arg "GO_IMAGE=${image}" \ - --build-arg "NODE_ARCH=${node_arch}" \ - --build-arg "NODE_VERSION=${node_version}" \ - --build-arg "WAILS_VERSION=${WAILS_VERSION}" \ - -t "$builder_image" \ - -f "$SCRIPT_DIR/scripts/linux-release.Dockerfile" \ - "$SCRIPT_DIR/scripts"; then - warn "Could not prepare the Linux/${target_arch} release builder." - rm -rf -- "$container_release_dir" - return 1 - fi - - log "Building ${target} in the ${engine} container..." - if ! "$engine" run --rm \ - --platform "linux/${target_arch}" \ - -v "${SCRIPT_DIR}:/src" \ - --tmpfs /src/frontend/node_modules \ - -w /src \ - -e "LINK_RELEASE_TARGETS=${target}" \ - -e "LINK_RELEASE_ROOT=${container_release_root}" \ - -e "LINK_UPDATE_URL=${LINK_UPDATE_URL}" \ - -e "LINK_RELEASE_BASE_URL=${LINK_RELEASE_BASE_URL}" \ - -e "GIT_CONFIG_COUNT=1" \ - -e "GIT_CONFIG_KEY_0=safe.directory" \ - -e "GIT_CONFIG_VALUE_0=/src" \ - "$builder_image" \ - ./release.sh; then - warn "Container build failed for ${target}." - rm -rf -- "$container_release_dir" - return 1 - fi - - local output_base="AlchementaryLink-${target_os}-${target_arch}" - local container_out="${container_release_dir}/${version}" - if [ ! -f "${container_out}/${output_base}" ] \ - || [ ! -f "${container_out}/${output_base}.zip" ]; then - warn "Container did not produce the expected artefacts for ${target}." - rm -rf -- "$container_release_dir" - return 1 - fi - - cp "${container_out}/${output_base}" "${out_dir}/${output_base}" - cp "${container_out}/${output_base}.zip" "${out_dir}/${output_base}.zip" - echo "${target_os}|${target_arch}|${output_base}.zip" >> "${out_dir}/.release-files" - rm -rf -- "$container_release_dir" -} - -build_target() { - local target_os="$1" - local target_arch="$2" - local version="$3" - local out_dir="$4" - - local platform="${target_os}/${target_arch}" - - local output_base="AlchementaryLink-${target_os}-${target_arch}" - local output_path - if [ "$target_os" = "darwin" ]; then - output_path="${out_dir}/${output_base}.app" - elif [ "$target_os" = "windows" ]; then - output_path="${out_dir}/${output_base}.exe" - else - output_path="${out_dir}/${output_base}" - fi - - log "Building ${platform}..." - link_configure_pure_go_build - local ldflags - ldflags="$(build_ldflags "$version")" - local tags - tags="$(build_tags)" - local build_args=("-platform" "$platform" "-trimpath") - if link_use_upx_for_target "$target_os"; then - build_args+=("-upx") - if [ -n "${LINK_UPX_FLAGS:-}" ]; then - build_args+=("-upxflags" "$LINK_UPX_FLAGS") - fi - elif link_skips_upx_for_target "$target_os"; then - warn "Skipping UPX compression for ${platform}; distribute the zipped .app instead, or set LINK_FORCE_MACOS_UPX=1 with suitable LINK_UPX_FLAGS." - fi - if [ -n "$tags" ]; then - build_args+=("-tags" "$tags") - fi - build_args+=("-ldflags" "$ldflags" "-o" "$output_path") - if ! CGO_ENABLED=0 wails build "${build_args[@]}"; then - warn "Build failed for ${platform}" - return 1 - fi - - local expected_path="" - local build_bin="build/bin" - if [ "$target_os" = "darwin" ]; then - expected_path="${out_dir}/${output_base}.app" - if [ ! -d "$expected_path" ]; then - local source_app - source_app="$(find "$build_bin" -type d -name "*.app" -print0 | xargs -0 ls -td 2>/dev/null | head -1)" - if [ -z "$source_app" ]; then - warn "Built .app not found for ${platform}" - return 1 - fi - log "Using app bundle: ${source_app}" - cp -R "$source_app" "$expected_path" - fi - if ! (cd "$out_dir" && zip -r "${output_base}.zip" "${output_base}.app" >/dev/null); then - warn "Zip failed for ${platform}" - return 1 - fi - elif [ "$target_os" = "windows" ]; then - expected_path="${out_dir}/${output_base}.exe" - if [ ! -f "$expected_path" ]; then - local source_exe - source_exe="$(find "$build_bin" -type f -name "${output_base}.exe" -print0 | xargs -0 ls -t 2>/dev/null | head -1)" - if [ -z "$source_exe" ]; then - warn "Built .exe not found for ${platform}" - return 1 - fi - log "Using executable: ${source_exe}" - cp "$source_exe" "$expected_path" - fi - if ! (cd "$out_dir" && zip -j "${output_base}.zip" "${output_base}.exe" >/dev/null); then - warn "Zip failed for ${platform}" - return 1 - fi - else - expected_path="${out_dir}/${output_base}" - if [ ! -f "$expected_path" ]; then - local source_bin - source_bin="$(find "$build_bin" -type f -name "${output_base}" -print0 | xargs -0 ls -t 2>/dev/null | head -1)" - if [ -z "$source_bin" ] || [ ! -f "$source_bin" ]; then - warn "Built binary not found for ${platform}" - return 1 - fi - log "Using binary: ${source_bin}" - cp "$source_bin" "$expected_path" - fi - if ! (cd "$out_dir" && zip -j "${output_base}.zip" "${output_base}" >/dev/null); then - warn "Zip failed for ${platform}" - return 1 - fi - fi - - echo "$target_os|$target_arch|$output_base.zip" >> "${out_dir}/.release-files" -} - -write_manifest() { - local version="$1" - local out_dir="$2" - local release_root="$3" - local base_url="${LINK_RELEASE_BASE_URL:-}" - - local published_at - published_at="$(date -u +"%Y-%m-%dT%H:%M:%SZ")" - - while IFS='|' read -r os arch filename; do - [ -z "$os" ] && continue - local manifest_path="${release_root}/manifest-${os}-${arch}.json" - local version_path="${release_root}/version-${os}-${arch}.txt" - local file_path="${out_dir}/${filename}" - local sha - sha="$(shasum -a 256 "$file_path" | awk '{print $1}')" - local size - size="$(wc -c < "$file_path" | tr -d ' ')" - local url="" - if [ -n "$base_url" ]; then - url="${base_url}/${filename}" - fi - - echo "$version" > "$version_path" - - { - echo "{" - echo " \"latestVersion\": \"${version}\"," - echo " \"publishedAt\": \"${published_at}\"," - if [ -n "$base_url" ]; then - echo " \"releaseURL\": \"${base_url}/\"," - else - echo " \"releaseURL\": \"\"," - fi - echo " \"files\": [" - printf " {\"platform\":\"%s\",\"arch\":\"%s\",\"filename\":\"%s\",\"url\":\"%s\",\"sha256\":\"%s\",\"size\":%s}" "$os" "$arch" "$filename" "$url" "$sha" "$size" - echo - echo " ]" - echo "}" - } > "$manifest_path" - done < "${out_dir}/.release-files" - - rm -f "${out_dir}/.release-files" -} - -main() { - check_deps - install_deps - - local version - version="$(build_version)" - - if [ -z "${LINK_UPDATE_URL:-}" ]; then - LINK_UPDATE_URL="${DEFAULT_UPDATE_URL}" - fi - if [ -z "${LINK_RELEASE_BASE_URL:-}" ]; then - LINK_RELEASE_BASE_URL="${DEFAULT_RELEASE_BASE_URL}" - fi - - local release_root="${LINK_RELEASE_ROOT:-build/releases}" - local out_dir="${release_root}/${version}" - mkdir -p "$out_dir" - - log "Building release ${version}..." - : > "${out_dir}/.release-files" - - local targets=() - local selected_target - while IFS= read -r selected_target; do - [ -n "$selected_target" ] && targets+=("$selected_target") - done < <(release_targets) - if [ "${#targets[@]}" -eq 0 ]; then - err "No release targets selected." - exit 1 - fi - - local errors=0 - local host - host="$(host_os)" - local target - local container_linux_targets=() - if [ "$host" != "linux" ]; then - for target in "${targets[@]}"; do - if [[ "$target" == linux/* ]]; then - container_linux_targets+=("$target") - fi - done - fi - - if [ "${#container_linux_targets[@]}" -gt 0 ]; then - for target in "${container_linux_targets[@]}"; do - if ! build_linux_target_in_container "$target" "$version" "$out_dir"; then - errors=1 - fi - done - fi - - for target in "${targets[@]}"; do - local target_os="${target%%/*}" - local target_arch="${target##*/}" - if [ "$target_os" = "$target_arch" ] || [ -z "$target_os" ] || [ -z "$target_arch" ]; then - err "Invalid release target: ${target}" - errors=1 - continue - fi - if [ "$target_os" = "linux" ] && [ "$host" != "linux" ]; then - continue - fi - if ! build_target "$target_os" "$target_arch" "$version" "$out_dir"; then - errors=1 - fi - done - - write_manifest "$version" "$out_dir" "$release_root" - - log "Release artefacts written to ${out_dir}" - log "Manifests: ${release_root}/manifest--.json" - log "Versions: ${release_root}/version--.txt" - - if [ $errors -ne 0 ]; then - err "One or more targets failed. Manifest and version were still generated." - exit 12 - fi -} - -main "$@" +# Release builds live with the Rust workspace; see docs/build-and-release.md. +exec "$(dirname "${BASH_SOURCE[0]}")/rust/release.sh" "$@" diff --git a/rust/.gitignore b/rust/.gitignore deleted file mode 100644 index 2f7896d..0000000 --- a/rust/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target/ diff --git a/rust/core/tests/fixtures/README.md b/rust/core/tests/fixtures/README.md new file mode 100644 index 0000000..24c2f92 --- /dev/null +++ b/rust/core/tests/fixtures/README.md @@ -0,0 +1,12 @@ +# Compatibility fixtures + +Generated by the last Go build of Alchementary Link with `cmd/genfixtures` (see commit f588a79, +`git show f588a79:cmd/genfixtures/main.go`). Nexus Terminal must keep reading them unchanged: + +- `link.db` — a database through migration 006 with every protocol, a known host, a snippet, + IRC channel state and preferences. +- `vault/vault.json` — a vault whose master password is `correct horse battery staple`. +- `profiles.link` — an encrypted export with password `export-pass`. +- `recordings/rec1.cast` — an asciicast v2 recording. + +Don't regenerate them; they are the reference for data written by the old app. diff --git a/scripts/build-common.sh b/scripts/build-common.sh deleted file mode 100644 index b3e7776..0000000 --- a/scripts/build-common.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env bash -# Shared build helpers for local builds, release packaging, and CI snippets. - -link_wails_version() { - local go_mod="${1:-go.mod}" - awk '$1 == "github.com/wailsapp/wails/v2" { print $2; exit }' "$go_mod" -} - -link_bool_enabled() { - case "${1:-}" in - 1|true|TRUE|yes|YES|on|ON) return 0 ;; - *) return 1 ;; - esac -} - -link_use_upx_for_target() { - local target_os="$1" - link_bool_enabled "${LINK_COMPRESS:-0}" || return 1 - if [ "$target_os" = "darwin" ] && ! link_bool_enabled "${LINK_FORCE_MACOS_UPX:-0}"; then - return 1 - fi - return 0 -} - -link_skips_upx_for_target() { - local target_os="$1" - link_bool_enabled "${LINK_COMPRESS:-0}" || return 1 - [ "$target_os" = "darwin" ] || return 1 - ! link_bool_enabled "${LINK_FORCE_MACOS_UPX:-0}" -} - -link_configure_pure_go_build() { - export CGO_ENABLED=0 - unset CGO_CFLAGS - unset CGO_CXXFLAGS -} - -link_build_tags() { - if command -v pkg-config >/dev/null 2>&1 \ - && pkg-config --exists webkit2gtk-4.1 \ - && ! pkg-config --exists webkit2gtk-4.0; then - echo "webkit2_41" - fi -} - -link_build_version() { - local tag - local commit - tag="$(git describe --tags --abbrev=0 2>/dev/null || echo "dev")" - commit="$(git rev-parse --short HEAD 2>/dev/null || echo "unknown")" - echo "${tag}-${commit}" -} - -link_build_ldflags() { - local version="$1" - local flags="-s -w -buildid= -X github.com/andydixon/link/internal/api.appVersion=${version}" - if [ -n "${LINK_UPDATE_URL:-}" ]; then - flags="${flags} -X github.com/andydixon/link/internal/api.appUpdateURL=${LINK_UPDATE_URL}" - fi - echo "${flags}" -} diff --git a/scripts/linux-release.Dockerfile b/scripts/linux-release.Dockerfile deleted file mode 100644 index 6ae2c44..0000000 --- a/scripts/linux-release.Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -ARG GO_IMAGE -FROM ${GO_IMAGE} - -ARG NODE_ARCH -ARG NODE_VERSION -ARG WAILS_VERSION - -ENV PATH="/opt/node/bin:/go/bin:/usr/local/go/bin:${PATH}" - -RUN apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - ca-certificates \ - curl \ - git \ - libdigest-sha-perl \ - libgtk-3-dev \ - libwebkit2gtk-4.1-dev \ - pkg-config \ - xz-utils \ - zip \ - && node_archive="node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" \ - && curl -fsSLo "/tmp/${node_archive}" \ - "https://nodejs.org/dist/v${NODE_VERSION}/${node_archive}" \ - && mkdir -p /opt/node \ - && tar -xJf "/tmp/${node_archive}" -C /opt/node --strip-components=1 \ - && rm -f "/tmp/${node_archive}" \ - && go install "github.com/wailsapp/wails/v2/cmd/wails@${WAILS_VERSION}" \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /src diff --git a/wails.json b/wails.json deleted file mode 100644 index f3e297e..0000000 --- a/wails.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$schema": "https://wails.io/schemas/config.v2.json", - "name": "alchementary-link", - "outputfilename": "AlchementaryLink", - "frontend:install": "npm ci", - "frontend:build": "npm run build", - "frontend:dev:watcher": "npm run dev", - "frontend:dev:serverUrl": "auto", - "author": { - "name": "Alchementary Link Contributors" - }, - "info": { - "companyName": "Alchementary Link", - "productName": "Alchementary Link", - "productVersion": "0.1.0", - "copyright": "Copyright © 2024 Alchementary Link Contributors", - "comments": "A premium desktop SSH, Telnet, and RAW TCP client" - }, - "windows": { - "icon": "build/windows/icon.ico" - } -} From b3de13f0b7f598607192997e4b919c0b80976fed Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:49:37 +0100 Subject: [PATCH 35/45] docs: port status --- docs/rust-port-plan.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/rust-port-plan.md b/docs/rust-port-plan.md index 321bc17..d9c96e9 100644 --- a/docs/rust-port-plan.md +++ b/docs/rust-port-plan.md @@ -1,5 +1,22 @@ # Rust port plan: Nexus Terminal, a native egui app with no Go and no webview +> **Status (11 October 2026): converted.** Every phase below has been carried out on the +> `rust-port` branch; Go, Node and Wails are gone. What was verified and what wasn't: +> +> - **Verified here (Linux):** the Go-generated fixtures (database, vault, `.link` export, +> recording) load; SSH against a real OpenSSH server (key and agent auth, PTY shell, exec, +> local/remote forwards, SFTP, jump host, agent forwarding, host-key rejection), including +> through the GUI with the host-key prompt and login scripts; local shell, RAW, Telnet/RAW/serial +> client tests (serial on a pty pair); the session manager (events, reconnect backoff, restart of +> an exited local shell); the terminal renderer (colours, 256/true colour, CJK fallback); every +> window rendered headlessly; `cargo clippy` clean; the Windows target builds and links. +> - **Not verified:** macOS builds (need Apple's C toolchain), running on Windows or macOS, the OS +> keychain on a real desktop session (none was available, so the go-keyring compatibility in §3 +> is untested), IRC, GitHub and VNC against real servers (tested against fakes), serial hardware. +> - **Still open:** the update/release URLs still point at alchementary.co.uk (§7) — to move to +> dixon.cx, change `DEFAULT_UPDATE_URL`/`DEFAULT_RELEASE_BASE_URL` in `rust/release.sh` and +> `DEFAULT_UPDATE_URL` in `rust/core/src/updater.rs`. + Goal: rebuild Alchementary Link in Rust as **Nexus Terminal**, a single Rust application in the style of Leviathan Desktop (`../leviathan/rust`). - **UI:** the Wails webview (HTML/CSS/JS, xterm.js) is replaced by an **eframe/egui 0.36** shell with From 8c646e71767c968917f34848db69741fe2ed137e Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:52:35 +0100 Subject: [PATCH 36/45] docs: rewrite for Nexus Terminal --- .github/copilot-instructions.md | 151 +++++----- README.md | 81 +++--- docs/overview.md | 343 ++++++++++------------ docs/quick-start.md | 55 ++-- docs/user-manual.md | 488 +++++++++++++++++++++----------- 5 files changed, 613 insertions(+), 505 deletions(-) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 0a8e9d7..e6bfdb7 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,109 +1,86 @@ -# Copilot Instructions — Alchementary Link +# Copilot Instructions — Nexus Terminal + +Nexus Terminal is a native desktop client (SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub, local shell) written in Rust with an eframe/egui UI. All the code lives in the Cargo workspace under `rust/`. ## Build & Test ```bash -make deps # go mod download + npm install -make build # production build → build/bin/alchementary-link.app -make dev # wails dev with hot reload -make test # go test ./... -v -count=1 -make lint # golangci-lint run ./... +cd rust +cargo run --release # the app (binary `nexus-terminal`) +cargo test --workspace # unit and integration tests +cargo clippy --workspace --all-targets -- -D warnings # must be clean +cargo fmt --all --check # must be clean -# Single package test -go test ./internal/storage/ -v -count=1 -run TestProfileCRUD +# Single test +cargo test -p nexus-core --lib storage:: +cargo test -p nexus-core --test ssh_integration ``` -The frontend has no separate build step — `npm run build` (called by Wails) copies xterm vendor libs from `node_modules` into `frontend/src/js/lib/` and `frontend/src/css/lib/`. All frontend assets are embedded at compile time via `frontend/embed.go`. +CI (`.github/workflows/ci.yml`) runs `cargo test --locked`, `cargo fmt --check` and `cargo clippy --all-targets -D warnings` on Linux, macOS and Windows. The toolchain is pinned in `rust/rust-toolchain.toml`. Platform prerequisites and release packaging are in `docs/build-and-release.md`. ## Architecture -Wails v2 desktop app: Go backend + HTML/CSS/JS frontend rendered in a native webview (WKWebView on macOS). +The project is engine-first: a UI-free core crate, and the egui app is one client of it. -**Go backend** (`internal/`): -- `app/` — lifecycle bootstrap: opens SQLite, inits secret stores, wires everything together -- `api/` — single `Service` struct bound to Wails; every public method is callable from JS as `window.go.api.Service.MethodName()` -- `sessions/` — manages concurrent protocol sessions; owns SSH/Telnet/RAW/GitHub client lifecycles; handles auto-reconnect, login scripts, broadcast -- `storage/` — SQLite repository (WAL mode) with embedded sequential migrations in `storage/migrations/` -- `secrets/` — two-tier credential storage: OS keychain (preferred) → AES-256-GCM encrypted file vault (fallback) -- `models/` — shared domain types and defaults; protocol configs are JSON-encoded in the DB -- `sshclient/` — SSH client with agent, TOTP/2FA, and SOCKS5/HTTP proxy support -- `telnetclient/`, `rawclient/`, `githubclient/` — protocol implementations -- `sftpclient/` — SFTP file operations (browse, upload, download, rename, delete) -- `sshconfig/` — OpenSSH `~/.ssh/config` parser and importer -- `recording/` — asciicast v2 session recording and playback -- `exportimport/` — encrypted profile bundles (.link format) -- `updater/` — GitHub Releases update checker -- `knownhosts/` — SSH host key management -- `validation/` — profile and protocol validation -- `audit/` — timestamped audit logging +``` +rust/ +├── core/ nexus-core: storage, secrets, models, protocol clients, session Manager, Service. No egui. +│ ├── migrations/ numbered SQL migrations, embedded with include_str! +│ └── tests/ sessions.rs, ssh_integration.rs, fixtures/ +└── app/ nexus: the egui shell (binary `nexus-terminal`) +``` -**Frontend** (`frontend/`): -- Plain HTML/CSS/JS — no framework, no bundler, no ES modules -- `index.html` is the app shell; loads CSS then JS in strict order -- `src/js/` — 18 modules, each IIFE-wrapped, exporting to `window.AlchementaryLink` -- `src/css/` — modular stylesheets; `tokens.css` defines all design tokens -- Vendor libs (xterm.js, addons) live in `src/js/lib/` and `src/css/lib/` — auto-copied, gitignored +**Core (`rust/core/src/`):** +- `service.rs` — `Service`, the app-facing API: profiles, connect/reconnect, credentials, known hosts, preferences, snippets, import/export, SSH keys, SFTP, updates. File dialogs live in the UI; methods here take paths. +- `sessions.rs` — `Manager`: concurrent sessions, auto-reconnect with backoff, login scripts, broadcast writes, recording hooks, secret resolution (saved / session-only / ask every time), host-key checks. It reports through one `Event` enum (`Data`, `State`, `Closed`, `RawData`, `Irc`, `Github*`, `Forward*`, `Recording*`, `SftpProgress`, `SecretPrompt`, `HostKeyVerify`, `Vnc*`). +- `clients/` — one module per protocol (`ssh`, `sftp`, `telnet`, `raw`, `serial`, `local`, `irc`, `github`, `vnc`). Read the conventions at the top of `clients/mod.rs` before touching one: `connect()` blocks its (worker) thread and returns once usable; the client then owns its reader thread(s) and reports through `Callbacks` (data, fatal error, close exactly once); `write`/`resize`/`close` are callable from any thread and `close` is idempotent. russh-based clients use the shared tokio runtime from `clients::rt()`; the rest use plain threads. +- `storage.rs` (SQLite via rusqlite, WAL), `secrets.rs` (keychain + vault), `config.rs` (data directories and Alchementary Link migration), `models.rs`, `validation.rs`, `exportimport.rs`, `recording.rs` (asciicast v2), `sshconfig.rs`, `updater.rs`, `ircformat.rs`, `terminalenv.rs`, `keyfiles.rs`. -**Go ↔ JS communication:** -- **Synchronous:** Frontend calls `window.go.api.Service.*()` (returns Promise) -- **Asynchronous:** Backend pushes events via `wailsRuntime.EventsEmit()`; frontend listens with `window.runtime.EventsOn()` -- **Bidirectional events** for secret prompts and host key verification: backend emits a request event with a `requestID`, frontend shows a modal, then emits a response event with the same ID. The Go side blocks on a channel keyed by that ID. +**App (`rust/app/src/`):** +- `main.rs` parses flags, opens the `Service` and starts eframe. `app.rs` owns `App`: it installs an event sink that forwards `Event`s over a channel and calls `request_repaint`, drains them each frame (`on_event`), handles `Request`s, the keyboard shortcuts (`shortcuts()`), and lays out the frame. +- `state.rs` — `Shared` (service handle, cached profiles and preferences, tabs, panes, toasts) and the `Request` queue features use to ask the main window to act. +- `views.rs` — `View::{Terminal, Raw, Irc, Github, Vnc}`, one per pane. +- `terminal.rs` (`TermView`: alacritty_terminal drawn with egui's painter), `tabs.rs` (tab strip, workspace, find bar, overlays), `splits.rs` (split tree), `sidebar.rs`, `chrome.rs` (title bar, status bar, command palette `Cmd` enum, prompts, dialogs, toasts). +- Feature windows: `profile_editor.rs`, `quick_connect.rs`, `settings.rs` (including the Help topics), `ssh_keys.rs`, `transfer.rs` (import/export), `sftp.rs`, `forwards.rs`, `snippets.rs`, `triggers.rs`, `recordings.rs`. +- Protocol views: `raw_view.rs`, `irc_view.rs`, `github_view.rs`, `vnc_view.rs`. -Key events: `session:data`, `session:state`, `session:closed`, `session:raw:data`, `session:github:repos`, `session:secret:prompt`/`session:secret:response`, `session:hostkey:verify`/`session:hostkey:response`. - -## WKWebView Constraints (Critical) - -The macOS webview silently blocks or breaks several standard web APIs. These constraints affect every frontend change: - -1. **No `window.confirm()`, `window.prompt()`, `window.alert()`** — they either return false/null silently or do nothing. All user prompts must use the custom modal system (DOM-based backdrop + modal with `is-visible` class toggling). - -2. **No ES modules** — `
` elements, which swallows the first `click` event (appears to require double-click). Fix: add `mousedown` handler with `e.preventDefault()` on any clickable non-input element (section headers, list items, etc.). - -4. **Modal visibility pattern** — both `.modal-backdrop` AND `.modal` elements start with `opacity: 0; visibility: hidden` in CSS. You must add the `is-visible` class to **both** elements, not just the backdrop. - -5. **Wails runtime injection** — `window.go` and `window.runtime` are injected by Wails before scripts execute. Do NOT add explicit script tags for Wails scripts. The frontend polls for their availability at startup (max 5s). +**Flow:** UI → `Service` / `Manager` method (plain Rust call) → protocol client on a worker thread → `Callbacks` → `Manager` emits `Event` → app channel → `App::on_event` on the next frame. Credential and host-key questions are `Event::SecretPrompt` / `Event::HostKeyVerify` carrying an `mpsc::Sender` reply; the connecting thread blocks on the receiver, and dropping the sender (closing the prompt or the session) cancels it. ## Conventions -**Language:** All code comments in British English (colour, initialise, behaviour, etc.). +- **British English** in comments, docs and UI strings (colour, initialise, behaviour, favourite). +- **Never block the UI thread.** Anything that touches the network, the keychain, `ssh-keygen` or slow disk work runs on a worker thread: `Shared::spawn` (errors become toasts), `settings::Job` (poll a result each frame), or a module's own inbox. Call `ctx.request_repaint()` when the work finishes. Quick SQLite calls through `Service` are fine inline. +- **Colours come from `Tokens`** (`theme.rs`, `Tokens::get(ctx)`): `text`, `text_dim`, `text_faint`, `accent`, `success`, `warning`, `danger`, `field`, `card`, `dock`, … Don't hard-code colours; all three app themes (Pro, Midnight, Light) must work. Protocol colours and badges come from `views::protocol_color` / `protocol_badge`; terminal palettes from `terminal_themes.rs`. +- **Icons are Phosphor** (`egui_phosphor::regular as icon`), usually as `format!("{} Label", icon::X)`. Reuse the helpers in `widgets.rs` (`primary`, `button`, `icon_button`, `hint`, `empty`, `chip`, `section`, `kv`) and `chrome::modal` for modal dialogs. +- **Shortcut labels** go through `state::keys("Mod+Shift+K")`, which renders ⌘ on macOS and Ctrl elsewhere. A new shortcut needs an entry in `App::shortcuts()`, the command palette (`chrome::commands`), the menu, the Help "Keyboard Shortcuts" topic and the README table. +- **Formatting:** `rustfmt.toml` sets `max_width = 160` with `use_small_heuristics = "Max"`, so long single-line expressions are normal. Run `cargo fmt --all` rather than wrapping by hand. +- Errors are `nexus_core::Result` (`Box`); use `?` and `format!` messages that read well in a toast. +- Protocol configs are `Option<…Config>` fields on `Profile` (`ssh`, `telnet`, `raw`, `github`, `vnc`, `irc`, `serial`), stored as JSON columns; `Profile::new(protocol)` fills the defaults, and the structs use `#[serde(default)]` so older rows still load. +- A `ponytail:` comment marks a deliberate simplification and names its limit; keep them accurate when you change that code. -**Go patterns:** -- String-typed enums: `type Protocol string` with `const ProtocolSSH Protocol = "ssh"` etc. -- Protocol configs are pointer fields on `Profile` (`SSH *SSHConfig`, `Telnet *TelnetConfig`, etc.) — nil when not applicable -- `NewProfile(protocol)` factory in `defaults.go` populates sensible defaults per protocol -- Storage uses `nullableJSON()` helper to write nil for unused config columns -- Tests use `t.TempDir()` for isolation, `t.Fatalf()` for setup failures +**Adding a protocol** touches: `models.rs` (`Protocol`, config struct, defaults, labels), `validation.rs`, a new migration (CHECK constraint and config column) plus the column lists in `storage.rs`, a client in `clients/`, the `Manager` connect/close/write paths in `sessions.rs`, the profile editor section, Quick Connect if relevant, a view (or `TermView` for terminal protocols), `views::protocol_badge`/`protocol_color`, the palette, and a Help topic. -**Frontend patterns:** -- Every JS file is IIFE-wrapped and exports to `window.AlchementaryLink` namespace -- Script load order matters (defined in `index.html`) — utils → state → notifications → sidebar → tabs → themes → terminal → profiles → raw → github → settings → commands → secrets → recordings → sftp → splitpane → snippets → app -- DOM helpers: `createElement(tag, className, attrs)` from utils.js (supports `textContent`, `innerHTML`, `dataset`, `on*` event handlers) -- Profile editor uses `_renderSection(title, fields)` pattern with `_field(type, name, label, value, opts)` helper for form generation -- Login script editor uses `_renderLoginScriptSection(prefix, steps)` for expect-style step lists -- TOTP section uses `_renderTOTPSection(profile)` with encrypted vault storage -- `protocolColour()` returns CSS class names like `proto-ssh`; tab badges use `badge-ssh` class naming (different pattern) +## Data compatibility (hard rules) -**CSS patterns:** -- Design tokens in `tokens.css`: `--bg-base`, `--bg-surface`, `--bg-elevated`, `--text-primary`, `--accent`, `--protocol-ssh`, etc. -- Dark charcoal palette — not Material Design literally, but Material-inspired layering -- Protocol colours: SSH=teal `#4ecdc4`, Telnet=amber `#ffa726`, RAW=purple `#ab47bc`, GitHub=indigo `#7c8aff` +Existing users upgrade in place, and their data came from the Go app first. Nexus Terminal must keep reading it. -**Adding a new protocol:** -1. Add `Protocol` constant + config struct in `models/models.go` -2. Add defaults in `models/defaults.go` `NewProfile()` switch -3. Create client package in `internal/client/` -4. Add `connect()` in `sessions/manager.go` + case in `Connect()` switch -5. Add API methods in `api/api.go` if needed -6. Add DB migration in `storage/migrations/` (update CHECK constraint, add config column) -7. Update `storage/store.go`: all SELECT/INSERT/UPDATE queries + both scan functions -8. Frontend: add DEFAULTS in `profiles.js`, render sections, collect form values, protocol tab -9. Frontend: add viewer in new JS file (IIFE-wrapped), include in `index.html`, wire in `app.js` -10. Add CSS: protocol colour token, sidebar indicator, tab badge, viewer styles +- **Never change the database, vault, export or recording formats incompatibly.** The vault (`vault/vault.json`: Argon2id t=3, m=128 MiB, 4 lanes; AES-256-GCM per secret), the export envelope (`[version][32-byte salt][12-byte nonce][ciphertext]`, `.nexus`; `.link` must still import) and asciicast v2 recordings are fixed. +- **Fixtures** generated by the old Go app live in `rust/core/tests/fixtures/` (`link.db`, `vault/`, `profiles.link`, `recordings/`). Tests in `storage.rs`, `secrets.rs`, `exportimport.rs` and `recording.rs` read them. Don't regenerate or edit them to make a test pass. +- **Schema changes are new SQL files** in `rust/core/migrations/` (`008_….sql`, …), added to `MIGRATIONS` in `storage.rs`. Each ends with `INSERT INTO schema_version (version, applied_at) VALUES (N, datetime('now'));`. Never edit an applied migration. SQLite has no `ALTER COLUMN`; use the recreate-table pattern. +- **Keychain** entries are stored under the service `NexusTerminal`; reads fall back to `AlchementaryLink` and copy forward. Don't change key names (`profile::`). +- **Data directory** names (`NexusTerminal`, `nexus.db`, `vault/`, `recordings/`) and the first-run migration from `AlchementaryLink/` in `config.rs` are user-facing; keep them stable. -**Database migrations:** -- Numbered SQL files: `001_initial.sql`, `002_github_protocol.sql`, etc. -- Each must `INSERT INTO schema_version (version, applied_at) VALUES (N, datetime('now'))` at the end -- SQLite has no `ALTER COLUMN` — use the recreate-table pattern (CREATE new → INSERT INTO new SELECT FROM old → DROP old → ALTER TABLE RENAME) -- Every query in `store.go` that touches the profiles table must be updated when columns change (there are ~8 SELECT column lists + 2 scan functions) +## Testing tips + +- **Never point a test or manual run at your real data.** Use `--data-dir DIR` (a temporary directory) for manual runs; tests create their own directory under `std::env::temp_dir()`, as the existing ones do. +- **App flags** (`rust/app/src/main.rs`): + - `--data-dir DIR` — use another data directory. + - `--screenshot out.png` — render, save a PNG and exit (works under `xvfb-run`); combine with `--size WxH` and `--theme pro|midnight|light`. + - `--open WINDOW` — open a window at startup: `editor[:protocol|id]`, `quick`, `settings[:terminal|security|knownhosts|help|about]`, `keys`, `snippets`, `triggers`, `recordings`, `palette`, `export`, `import`. + - `--connect NAME` — connect a saved profile by display name or id. + - `--run-local CMD` — open a local terminal and type `CMD` into it once connected. + + For example: `cargo run -- --data-dir /tmp/nx --open settings:help --screenshot /tmp/help.png`. +- **SSH integration tests** (`core/tests/ssh_integration.rs`) start a throwaway `sshd` as the current user on a random port and skip themselves when OpenSSH isn't installed. They **must never open an interactive login shell** — the user's shell profile can auto-attach tmux/byobu to a real session. Set the profile's startup command to `/bin/sh -i`, as the existing `profile()` helper does. +- `core/tests/sessions.rs` drives the `Manager` end to end against local TCP listeners; follow its `manager()` and `wait()` helpers for event-driven tests. +- UI logic that can be tested without a window (parsers, fuzzy matching, split trees, snippet variables, trigger matching) has `#[cfg(test)]` unit tests next to it; add one with any non-trivial change. diff --git a/README.md b/README.md index 72c35d8..e4ad419 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,36 @@ -# Alchementary Link +# Nexus Terminal -**A desktop client for SSH, Telnet, RAW TCP, VNC, GitHub, local terminals, and Leviathan binary analysis — with secure credential handling and a focused, multi-session workflow.** +**A native desktop client for SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local terminals — with secure credential handling and a focused, multi-session workflow.** -Alchementary Link helps you manage many connections at once, keep terminal work organised, and move files with confidence. +Nexus Terminal helps you manage many connections at once, keep terminal work organised, and move files with confidence. It is a single Rust application with no webview, so it starts quickly and stays responsive under heavy terminal output. ## Highlights -- SSH, Telnet, RAW TCP, VNC, GitHub, and local terminal sessions in one app -- Tabbed sessions with split panes, pinning, and reordering -- SFTP browser with drag-and-drop uploads and a directory picker -- Leviathan binary workbench with hex editing, entropy, carving, BinSnoop, structure parsers, transforms, and spectrogram tools -- Disconnected overlay with one-click reconnect -- Broadcast input, snippets, and terminal search -- Session recording and playback -- Secure credential storage with OS keychain and encrypted vault fallback +- SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local terminal sessions in one app +- Tabbed sessions with split panes, pinning and drag-to-reorder +- SFTP browser with drag-and-drop uploads and a permission-aware directory picker +- SSH jump hosts, SOCKS5/HTTP proxies, local and remote port forwards, agent forwarding and TOTP auto-fill +- Serial consoles with full line settings (baud, data bits, parity, stop bits, flow control) +- Native VNC viewer, optionally tunnelled over SSH +- Disconnected overlay with one-click reconnect, and auto-reconnect with backoff +- Broadcast input, snippets, trigger rules and terminal search +- Session recording (asciicast v2) and playback +- Secure credential storage with the OS keychain first and an encrypted vault fallback ## Quick start -1. Launch Alchementary Link. -2. Click **New** in the sidebar and create a connection profile. -3. Click **Connect** to open a session in a new tab. +1. Launch Nexus Terminal. +2. Click **+** at the bottom of the sidebar (or press **Cmd+N** / **Ctrl+N**) and create a connection profile. +3. Click **Create & Connect** to save it and open a session in a new tab. Afterwards, click the profile in the sidebar to connect. -Prefer a one-off session? Use **Cmd+Shift+Q** / **Ctrl+Shift+Q** to quick connect. +Prefer a one-off session? Use **Cmd+Shift+Q** / **Ctrl+Shift+Q** to quick connect, or click **Local Machine** in the sidebar for a local shell. If a session drops, use the **Reconnect** button in the disconnected overlay. +### Coming from Alchementary Link? + +On first run, Nexus Terminal copies your Alchementary Link profiles, encrypted vault and recordings into its own data directory, and picks up credentials saved in the OS keychain. The old data is left untouched. Older `.link` export files can still be imported. + ## Documentation - Product overview: [docs/overview.md](docs/overview.md) @@ -36,20 +42,31 @@ If a session drops, use the **Reconnect** button in the disconnected overlay. All shortcuts use **Cmd** on macOS and **Ctrl** on Windows/Linux. -| macOS Shortcut | Windows/Linux Shortcut | Action | -| ----------------- | ---------------------- | --------------------------- | -| Cmd + K | Ctrl + K | Open command palette | -| Cmd + N | Ctrl + N | New connection | -| Cmd + W | Ctrl + W | Close current tab | -| Cmd + Tab | Ctrl + Tab | Next tab | -| Cmd + Shift + Tab | Ctrl + Shift + Tab | Previous tab | -| Cmd + 1–9 | Ctrl + 1–9 | Switch to tab by number | -| Cmd + , | Ctrl + , | Open settings | -| Cmd + F | Ctrl + F | Find in terminal | -| Cmd + Shift + D | Ctrl + Shift + D | Split pane vertically | -| Cmd + Shift + E | Ctrl + Shift + E | Split pane horizontally | -| Cmd + Shift + R | Ctrl + Shift + R | Toggle session recording | -| Cmd + Shift + B | Ctrl + Shift + B | Toggle broadcast input mode | -| Cmd + Shift + S | Ctrl + Shift + S | Open snippet library | -| Cmd + Shift + Q | Ctrl + Shift + Q | Quick connect | -| F11 | F11 | Toggle fullscreen | +| macOS Shortcut | Windows/Linux Shortcut | Action | +| ----------------- | ---------------------- | -------------------------------- | +| Cmd + K | Ctrl + K | Open command palette | +| Cmd + N | Ctrl + N | New connection | +| Cmd + W | Ctrl + W | Close current tab | +| Cmd + Tab | Ctrl + Tab | Next tab | +| Cmd + Shift + Tab | Ctrl + Shift + Tab | Previous tab | +| Cmd + 1–9 | Ctrl + 1–9 | Switch to tab by number | +| Cmd + , | Ctrl + , | Open settings | +| Cmd + F | Ctrl + F | Find in terminal | +| Cmd + Shift + D | Ctrl + Shift + D | Split pane right (side by side) | +| Cmd + Shift + E | Ctrl + Shift + E | Split pane down (top and bottom) | +| Cmd + Shift + R | Ctrl + Shift + R | Toggle session recording | +| Cmd + Shift + B | Ctrl + Shift + B | Toggle broadcast input mode | +| Cmd + Shift + S | Ctrl + Shift + S | Open snippet library | +| Cmd + Shift + Q | Ctrl + Shift + Q | Quick connect | +| F11 | F11 | Toggle fullscreen | + +## Building from source + +Nexus Terminal is a Rust workspace in `rust/`. With [rustup](https://rustup.rs) installed: + +```bash +cd rust +cargo run --release +``` + +See [docs/build-and-release.md](docs/build-and-release.md) for platform prerequisites, tests and release packaging. diff --git a/docs/overview.md b/docs/overview.md index 51e55de..ac5c7cc 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,19 +1,21 @@ -# Alchementary Link Overview +# Nexus Terminal Overview -Alchementary Link is a desktop connection and analysis workspace for people who manage servers, network services, remote desktops, repositories, and binary artefacts every day. It brings SSH, Telnet, RAW TCP, VNC, GitHub, local shell, and Leviathan binary-analysis workflows into one focused app with secure credential handling, multi-session organisation, file transfer, automation, recording, and inspection tooling built in. +Nexus Terminal is a native desktop connection workspace for people who manage servers, network services, serial devices, remote desktops, chat networks and repositories every day. It brings SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local shell sessions into one focused app with secure credential handling, multi-session organisation, file transfer, automation and recording built in. + +It is a single Rust application drawn with egui: there is no webview, no bundled browser engine and no runtime to install. Terminal output goes straight from the network into the terminal emulator, so large outputs stay responsive. ## Product positioning -Link is more than an SSH client. It is a single command centre for remote access, terminal operations, file movement, raw socket inspection, GitHub repository visibility, binary triage, and repeatable operational workflows. +Nexus Terminal is more than an SSH client. It is a single command centre for remote access, terminal operations, file movement, raw socket inspection, serial consoles, IRC, GitHub repository visibility and repeatable operational workflows. -Use Link when you need to: +Use Nexus Terminal when you need to: - Connect quickly to many systems without re-entering credentials. -- Keep multiple live sessions organised with tabs, pinned tabs, split panes, and status indicators. +- Keep multiple live sessions organised with tabs, pinned tabs, split panes and status indicators. - Move files over SSH without leaving the terminal workspace. -- Work safely with host key verification, encrypted secrets, TOTP support, and audit logging. -- Reuse commands, automate login flows, record sessions, and react to terminal output. -- Support mixed estates that include SSH servers, Telnet devices, raw TCP services, VNC desktops, GitHub repositories, local shells, firmware images, binaries, archives, and other artefacts. +- Work safely with host key verification, encrypted secrets, TOTP support and audit logging. +- Reuse commands, automate login flows, record sessions and react to terminal output. +- Support mixed estates that include SSH servers, Telnet devices, raw TCP services, serial consoles, VNC desktops, IRC networks, GitHub repositories and local shells. ## Main feature groups @@ -21,287 +23,238 @@ Use Link when you need to: - **SSH** for secure remote terminal access. - **Telnet** for legacy devices and services. -- **RAW TCP** for direct socket interaction, protocol testing, and device troubleshooting. -- **VNC** for remote desktop access. +- **RAW TCP** for direct socket interaction, protocol testing and device troubleshooting. +- **Serial** for USB-serial adapters, console cables, development boards and network gear. +- **VNC** for remote desktop access, with a native viewer. +- **IRC** for chat networks, with rooms, users and an optional SSH tunnel. - **GitHub** for repository browsing and download workflows. - **Local terminal** for opening a shell on the local machine. -- **Leviathan** for binary inspection, editing, carving, transforms, and spectrogram workflows. -- **Quick connect** for one-off sessions without saving a profile. +- **Quick connect** for one-off SSH, Telnet, RAW, IRC or Serial sessions without saving a profile. - **Saved profiles** for repeat connections. ### Profile management -- Create, edit, duplicate, and delete connection profiles. -- Store display names, descriptions, notes, groups, tags, environment labels, and favourites. -- Search profiles by name, host, or tag. -- Filter profiles by protocol, group, tag, and favourite status. +- Create, edit, duplicate and delete connection profiles. +- Store display names, descriptions, notes, groups, tags and favourites. +- Fuzzy search across name, host, tags and protocol. +- Sidebar sections for Local Machine, Favourites, Recent (last five), groups and Ungrouped. +- Multi-select profiles with Ctrl/Cmd-click for bulk connect, disconnect, tagging, moving between groups and deleting. - Test basic host and port reachability before connecting. -- Import existing OpenSSH config entries from `~/.ssh/config`, including hostnames, usernames, ports, identity files, and jump hosts. -- Export and import profiles using encrypted `.link` bundles. -- Choose import behaviour for duplicates: skip, overwrite, or duplicate. +- Import existing OpenSSH config entries from `~/.ssh/config`, including hostnames, usernames, ports, identity files and jump hosts. +- Export and import profiles using encrypted `.nexus` bundles; older `.link` bundles from Alchementary Link import too. +- Plain JSON export and import, with secrets redacted unless you choose to include them. +- Choose import behaviour for duplicates: skip, overwrite or duplicate. ### Multi-session workspace -- Tabbed sessions with connection state indicators. -- Pin tabs to keep important systems visible. -- Rename sessions. -- Reorder tabs. -- Scroll overflowing tabs and hover them to see full names and connection start times. -- Close one session or all sessions. -- Split panes vertically or horizontally. -- Resize split panes. +- Custom title bar with File, Session, View and Help menus, a command palette launcher and a light/dark toggle. +- Tabbed sessions with protocol badges, connection state dots and activity markers. +- Pin tabs to keep important systems at the front. +- Rename, duplicate, reconnect and reorder tabs (drag, or Move Left/Right). +- Scroll overflowing tabs and hover them to see the host, state and connection start time. +- Split the active tab right or down, as many times as you like, with draggable dividers. - Switch tabs by keyboard shortcut. -- Broadcast input to multiple connected sessions. -- Show disconnected overlays with a one-click reconnect action. -- Auto-reconnect support for SSH, Telnet, and RAW TCP profiles. -- Command palette for fast access to connections, settings, terminal actions, security tools, import/export, snippets, triggers, and local terminal. +- Broadcast input to all connected terminal sessions, or to a chosen set. +- Disconnected and error overlays with one-click reconnect. +- Auto-reconnect with exponential backoff for SSH, Telnet, RAW TCP, Serial and IRC profiles. +- Guided error dialogs for authentication and host-key failures, with shortcuts to edit the profile or open Known Hosts. +- Command palette for connections, tabs, new profiles, settings, import/export, themes, the secret store, SSH keys, snippets, triggers, recordings and terminal actions. +- Status bar with session state, protocol, recording indicator, SSH latency, broadcast state, vault state, session count and update notices. ### Terminal experience -- xterm.js-powered terminal rendering. -- Configurable terminal font family, font size, scrollback, cursor style, and colour theme. -- Search within terminal output. -- Search options for case sensitivity, regular expressions, and whole-word matching. -- Copy, paste, select all, clear, and reset from the terminal context menu. -- Clipboard-aware paste behaviour. -- Clickable web links where supported. -- Alternate-screen scroll handling for tools such as `tmux`, `screen`, and `byobu`. -- Export terminal scrollback as plain text. -- Send break signals where supported. -- Ping active sessions to check responsiveness. +- Native terminal emulation (alacritty_terminal) with full colour, mouse reporting, bracketed paste and IME input. +- Eleven terminal colour schemes, applied to every open terminal at once. +- Configurable font family, font size, scrollback and cursor style, per profile or as defaults. +- Find in terminal output, with case-sensitive and regular-expression options and a match counter. +- Copy with Ctrl/Cmd+C when text is selected (otherwise it sends an interrupt), paste with Ctrl/Cmd+V. +- Double-click selects a word, triple-click selects a line, Shift-click extends a selection. +- Ctrl/Cmd-click a web link to open it in your browser. +- Shift+PageUp/PageDown/Home/End and the mouse wheel scroll the scrollback; on the alternate screen the wheel sends arrow keys, so `tmux`, `screen` and `byobu` scroll naturally. +- Context menu with Copy, Paste, Select All, Find, Send Snippet, Port Forwards, Split Right, Split Down, Export Output, Clear and Reset. +- Export the whole scrollback as plain text. +- Status-bar latency for the active SSH session. ### SSH capabilities -- Password authentication. -- Public key authentication. -- Keyboard-interactive authentication. -- SSH agent authentication. +- Password, public key, keyboard-interactive and SSH agent authentication. - SSH agent forwarding. -- Private key path discovery from `~/.ssh`. -- SSH key manager for listing, generating, copying, and deleting key pairs. -- Ed25519, RSA, and ECDSA key generation. -- Optional private key passphrase handling. -- TOTP / 2FA secret storage and code generation for keyboard-interactive login flows. -- Host key verification policies per profile: Strict, Ask, Accept new, and Insecure. +- Private key suggestions from `~/.ssh`, with a key file check. +- SSH key manager for listing, generating, copying and deleting key pairs (Ed25519, RSA 2048/4096 and ECDSA, optional passphrase). +- TOTP / 2FA secret storage and automatic code entry for keyboard-interactive logins. +- Host key verification policies per profile: Strict, Ask, Accept new and Insecure. - Known hosts storage and management. -- Jump host support for bastion and multi-hop access. -- SOCKS5 proxy support. -- HTTP CONNECT proxy support. -- Local and remote port forwarding. -- Runtime port forward management for active SSH sessions. -- Startup commands. -- Custom environment variables. -- Terminal type configuration. -- Keepalive interval and missed-keepalive settings. -- Compression option. +- Jump hosts for bastion and multi-hop access. +- SOCKS5 and HTTP CONNECT proxies. +- Local and remote port forwarding, started on connect or managed at runtime. +- Startup commands, custom environment variables and terminal type. +- Keepalive interval and missed-keepalive settings, and compression. - Expect-style login scripts with wait and send steps. - Built-in SFTP browser for file transfer. ### SFTP file browser -- Open a side panel for SSH file browsing. -- Navigate remote folders with breadcrumbs. -- Go directly to a remote path. -- Bookmark remote locations. -- Refresh remote listings. -- Create folders. -- Upload files. -- Download single files or multiple selected files. -- Rename files and folders. -- Delete files and folders. -- Multi-select support for batch actions. -- Resizable SFTP panel alongside the terminal. -- Drag and drop files into an SSH terminal. -- Choose whether dropped files are uploaded or pasted into the terminal as text. -- Upload directory picker with folder filtering, breadcrumbs, keyboard navigation, and permission badges. -- Permission labels for read-only and inaccessible folders. +- Opens in a resizable dock beside the terminal. +- Navigate remote folders with breadcrumbs, or go directly to a path. +- Bookmark remote locations (kept per profile). +- Refresh listings and create folders. +- Upload files, and download single or multiple selected files. +- Rename and delete files and folders. +- Multi-select for batch actions. +- Transfer queue with progress. +- Drag and drop files onto an SSH terminal, then upload them or paste their contents. +- Upload directory picker with folder filtering, breadcrumbs, keyboard navigation and READ ONLY / NO READ badges. ### Telnet capabilities -- Telnet option negotiation. -- Terminal type negotiation. -- NAWS window-size updates. -- Local echo option. -- Line mode option. -- Configurable line endings: LF, CR, CRLF, or none. -- Configurable encoding: UTF-8, ASCII, and ISO 8859-1. +- Telnet option negotiation, including echo, terminal type and NAWS window-size updates. +- Local echo and line mode options. +- Configurable line endings: LF, CR, CRLF or none. +- Configurable encoding: UTF-8, ASCII and ISO 8859-1. - Negotiation debug mode. - Expect-style login scripts. -- Auto-reconnect with configurable retry count. -- Break signal support. +- Auto-reconnect with a configurable retry count. ### RAW TCP capabilities - Direct TCP socket sessions. -- Text, hex, and split data views. -- Send data as text, hex, or escaped bytes. -- Quick insert controls for common bytes such as NUL, CR, LF, ETX, and EOT. +- Text, hex and split data views. +- Send data as text, hex or escaped bytes. +- Quick insert buttons for NUL, CR, LF, ETX and EOT. - Payload presets for reusable test messages. -- Optional timestamps. -- Optional send/receive direction labels. -- Pause and resume display updates. -- Clear the capture buffer. -- Save RAW session captures. +- Optional timestamps and send/receive direction labels. +- Pause and resume display updates, and clear the capture buffer. +- Save RAW session captures as JSON. - Configurable encoding and line endings. - Connect and read timeouts. - Auto-reconnect option. +### Serial capabilities + +- Pick a device from the detected port list, or type a path such as `/dev/ttyUSB0`, `/dev/cu.usbserial-…` or `COM3`. +- Baud rate from common presets or any custom value, 5–8 data bits, none/odd/even parity, 1 or 2 stop bits. +- Flow control: none, XON/XOFF (software) or RTS/CTS (hardware). +- Line ending, encoding and local echo. +- Assert DTR and RTS on connect (both on by default). +- Auto-reconnect: unplugging the adapter disconnects the session, and the port is reopened when it comes back. +- Clear hints for permission-denied and busy ports. +- Works with everything a terminal session supports: recording, triggers, broadcast, snippets, find and split panes. + ### VNC capabilities -- VNC remote desktop sessions. -- noVNC-based viewer inside the Link workspace. +- Native VNC viewer inside the Nexus Terminal workspace (RFB 3.3–3.8; Raw, CopyRect, Hextile, ZRLE and Tight encodings). - Password prompt and password storage modes. - View-only mode. -- Scale-to-fit display. -- Shared-session option. -- Quality setting. -- SSH tunnel option for VNC traffic. -- SSH tunnel authentication by password, public key, or SSH agent. +- Scale-to-fit or 1:1 display, switchable from the toolbar. +- Shared-session option and a quality setting. +- Send the local clipboard to the remote desktop, send Ctrl+Alt+Del, and request a full refresh. +- SSH tunnel option for VNC traffic, with password, public key or SSH agent authentication. +- A warning before connecting without a tunnel, because classic VNC traffic is not encrypted. + +### IRC capabilities + +- Nick, real name, TLS and an optional server password. +- NickServ identification on connect. +- Auto-join rooms. +- A Server tab plus one tab per room or private conversation, with unread markers. +- Slash commands such as `/join`, `/part`, `/msg`, `/me`, `/nick`, `/topic`, `/list`, `/names`, `/whois` and `/raw`. +- Tab nick completion, and click a nick to mention it. +- Room browser with a filter, and a users list for the current room. +- mIRC colour and formatting codes rendered in the conversation. +- Optional SSH tunnel and auto-reconnect. ### GitHub capabilities - GitHub profile type using a personal access token. - GitHub.com and GitHub Enterprise API base URL support. - Repository viewer grouped by owner or organisation. -- Repository filtering by name, description, or language. +- Repository filtering. - Repositories sorted by latest commit activity. -- Shows repository metadata such as language, stars, private/fork/archived state, latest committer, and latest commit time. -- Manual refresh. -- Configurable refresh interval. +- Repository metadata such as language, stars, private/fork/archived state, latest committer and latest commit time. +- Manual refresh and a configurable refresh interval. - Open repositories in the browser. - Download repository ZIP archives. -### Leviathan binary workbench - -- Switch from **Alchementary Link** to **Leviathan** from the sidebar header. -- Load a local file into a working document, inspect it with multiple tools, then save the patched working copy. -- Tools open as normal tabs beside connection sessions. -- Leviathan tabs close immediately without connection-disconnect confirmation. -- Shared undo, redo, and revert actions for byte-level edits. -- File-backed document model for large local files, with chunked save/export where supported. -- Clear limits for tools that require bounded in-memory buffers, such as image/audio spectrogram conversion and some parsers. - -### Leviathan editor and triage tools - -- **Hex Editor** with editable byte cells, live ASCII preview, 16-byte and 32-byte rows, byte search, string/hex search, previous/next match navigation, and range opening from analysis views. -- **Overview** for first-pass summary across hashes, entropy, strings, indicators, and carve candidates. -- **Hashes** for CRC32, MD5, SHA-1, SHA-256, and SHA-512. -- **Entropy** graph over the whole file, with selectable ranges that can open a new hex editor tab. -- **Strings** extraction for ASCII and UTF-16 text. -- **IOCs** for URLs, domains, paths, hashes, and other indicators. -- **Search** and **Multi-search** for text, hex, and regular-expression matching. -- Exportable results from tools that find files, ranges, or structured artefacts. - -### Leviathan structure, recovery, and transform tools - -- **Structures** parser for common binary formats such as PE, ELF, Mach-O, ZIP, images, RIFF, ASN.1, and certificates. -- **Disasm** heuristic disassembly for common CPU families, with raw byte fallback for unsupported architectures. -- **PDF** object, stream, xref, and suspicious-surface inspection. -- **SquashFS** superblock and filesystem metadata inspection with human-readable bad-magic errors. -- **Carve**, **Keys**, **Timestamps**, **Stego**, and **Metadata** tools for embedded artefacts and recovery workflows. -- **BinSnoop** deep signature scanning across the whole file, with export for discovered files and ranges. -- **Rules** for YARA-like rule scans. -- **Patch Plan** and **Patch Apply** for validating and applying patch scripts. -- **Transforms** for hex, Base64, URL, XOR, inflate, and cipher pipelines. -- **Case Export** for bundling useful artefacts from a working investigation. - -### Leviathan spectrogram tools - -- Convert an image into WAV audio so the image appears in a spectrogram. -- Insert an encoded image into an existing WAV or MP3 carrier. -- Visualise WAV or MP3 audio as a spectrogram image. -- Control FFT size, height, maximum width, aspect ratio, frequency range, brightness, and contrast. - ### Automation and reusable workflows -- Snippet library for saved commands. -- Snippet folders, descriptions, and tags. -- Send snippets to active sessions. -- Snippet variables before sending commands. +- Snippet library for saved commands, with names, descriptions and folders. +- Send snippets to the active session. +- Built-in snippet variables (`{{hostname}}`, `{{date}}`, `{{time}}`, `{{timestamp}}`) and prompted custom variables. - Broadcast input mode for running the same command across connected sessions. -- Trigger rules that watch terminal output. -- Trigger matching by text or regular expression. -- Trigger actions for notifications, tab highlighting, sound, or combined notify/highlight behaviour. +- Trigger rules that watch terminal output, matching plain text or regular expressions. +- Trigger actions for notifications, tab highlighting, both, or a sound (attention request). - Expect-style login scripts for SSH and Telnet. - Startup commands for SSH sessions. ### Session recording - Start and stop terminal recording per session. -- Recording indicator on active tabs. -- Recordings saved in asciicast v2-compatible format. -- Recording browser for listing saved recordings. -- Playback controls. +- Recording indicator on the tab and in the status bar. +- Recordings saved in asciicast v2 format. +- Recording browser with storage usage and clean-up by age or total size. +- Playback with play/pause, restart, a seek bar and speed control. - Delete saved recordings. -- Recording metadata includes title, dimensions, duration, size, creation time, and file path. ### Security and credential handling -- OS keychain first where available, including macOS Keychain, Linux Secret Service, and Windows Credential Manager. -- Encrypted vault fallback. -- Vault encryption with AES-256-GCM and Argon2id-derived keys. -- Secret store lock and unlock workflow. -- Per-profile secret modes: Saved, Session only, and Ask every time. +- OS keychain first where available: macOS Keychain, Linux Secret Service and Windows Credential Manager. +- Encrypted vault fallback, using AES-256-GCM with an Argon2id-derived key. +- Secret store lock and unlock workflow, with a master-password strength meter. +- Per-credential secret modes: Save securely, Remember for this session, and Ask every time. - Encrypted storage for TOTP secrets. - Known host verification and known host management. -- Audit log for security-relevant operations. -- Audit entries for connections, disconnections, profile changes, secret access, imports, and exports. -- Secrets are not written into audit entries. +- Audit log of connections, disconnections, profile changes, credential access, imports and exports. Secrets are never written to it. - Encrypted profile exports with password-based AES-256-GCM encryption and Argon2id key derivation. -- Plain profile export can redact inline secrets. ### Settings and maintenance -- Application theme setting. -- Terminal theme selection. +- App theme (Pro, Midnight or Light) and terminal colour scheme. - Default protocol preference. -- Default terminal font, size, scrollback, and cursor settings. +- Default terminal font, size, scrollback and cursor settings. - Confirm-on-close and confirm-on-disconnect preferences. -- Status bar visibility setting. -- Sidebar width preference. -- Native paste preference. +- Status bar visibility and sidebar width. - Local shell selection. - Secret store settings. - Known hosts management. -- Help and troubleshooting content inside settings. -- Background and manual update checks. -- Update checks report availability, current version, latest version, release notes, release URL, and publish date. +- Help topics and troubleshooting inside Settings. +- A background update check at launch, and a manual check in Settings → About. ## Website-ready summary ### Suggested headline -Alchementary Link: one desktop workspace for SSH, files, sockets, remote desktops, repositories, and binary artefacts. +Nexus Terminal: one native desktop workspace for SSH, files, sockets, serial consoles, remote desktops, chat and repositories. ### Suggested subheadline -Manage SSH, Telnet, RAW TCP, VNC, GitHub, local shell, and Leviathan binary-analysis sessions from a fast tabbed interface with secure credentials, SFTP file transfer, split panes, snippets, recordings, automation, and artefact-inspection tools built in. +Manage SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local shell sessions from a fast tabbed interface with secure credentials, SFTP file transfer, split panes, snippets, recordings and automation built in. ### Short product description -Alchementary Link helps developers, operators, support engineers, reverse engineers, firmware analysts, and homelab users manage many connections and artefacts without juggling separate tools. Save profiles, organise them with groups and tags, connect in tabs or split panes, move files over SFTP, inspect raw TCP traffic, open VNC desktops, browse GitHub repositories, triage binaries with Leviathan, and keep credentials protected with keychain and encrypted-vault storage. +Nexus Terminal helps developers, operators, support engineers, network and hardware engineers, and homelab users manage many connections without juggling separate tools. Save profiles, organise them with groups and tags, connect in tabs or split panes, move files over SFTP, inspect raw TCP traffic, talk to serial devices, open VNC desktops, join IRC networks, browse GitHub repositories, and keep credentials protected with keychain and encrypted-vault storage. ### Product page feature bullets -- All-in-one connection and analysis workspace for SSH, Telnet, RAW TCP, VNC, GitHub, local shells, and Leviathan tools. -- Fast multi-session interface with tabs, pinned sessions, split panes, reconnect, and broadcast input. -- Rich SSH support including agent auth, TOTP, jump hosts, proxies, port forwarding, startup commands, login scripts, and host key policies. -- Built-in SFTP browser with upload, download, rename, delete, folders, bookmarks, drag-and-drop upload, and permission-aware directory picking. -- RAW TCP tooling with text, hex, split views, payload presets, captures, timestamps, and byte-level sending. -- VNC viewer with scale-to-fit, view-only mode, quality controls, shared sessions, and optional SSH tunnelling. -- GitHub repository browser with organisation grouping, filtering, activity sorting, metadata, ZIP downloads, and GitHub Enterprise support. -- Leviathan binary workbench with hex editing, hashes, entropy, strings, IOCs, carving, BinSnoop, structures, PDF/SquashFS inspection, transforms, rules, patching, case export, and spectrogram image/audio tools. -- Secure credential model using OS keychain first, encrypted vault fallback, per-profile secret modes, known hosts, encrypted exports, and audit logging. -- Productivity tools including command palette, snippets, trigger rules, session recording, terminal search, scrollback export, and local terminal. +- All-in-one connection workspace for SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local shells. +- Native and fast: no webview, with a terminal that stays responsive under heavy output. +- Multi-session interface with tabs, pinned sessions, split panes, reconnect and broadcast input. +- Rich SSH support including agent auth and forwarding, TOTP, jump hosts, proxies, port forwarding, startup commands, login scripts and host key policies. +- Built-in SFTP browser with upload, download, rename, delete, folders, bookmarks, drag-and-drop upload and permission-aware directory picking. +- RAW TCP tooling with text, hex and split views, payload presets, captures, timestamps and byte-level sending. +- Serial consoles with full line settings, flow control, DTR/RTS control and auto-reconnect on replug. +- Native VNC viewer with scale-to-fit, view-only mode, quality controls, shared sessions and optional SSH tunnelling. +- IRC client with rooms, nick completion, NickServ and mIRC formatting. +- GitHub repository browser with organisation grouping, filtering, activity sorting, ZIP downloads and GitHub Enterprise support. +- Secure credential model using the OS keychain first, encrypted vault fallback, per-credential secret modes, known hosts, encrypted exports and audit logging. +- Productivity tools including command palette, snippets, trigger rules, session recording, terminal search, scrollback export and local terminal. ## Who it is for -- Developers who connect to many application servers, staging environments, and production hosts. +- Developers who connect to many application servers, staging environments and production hosts. - Platform and DevOps teams who need organised, secure access across infrastructure. -- Support teams who troubleshoot customer systems, network appliances, and legacy Telnet devices. -- Network and hardware engineers who need RAW TCP and Telnet tools beside normal SSH workflows. -- Homelab users who want SSH, VNC, file transfer, and repo access in one desktop app. -- Reverse engineers, firmware analysts, and security researchers who want binary triage beside their live operational sessions. +- Support teams who troubleshoot customer systems, network appliances and legacy Telnet devices. +- Network and hardware engineers who need RAW TCP, Telnet and serial tools beside normal SSH workflows. +- Homelab users who want SSH, VNC, file transfer, serial consoles and repo access in one desktop app. ## Learn more diff --git a/docs/quick-start.md b/docs/quick-start.md index 0a366ea..e1a62a4 100644 --- a/docs/quick-start.md +++ b/docs/quick-start.md @@ -1,42 +1,46 @@ -# Alchementary Link Quick Start +# Nexus Terminal Quick Start -Alchementary Link is a desktop client for SSH, Telnet, RAW TCP, VNC, GitHub, local terminal sessions, and Leviathan binary analysis. This page is a shareable quick start for everyday use. +Nexus Terminal is a native desktop client for SSH, Telnet, RAW TCP, Serial, VNC, IRC, GitHub and local terminal sessions. This page is a shareable quick start for everyday use. ## Get connected in minutes -1. Open Alchementary Link. -2. Click **New** in the sidebar. -3. Choose a protocol and enter the connection details. -4. Click **Connect** to open the session in a new tab. +1. Open Nexus Terminal. +2. Click **+** at the bottom of the sidebar, or press **Cmd+N** (macOS) / **Ctrl+N** (Windows/Linux). +3. Choose a protocol at the top of the editor and enter the connection details. +4. Click **Create & Connect** to save the profile and open the session in a new tab. -One-off session? Use **Cmd+Shift+Q** / **Ctrl+Shift+Q** for quick connect. +Next time, just click the profile in the sidebar. + +One-off session? Use **Cmd+Shift+Q** / **Ctrl+Shift+Q** for quick connect. Need a local shell? Click **Local Machine** at the top of the sidebar. ## Daily workflow -- **Tabs and splits**: Drag tabs to reorder, and split the workspace to view multiple sessions at once. -- **Scrollable tabs**: Use the tab bar scrollbar or mouse wheel/trackpad over the tab strip when many tabs are open. Hover a tab to see its full name and connection start time. -- **Broadcast input**: Toggle with **Cmd+Shift+B** (macOS) or **Ctrl+Shift+B** (Windows/Linux) to send the same command to all connected sessions. -- **Snippets**: Save common commands and run them with one click. -- **Search**: Press **Cmd+F** (macOS) or **Ctrl+F** (Windows/Linux) to search within the active terminal. +- **Command palette**: Press **Cmd+K** / **Ctrl+K** to connect, switch tabs or run any command by typing a few letters. +- **Tabs and splits**: Drag tabs to reorder them, right-click a tab to pin, rename, duplicate or reconnect it, and split the workspace with **Cmd+Shift+D** / **Ctrl+Shift+D** (right) or **Cmd+Shift+E** / **Ctrl+Shift+E** (down). +- **Scrollable tabs**: Use the mouse wheel or trackpad over the tab strip when many tabs are open. Hover a tab to see its host, state and connection start time. +- **Broadcast input**: Toggle with **Cmd+Shift+B** / **Ctrl+Shift+B** to send the same keystrokes to every connected terminal. Right-click the broadcast button on the tab strip to pick targets. +- **Snippets**: Save common commands with **Cmd+Shift+S** / **Ctrl+Shift+S** and send them with one click. +- **Search**: Press **Cmd+F** / **Ctrl+F** to search within the active terminal. +- **Copy, paste and links**: Select text and press **Cmd+C** / **Ctrl+C** to copy (with nothing selected, Ctrl+C interrupts as usual). **Cmd+V** / **Ctrl+V** pastes. **Cmd-click** / **Ctrl-click** a URL to open it. - **Reconnect quickly**: If a session drops, click **Reconnect** on the disconnected overlay. +## Serial consoles + +Create a **Serial** profile, pick the device from the list (click refresh after plugging in an adapter) and set the baud rate and line settings to match the device — 115200 8N1 with no flow control is the default. On Linux your user needs to be in the `dialout` (or `uucp`) group; on macOS use the `/dev/cu.*` device. + ## VNC remote desktops -Create a **VNC** profile for remote desktop sessions. Use SSH tunnelling when possible, especially outside trusted networks. Direct VNC is available for isolated or trusted networks, but classic VNC traffic may not be encrypted. - -## Leviathan binary tools - -Click **Leviathan** beside **Alchementary Link** in the sidebar header to switch from connection profiles to binary-analysis tools. Use **Load** to choose a file, then open tools such as Hex Editor, Overview, Hashes, Entropy, Strings, Carve, BinSnoop, Structures, PDF, SquashFS, Transforms, and Spectrogram tools. Tools open in normal tabs and can be closed without a disconnect confirmation. +Create a **VNC** profile for remote desktop sessions. Use the SSH tunnel option when possible, especially outside trusted networks. Direct VNC is available for isolated or trusted networks, but classic VNC traffic is not encrypted, so Nexus Terminal asks before connecting without a tunnel. ## File transfers on SSH -Click the **folder** icon on an SSH tab to open the SFTP browser. You can browse, upload, download, rename, and delete files. +Click the **folder** icon on an SSH tab to open the SFTP browser. You can browse, upload, download, rename and delete files, create folders and bookmark locations. ### Drag and drop files -Drop file(s) into the terminal and choose to: +Drop file(s) onto a terminal and choose to: -- **Upload** to a selected remote folder, or +- **Upload to current directory** — pick a remote folder (SSH sessions only), or - **Paste contents** into the terminal as if typed. The upload picker lists folders only and marks permissions: @@ -47,9 +51,14 @@ The upload picker lists folders only and marks permissions: ## Security basics - Credentials are stored in the OS keychain where available. -- An encrypted vault is used as a fallback. +- An encrypted vault (AES-256-GCM, Argon2id) is used as a fallback; set it up in **Settings → Security**. +- Each password, passphrase or token can be saved, remembered for the session only, or asked for every time. - SSH host key checks can be set per profile (Strict, Ask, Accept new, Insecure). +## Coming from Alchementary Link + +Your profiles, vault and recordings are copied across automatically on first run, and keychain entries are picked up the first time they are needed. Your old Alchementary Link data is left untouched. + ## Common shortcuts | macOS Shortcut | Windows/Linux Shortcut | Action | @@ -62,8 +71,8 @@ The upload picker lists folders only and marks permissions: | Cmd + Shift + B | Ctrl + Shift + B | Broadcast input | | Cmd + Shift + Q | Ctrl + Shift + Q | Quick connect | | Cmd + Shift + R | Ctrl + Shift + R | Toggle session recording | -| Cmd + Shift + D | Ctrl + Shift + D | Split pane vertically | -| Cmd + Shift + E | Ctrl + Shift + E | Split pane horizontally | +| Cmd + Shift + D | Ctrl + Shift + D | Split pane right | +| Cmd + Shift + E | Ctrl + Shift + E | Split pane down | ## Need more detail? diff --git a/docs/user-manual.md b/docs/user-manual.md index 628fd19..1b8bad8 100644 --- a/docs/user-manual.md +++ b/docs/user-manual.md @@ -1,4 +1,4 @@ -# Alchementary Link User Manual +# Nexus Terminal User Manual ## Contents @@ -8,14 +8,16 @@ 4. Connection types 5. Tabs and split panes 6. Terminal actions and search -7. Snippets and broadcast input +7. Snippets, broadcast input and triggers 8. SFTP file browser 9. Drag-and-drop uploads and paste -10. Session recordings -11. Leviathan binary workbench +10. Port forwards +11. Session recordings 12. Security and credentials -13. Settings -14. Troubleshooting +13. Import and export +14. Settings and updates +15. Data locations and migration from Alchementary Link +16. Troubleshooting --- @@ -23,26 +25,35 @@ ### Launch and connect -- Start Alchementary Link. -- Click **New** in the sidebar, choose a protocol, and fill in the profile details. -- Click **Connect** to open a session in a new tab. +- Start Nexus Terminal. +- Click **+** at the bottom of the sidebar (or press **Cmd+N** / **Ctrl+N**), choose a protocol, and fill in the profile details. +- Click **Create & Connect** to save the profile and open a session in a new tab, or **Create** to save it for later. +- Click a profile in the sidebar to connect to it. ### Quick connect -Use **Cmd+Shift+Q** (macOS) or **Ctrl+Shift+Q** (Windows/Linux) to open a lightweight connect dialog. This starts a one-off session without saving a profile. +Use **Cmd+Shift+Q** (macOS) or **Ctrl+Shift+Q** (Windows/Linux) to open a lightweight connect dialog. Choose SSH, Telnet, RAW, IRC or Serial, enter the host (or serial device), port, and username or nick where relevant, then press **Enter**. This starts a one-off session without saving a profile. + +### Local terminal + +Click **Local Machine** at the top of the sidebar, choose **File → Local terminal**, or click **Local** on the welcome screen to open a shell on this computer. When the shell exits, the tab shows `[Exited - press enter to restart]`; press **Enter** to start a new shell in the same tab. + +### Command palette + +Press **Cmd+K** / **Ctrl+K**, or click the search box in the title bar, to open the command palette. Type a few letters to find a connection, switch to a tab, create a profile, open a window, change theme, lock or unlock the secret store, or run a terminal action. Use **Up/Down** to move, **Enter** to run, and **Esc** to close. --- ## 2. The interface at a glance -- **Sidebar**: Profiles grouped by folders or tags, with search at the top. -- **Tab bar**: One tab per session with status indicators, badges, and quick actions. -- **Workspace**: The active terminal or viewer; split panes appear here. -- **Status bar**: Connection state and session details. +- **Title bar**: the File, Session, View and Help menus, the active session's name and host, the command palette launcher, a light/dark toggle, and the window controls. Drag the title bar to move the window; double-click it to maximise. +- **Sidebar**: a search box, then **Local Machine**, **★ Favourites**, **Recent** (the last five connections), one section per group, and **Ungrouped** (or **All Connections** when you have no groups). Sections collapse when you click their headers. The footer has buttons for New connection, Command palette, Import, Export, Recordings and Settings. Drag the sidebar edge to resize it. +- **Tab strip**: one tab per session, with a state dot, protocol badge, pin marker, activity dot and close button. SSH tabs also have a folder icon for SFTP. On the right are **+** (new connection) and the broadcast button. +- **Workspace**: the active terminal or viewer; split panes appear here. With no tabs open it shows a welcome screen with **New Connection**, **Quick Connect** and **Local** buttons. +- **Right dock**: the profile editor or the SFTP browser, resizable. +- **Status bar**: the active session's state and protocol, a **REC** marker while recording, latency for SSH sessions, a **BROADCAST** marker, the vault state (click it to open Security settings), the number of connected sessions, and an update notice when a new version is available. -Screenshot: Main window with sidebar, tab bar, and active terminal. - -Tip: You can drag the divider between split panes to rebalance space. +Notifications appear as toasts in the top-right corner and dismiss themselves after a few seconds; click **×** to dismiss one early. --- @@ -50,22 +61,39 @@ Tip: You can drag the divider between split panes to rebalance space. ### Create a new profile -1. Click **New** in the sidebar. -2. Choose the protocol (SSH, Telnet, RAW, VNC, or GitHub). -3. Fill in the required fields and optional settings. -4. Save and connect. +1. Click **+** in the sidebar footer or on the tab strip, press **Cmd+N** / **Ctrl+N**, or right-click a group header and choose **New connection in …**. +2. Choose the protocol (SSH, Telnet, RAW TCP, GitHub, VNC, IRC or Serial) at the top of the editor. Switching protocol keeps the common fields. +3. Fill in the **General** section: Display Name, Host (or Device for serial), Port, Username, Group and Description. +4. Fill in the protocol sections and, for terminal protocols, the **Terminal** section (font family, font size 8–36, scrollback 0–100000 lines, cursor style). +5. Optionally add **Tags** (comma-separated) and **Notes**. +6. Click **Create & Connect**, or **Create** to save without connecting. -Screenshot: New profile form with protocol selector and connection fields. +Click **Test** to check that the host and port are reachable; a toast reports the round-trip time. Fields with problems are highlighted when you save. -### Edit, duplicate, or delete +The **Group** field suggests existing groups; type a new name to create one. -Right-click a profile in the sidebar to edit, duplicate, or delete it. You can also click the pencil icon on hover, or double-click the item to edit. +### Edit, duplicate, favourite, export or delete -### Folders, tags, and favourites +Right-click a profile in the sidebar for **Connect**, **Edit**, **Duplicate**, **Add to Favourites** / **Remove from Favourites**, **Export…** and **Delete**. Double-click a profile to edit it. Deleting a profile also removes its saved credentials. -Use folders and tags to organise profiles. Mark profiles as favourites for quick access. +### Bulk actions -Tip: Use the search box to filter profiles by name, host, or tag. +Ctrl-click (Cmd-click on macOS) profiles to select several. A bar appears at the bottom of the sidebar with: + +- **Connect all** +- **Disconnect** (asks first when **Confirm on disconnect** is on) +- **Tags** → **Add tags…** or **Replace tags…** +- **Move group** (leave the name empty to move to Ungrouped) +- **Delete** +- **Clear** (clear the selection) + +### Search + +Type in the sidebar search box to fuzzy-search profiles by name, host, tag and protocol. + +### Already open? + +If you connect to a profile that already has a live session, Nexus Terminal asks whether to **Use Existing**, open a **New Connection**, or **Cancel**. --- @@ -73,105 +101,190 @@ Tip: Use the search box to filter profiles by name, host, or tag. ### SSH -- Password, public key, keyboard-interactive, SSH agent, and TOTP/2FA. -- Jump hosts, port forwarding, proxy support, and auto-reconnect. -- Built-in SFTP browser and session recording. +- **Authentication**: password, public key, keyboard-interactive and SSH agent. Choose which methods to try under **Auth Methods**, and tick **Use SSH Agent for Authentication** to use `ssh-agent` (Pageant or the OpenSSH agent on Windows). +- **Key & Passphrase**: choose a key from the suggestions (keys in `~/.ssh`), click **Browse**, or type a full path (`~` is not expanded). The editor checks that the file exists and is a private key. +- **Host Verification**: the host key policy (see section 12). +- **Jump Hosts**: one or more bastions, each with user, host, port and an optional key. Nexus Terminal tunnels through each hop in order. +- **Port Forwarding**: local and remote forwards, each with a bind address and port, destination, label and an **Active** box to start it on connect. +- **Startup**: a startup command and environment variables (`KEY=VALUE`, one per line). +- **Login Script**: expect-style steps (see below). +- **SSH Agent**: agent authentication and **Forward Agent to Remote** (like `ssh -A`). +- **TOTP / 2FA**: store a Base32 TOTP secret to answer two-factor prompts automatically. Click **Test** to show the current code, **Save** to store the secret, and **Delete** to remove it. Save the profile first. +- **Proxy**: none, SOCKS5 or HTTP CONNECT, with host, port and optional username and password. The proxy is used to reach the SSH server, or the first jump host. +- **Advanced**: terminal type, keepalive interval, max missed keepalives, compression, auto-reconnect and the maximum number of reconnect attempts (1–20). + +The SFTP browser, port forwards and status-bar latency are available on SSH sessions. ### Telnet -- Option negotiation, encoding, local echo, and line endings. -- Auto-login scripts and auto-reconnect. +- **Login**: local echo and line mode. +- **Terminal & Encoding**: terminal type and encoding (UTF-8, ASCII or ISO 8859-1). +- **Echo & Line Endings**: LF, CR, CRLF or none. +- **Negotiation**: **Debug Negotiation** for troubleshooting option negotiation. +- **Login Script** and **Reconnection** settings. + +Telnet sends everything in plain text. Avoid it for anything sensitive over an untrusted network. ### RAW TCP -- Text, hex, and split views for binary-safe data. -- Payload presets and configurable encoding and line endings. +- **Encoding & Framing**: encoding (UTF-8, ASCII or Hex) and line ending. +- **Display**: show timestamps and show direction. +- **Payload Presets**: named payloads with a format (Text, Hex or Escaped) for quick sending. +- **Timeouts & Reconnect**: connect and read timeouts, and auto-reconnect. + +A RAW session opens a viewer instead of a terminal: + +- **Text**, **Hex** and **Split** views of everything sent and received. +- **Pause** / **Resume**, **Clear**, and **Save** (writes the capture as JSON). +- A send bar with a format selector (Text, Hex or Escaped), a **Presets…** menu, and quick buttons for EOT, ETX, LF, CR and NUL. Press **Enter** to send. + +### Serial + +Serial profiles open a terminal on a local serial port. + +- **Device**: pick the port from the list and click refresh after plugging in an adapter, or type a path such as `/dev/ttyUSB0`, `/dev/cu.usbserial-…` or `COM3`. +- **Line Settings**: baud rate (a preset from 300 to 1000000, or a custom value), data bits (5–8), parity (none, odd or even), stop bits (1 or 2) and flow control (none, XON/XOFF or RTS/CTS). +- **Behaviour**: line ending, encoding, local echo (for devices that don't echo), **Assert DTR on connect**, **Assert RTS on connect**, and auto-reconnect. + +The defaults are 115200 baud, 8 data bits, no parity, 1 stop bit, no flow control, CR line endings, and DTR and RTS asserted. + +When the adapter is unplugged the session disconnects; with auto-reconnect on, the port is reopened when the device returns. Serial sessions support everything other terminals do: recording, triggers, broadcast, snippets, find and split panes. ### VNC -- Remote desktop viewer embedded in the Link workspace. -- Password prompt and saved-password modes. -- View-only mode, scale-to-fit, shared sessions, and quality controls. -- Optional SSH tunnel for safer VNC transport. +- **VNC Server**: VNC host (defaults to the profile's Host), port, and password storage mode. +- **Display**: **View Only**, **Scale to Fit**, **Shared Session** and **Quality (0-9)**. +- **SSH Tunnel**: carry VNC traffic through SSH, with host, port, username and password, public key or SSH agent authentication. -Use SSH tunnelling for VNC whenever possible. Direct VNC is useful on trusted or isolated networks, but many classic VNC deployments do not provide strong transport encryption. +The viewer's toolbar shows the connection state and desktop size, and has buttons to switch between scaled and 1:1 display, refresh the screen, **Paste Clipboard** (send your local clipboard to the remote desktop) and **Ctrl+Alt+Del**. Click the desktop to give it keyboard focus. + +Use SSH tunnelling for VNC whenever possible. If a VNC profile has no tunnel, Nexus Terminal warns that the traffic is unencrypted and offers **Connect Anyway**, **Edit Profile** or **Cancel**. + +### IRC + +- **IRC**: nick, real name, **Use TLS** (port 6697 for most TLS networks) and an optional server password. +- **NickServ**: identify on connect, with an optional account name and password. +- **Rooms**: rooms to join automatically, one per line or comma-separated. +- **SSH Tunnel** and **Reconnection** settings. + +The IRC view has a **Server** tab and a tab per room or conversation, with a dot on tabs that have unread messages. Type messages or `/commands` in the composer (`/join`, `/part`, `/msg`, `/me`, `/notice`, `/nick`, `/topic`, `/list`, `/names`, `/whois`, `/who`, `/raw`, `/quit`, `/help`). Press **Tab** to complete a nick, or click a nick to mention it. The side panel lists joined rooms, a room browser with a filter (click a room to join it), and the users in the current room. ### GitHub -- Browse repositories by organisation. -- Sorted by most recent activity. +- **API**: API base URL (`https://api.github.com`, or your GitHub Enterprise API URL) and personal access token storage. The token needs the `repo` scope. +- **Refresh**: refresh interval, 60–3600 seconds. + +Repositories are grouped by owner or organisation and sorted by most recent activity. Each card shows Private, Fork and Archived badges, language, stars, description, and the last commit and committer. Use the filter box to narrow the list, **Refresh now** to update it, **Visit** to open a repository in your browser, and **ZIP** to download an archive. --- ## 5. Tabs and split panes -- **Reorder** tabs by dragging them. -- **Pin** important sessions to keep them at the front. -- **Scroll** the tab bar with the scrollbar or mouse wheel/trackpad when many tabs are open. -- **Hover** a tab to see the full name and, for connected sessions, the connection start time. -- **Close** tabs with the close button on the left side of each tab. Leviathan tool tabs close without a disconnect confirmation. -- **Split** the workspace vertically or horizontally to view multiple sessions at once. -- **Broadcast** input to multiple connected sessions when needed. +- **Reorder** tabs by dragging them, or right-click and choose **Move Left** / **Move Right**. +- **Pin** important sessions from the tab's right-click menu; pinned tabs stay at the front. +- **Rename**, **Duplicate** (open another session to the same profile) and **Reconnect** from the right-click menu. +- **Scroll** the tab strip with the mouse wheel or trackpad when many tabs are open. +- **Hover** a tab to see its name, host, state and, for connected sessions, the connection start time. +- **Close** a tab with its **×**, **Cmd+W** / **Ctrl+W**, or **Close** in the right-click menu. When **Confirm on close** is on, closing a connected tab asks first (GitHub tabs close straight away). +- **Switch** tabs with **Cmd+Tab** / **Ctrl+Tab**, **Cmd+Shift+Tab** / **Ctrl+Shift+Tab**, or **Cmd+1–9** / **Ctrl+1–9**. +- A dot on a background tab means it has new output. -Screenshot: Tab bar with pinned sessions and split panes. +### Split panes -Tip: Pinned tabs stay at the front so key systems never get buried. +- **Cmd+Shift+D** / **Ctrl+Shift+D** splits the focused pane to the right (side by side). +- **Cmd+Shift+E** / **Ctrl+Shift+E** splits it downwards (top and bottom). +- Split Right and Split Down are also in the Session menu, the terminal's right-click menu and the command palette. + +A new pane offers **Local Machine** and your terminal profiles (SSH, Telnet and Serial); click one to open it in that pane. Drag a divider to resize panes. Click **×** on a pane's header to close it; its neighbour takes the space. Closing the tab closes every pane in it. --- ## 6. Terminal actions and search +### Keyboard and mouse + +- **Copy**: select text and press **Cmd+C** / **Ctrl+C**. With nothing selected, **Ctrl+C** sends an interrupt as usual. +- **Paste**: **Cmd+V** / **Ctrl+V**. Pastes are bracketed when the remote application asks for it. +- **Select**: drag to select, double-click for a word, triple-click for a line, Shift-click to extend. +- **Links**: hold **Cmd** / **Ctrl** and click a web link to open it in your browser. +- **Scroll**: the mouse wheel, or **Shift+PageUp**, **Shift+PageDown**, **Shift+Home** and **Shift+End**. On the alternate screen (for example in `tmux`, `screen`, `byobu` or `less`) the wheel sends arrow keys instead. +- Applications that use the mouse (such as `htop`, `vim` or `tmux` with mouse mode) receive mouse clicks and the wheel directly. + ### Context menu Right-click in the terminal for: -- Copy and paste -- Select all +- Copy and Paste +- Select All - Find -- Send snippet -- Clear and reset - -Screenshot: Terminal context menu and search bar. +- Send Snippet +- Port Forwards (SSH) +- Split Right and Split Down +- Export Output (save the whole scrollback as a text file) +- Clear and Reset ### Search -Press **Cmd+F** (macOS) or **Ctrl+F** (Windows/Linux) to search in the active terminal. Toggle case sensitivity and regular expressions as needed. +Press **Cmd+F** (macOS) or **Ctrl+F** (Windows/Linux) to search in the active terminal. Toggle **Aa** for case-sensitive matching and **.\*** for regular expressions. The bar shows "*n* of *total*"; press **Enter** for the next match, **Shift+Enter** for the previous one, and **Esc** to close. + +### Disconnects and errors + +When a session drops, the pane is dimmed and shows a **Disconnected** (or **Connection error**) card with **Reconnect** and **Close**. While auto-reconnect is running it shows **Reconnecting…** with a **Cancel** button, and a toast shows the attempt number and delay. + +If a connection fails because of authentication or a host key, a guided dialog explains what happened and offers **Retry**, **Edit Profile**, **Known Hosts** (for host-key errors) and **Close**. --- -## 7. Snippets and broadcast input +## 7. Snippets, broadcast input and triggers ### Snippets -Open the snippet library with **Cmd+Shift+S** (macOS) or **Ctrl+Shift+S** (Windows/Linux) and save frequently-used commands. Send a snippet to the active session with one click. +Open the snippet library with **Cmd+Shift+S** (macOS) or **Ctrl+Shift+S** (Windows/Linux), from the Session menu, or with **Send Snippet** in the terminal's right-click menu. Create snippets with a name, description, command and folder, search them, and click **Send to Session** to type the command into the active session followed by Enter. -Screenshot: Snippet library with search and folder grouping. +Snippets can use variables: + +- `{{hostname}}`, `{{date}}`, `{{time}}` and `{{timestamp}}` are filled in automatically. +- Any other `{{name}}` is asked for when you send the snippet. ### Broadcast input -Toggle broadcast mode with **Cmd+Shift+B** (macOS) or **Ctrl+Shift+B** (Windows/Linux). While active, all input is sent to every connected session. +Toggle broadcast mode with **Cmd+Shift+B** (macOS) or **Ctrl+Shift+B** (Windows/Linux), the broadcast button on the tab strip, or the Session menu. While it is on, everything you type is also sent to every other connected terminal session, and the status bar shows **BROADCAST**. + +To broadcast to only some sessions, right-click the broadcast button (or choose **Session → Broadcast targets…**), tick the sessions you want, and click **Apply & Enable**. + +### Trigger rules + +Open **Trigger Rules** from the Session menu or the command palette. Each rule has a name, a pattern (plain text or regular expression), a case-sensitive option, an enabled switch and an action: + +- **Notify**: show a toast. +- **Highlight tab**: briefly outline the pane. +- **Notify + Highlight**: both. +- **Sound**: ask the operating system for attention (for example, a bouncing dock icon or flashing taskbar button) and show a toast. + +Rules are checked against each complete line of terminal output, with colour codes removed. --- ## 8. SFTP file browser -Open the file browser with the **folder** icon on an SSH tab. You can: +Open the file browser with the **folder** icon on an SSH tab, or **Session → SFTP browser**. It opens in the right dock beside the terminal. You can: -- Navigate folders using the breadcrumb trail. -- Upload, download, rename, and delete files. -- Refresh and create folders. +- Navigate folders using the breadcrumb trail, the `..` entry, or **Go to path**. +- Upload files, and download one or more selected files to a folder you choose. +- Rename a single item, and delete one or more items (with confirmation). +- Create folders and refresh the listing. +- Bookmark the current folder with the star button; bookmarks are kept per profile. +- Watch transfers and their progress in the transfer list. -The browser runs inside the SSH session and shows remote contents in a side panel. - -Screenshot: SFTP browser open alongside the terminal. +Ctrl-click (Cmd-click) or Shift-click to select several items. Drag the dock edge to resize it. --- ## 9. Drag-and-drop uploads and paste -When you drop file(s) into an active terminal, Alchementary Link asks whether to: +When you drop file(s) onto an active terminal, Nexus Terminal asks whether to: -- **Upload** to a chosen remote directory, or +- **Upload to current directory** — choose a remote directory (SSH sessions only), or - **Paste contents** into the terminal as if typed. ### Upload directory picker @@ -181,163 +294,202 @@ The picker looks like the SFTP browser and lists remote folders only. It shows: - **READ ONLY** for folders you cannot write to. - **NO READ** for folders you cannot access. -Use the filter box to narrow the list, and the breadcrumb trail or **Up** button to move around. You can also use the keyboard: +Use the filter box to narrow the list, and the breadcrumb trail or **Up** button to move around. **Use this folder** is disabled when the folder is not writable. You can also use the keyboard: - **Up/Down**: move selection - **Enter**: choose the selected folder +- **Backspace**: go up a folder (when the filter is empty) - **Escape**: cancel If the SFTP browser is already open, the picker starts in the same directory. -Screenshot: Upload directory picker with permissions badges. - Tip: Use the filter box to jump quickly to common folders. --- -## 10. Session recordings +## 10. Port forwards -Toggle recording with **Cmd+Shift+R** (macOS) or **Ctrl+Shift+R** (Windows/Linux). Recordings are saved as asciicast v2 files and can be replayed from the recordings panel. +Forwards saved in an SSH profile start when the session connects. To manage forwards on a live SSH session, choose **Port Forwards** from the terminal's right-click menu or **Session → Port forwards**. The dialog lists active forwards (marked **L** for local or **R** for remote) with a stop button, and lets you start a new one: -Screenshot: Recordings list with playback controls. +- **Local →**: listen on a local bind address and port, and forward to a destination reachable from the server. +- **Remote ←**: ask the server to listen, and forward connections back to a destination reachable from this computer. + +Toasts report when a forward starts, stops or fails. --- -## 11. Leviathan binary workbench +## 11. Session recordings -Leviathan is the integrated binary-analysis and editing workspace inside Alchementary Link. It is useful for firmware images, unknown binaries, archives, captures, embedded files, suspicious documents, and audio spectrogram experiments. +Toggle recording with **Cmd+Shift+R** (macOS) or **Ctrl+Shift+R** (Windows/Linux), or **Session → Toggle recording**. A red dot appears on the tab and **REC** in the status bar. Recordings are saved as asciicast v2 files. -### Switching to Leviathan +Open the recordings browser from the sidebar footer, the Session menu or the command palette. It shows how many recordings you have and how much space they use, lets you play or delete each one, and has **Clean up** for removing recordings older than 7, 30 or 90 days, or keeping the total under 1 GB or 5 GB. -Click **Leviathan** in the top-left sidebar header. The normal connection list changes into a tool list. Click **Alchementary Link** to return to saved servers and groups. - -Use **Load** to select a local file. The selected file becomes the current Leviathan working document. Use **Save** to write the patched working copy to a chosen destination. - -Leviathan tools open as regular tabs. You can open multiple tool tabs at once, switch between them, and close them without a connection-disconnect confirmation. - -### Hex Editor - -The Hex Editor is the primary byte-level view. - -- Choose 16 or 32 bytes per row. -- Edit byte cells directly; typing two hex digits advances to the next byte. -- The ASCII preview updates as bytes are edited. -- Paste hex runs such as `de ad be ef` or `deadbeef` into consecutive cells. -- Use Search to find strings or hex values, then move through matches with previous/next. -- Undo, redo, or revert edits from the Leviathan panel header. - -### Triage tools - -- **Overview**: first-pass summary across hashes, entropy, strings, indicators, and carve candidates. -- **Hashes**: CRC32, MD5, SHA-1, SHA-256, and SHA-512. -- **Entropy**: graph entropy across the whole file; select a region to open that range in a new Hex Editor tab. -- **Strings**: ASCII and UTF-16 string extraction. -- **IOCs**: URLs, domains, paths, hashes, and other indicators. -- **Search** and **Multi-search**: text, hex, and regular-expression matching. - -### Structure and recovery tools - -- **Structures** parses common formats such as PE, ELF, Mach-O, ZIP, images, RIFF, ASN.1, and certificates. -- **Disasm** provides heuristic disassembly for common CPU families and raw byte fallback where decoding is not implemented. -- **PDF** inspects objects, streams, xrefs, and suspicious surfaces. -- **SquashFS** inspects filesystem superblocks and metadata; non-SquashFS input shows a human-readable bad-magic error. -- **Carve** and **BinSnoop** scan for embedded files and signatures and can export discovered files or ranges. -- **Keys**, **Timestamps**, **Stego**, and **Metadata** help recover common artefacts from binaries and containers. - -### Editing, transforms, and cases - -- **Patch Plan** validates patch scripts without modifying the working copy. -- **Patch Apply** applies validated patch scripts. -- **Transforms** runs pipelines such as hex, Base64, URL, XOR, inflate, and cipher transforms over selected data. -- **Rules** runs YARA-like scans over the current document. -- **Case Export** packages investigation artefacts from the working document. - -### Spectrogram tools - -- **Spectrogram Encode** converts an image into WAV audio so the image appears in a spectrogram. -- It can also insert an encoded image into an existing WAV or MP3 carrier. -- **Spectrogram View** renders WAV or MP3 audio as a spectrogram image. -- Controls include FFT size, height, maximum width, aspect ratio, frequency range, brightness, and contrast. - -### Large file behaviour - -Leviathan keeps loaded files file-backed where possible and writes patched working copies in chunks. Some tools still need bounded in-memory buffers because the underlying parser or decoder works on complete byte slices; those tools reject oversized inputs with a clear message rather than trying to allocate unbounded memory. +Playback starts automatically and has play/pause, restart, a seek bar with elapsed and total time, and speed control. --- ## 12. Security and credentials -Alchementary Link stores credentials securely: +### Where credentials are stored -- **OS keychain first** (Keychain, Secret Service, or Credential Manager). -- **Encrypted vault** fallback using AES-256-GCM with Argon2id. +- **OS keychain first** (macOS Keychain, Linux Secret Service or Windows Credential Manager). +- **Encrypted vault** fallback, using AES-256-GCM with an Argon2id-derived key (128 MiB of memory, which makes brute-force attacks expensive). + +If the keychain is unavailable, or refuses to store a secret, credentials go to the vault. Set the vault up in **Settings → Security** with **Initialise Secret Store**; the master-password dialog shows a strength meter (Weak, Fair, Good, Strong) and asks you to confirm the password. Unlock and lock it from the same page, the command palette, or by clicking the vault state in the status bar. The vault is locked again when Nexus Terminal quits. + +Without the master password, credentials in the vault cannot be recovered. + +### Secret modes + +Each password, passphrase, token or TOTP secret has its own mode: + +- **Save securely**: stored in the keychain or vault and used automatically. +- **Remember for this session**: asked for once, kept in memory for that session, never written to disk. +- **Ask every time**: asked for on every connection. + +When a session needs a credential it doesn't have, a prompt appears; press **Esc** or **Cancel** to abandon the connection. ### Host key verification Choose one of the following policies per SSH profile: -- **Strict**: only accept previously saved fingerprints. -- **Ask**: prompt before trusting a new host key. -- **Accept new**: trust first-time keys, reject changes. -- **Insecure**: accept any key (not recommended). +- **Strict (known hosts only)**: only accept previously saved host keys. +- **Ask on new or changed keys**: prompt before trusting a new host key; the dialog shows the key type and fingerprint with **Accept & Save** and **Reject**. +- **Accept new, reject changed**: trust first-time keys automatically. +- **Insecure (accept anything)**: accept any key (not recommended). + +Under every policy except Insecure, a key that differs from the saved one is rejected. Nexus Terminal keeps its own known-hosts list and does not read `~/.ssh/known_hosts`. Review and remove entries in **Settings → Known Hosts**; click a fingerprint to copy it. ### TOTP / 2FA -Store a TOTP secret in the profile editor to auto-fill verification codes during SSH authentication. +Store a TOTP secret in the profile editor to answer verification-code prompts automatically during SSH keyboard-interactive authentication. Secrets are stored in the keychain or vault, never in the profile database. -Screenshot: TOTP configuration in the profile editor. +### SSH key manager + +Open **SSH key manager** from the Session menu or the command palette. It lists key pairs in `~/.ssh` with their type, lets you copy a public key to the clipboard or delete a pair, and generates new Ed25519, RSA (2048 or 4096 bits) or ECDSA keys with an optional passphrase. Key generation uses the `ssh-keygen` program. + +### Audit log + +Connections, disconnections, profile changes, credential access, imports and exports are recorded in an audit log in the database. Secrets are never written to it. --- -## 13. Settings +## 13. Import and export -Open settings with **Cmd+,** (macOS) or **Ctrl+,** (Windows/Linux) and configure: - -- Terminal themes and fonts -- Default behaviours -- Secrets and vault settings -- Recording and shortcut preferences - -Screenshot: Settings panel with appearance and security options. +- **Encrypted export**: **File → Export profiles…**, the sidebar footer, the command palette, or **Export…** on a profile's right-click menu. Choose the profiles, set a password and save a `.nexus` file. Credentials saved in the keychain or vault are not included. +- **Encrypted import**: **File → Import profiles…** or the sidebar footer. Choose a `.nexus` file (or a `.link` file from Alchementary Link), enter its password, and choose what happens when a profile already exists: **Skip existing**, **Overwrite existing** or **Create duplicates**. +- **JSON export and import**: **Export Profiles (JSON)** and **Import Profiles (JSON)** in the command palette. JSON export includes every profile; tick **Include secrets** to keep inline proxy passwords and TOTP secrets, which are otherwise removed. +- **OpenSSH config**: **File → Import ~/.ssh/config** or **Settings → About → Import ~/.ssh/config** creates profiles from your `Host` blocks, including hostnames, usernames, ports, identity files and jump hosts. --- -## 14. Troubleshooting +## 14. Settings and updates + +Open settings with **Cmd+,** (macOS) or **Ctrl+,** (Windows/Linux), the sidebar footer, or **File → Settings**. Click **Save** to apply changes. + +- **General**: app theme (Pro, Midnight or Light), default protocol, confirm on close, confirm on disconnect, status bar, sidebar width, and local shell (leave empty for the system default; **Browse…** to pick one). +- **Terminal**: colour scheme, font family, font size, scrollback lines and cursor style. These are the defaults for new profiles; profiles can override them. +- **Security**: keychain and vault status; initialise, unlock or lock the secret store. +- **Known Hosts**: saved host keys with first-seen and last-seen times, and **Remove**. +- **Help**: built-in help topics, including connection types, shortcuts and troubleshooting. +- **About**: version, **Check for Updates**, and **Import ~/.ssh/config**. + +The app theme and terminal colour scheme can also be changed from the **View** menu, and the light/dark button in the title bar. + +### Updates + +Nexus Terminal checks for a new version once at launch. When one is available, the status bar shows **Update available**; click it to open **Settings → About**, which shows the current and latest versions, release notes and a **View release** button. Nothing is downloaded or installed automatically. + +--- + +## 15. Data locations and migration from Alchementary Link + +### Data locations + +| OS | Data directory | +| ------- | -------------------------------------------------- | +| macOS | `~/Library/Application Support/NexusTerminal` | +| Windows | `%APPDATA%\NexusTerminal` | +| Linux | `$XDG_CONFIG_HOME/NexusTerminal` (usually `~/.config/NexusTerminal`) | + +The directory holds: + +- `nexus.db` — profiles, known hosts, preferences, snippets, IRC channel state and the audit log (SQLite) +- `vault/vault.json` — the encrypted vault +- `recordings/` — session recordings +- `triggers.json` — trigger rules +- `sftp-bookmarks.json` — SFTP bookmarks + +Credentials saved in the OS keychain are stored under the service name `NexusTerminal`. + +### Migrating from Alchementary Link + +On first run, if Nexus Terminal has no database yet and an Alchementary Link data directory (`AlchementaryLink`, in the same parent folder) exists, Nexus Terminal **copies** `link.db` to `nexus.db`, along with the `vault/` and `recordings/` directories, and shows a toast saying where the data came from. The Alchementary Link directory is left untouched. + +- If a `NexusTerminal/nexus.db` already exists (for example from a version before the Alchementary Link rebrand), it is used as-is and nothing is copied. +- Keychain entries saved under `AlchementaryLink` are found the first time they are needed and copied to `NexusTerminal`. The old entries are not deleted. +- Your vault master password is unchanged. +- Trigger rules and SFTP bookmarks from Alchementary Link are not migrated; recreate them in Nexus Terminal. + +--- + +## 16. Troubleshooting ### Session shows as disconnected -- The terminal is blurred and shows a **DISCONNECTED** overlay when the connection drops. +- The pane is dimmed and shows a **Disconnected** overlay when the connection drops. - Click **Reconnect** to reopen the session with the same profile. +### Authentication fails + +- Check the username, password, key path and the auth methods ticked in the profile. +- If the key has a passphrase, check the passphrase mode. +- If the server uses two-factor prompts, check the TOTP secret with **Test** and make sure your system clock is accurate. + +### "Host key verification failed" + +The server's key differs from the one saved earlier. This can happen when a server is reinstalled, but it can also indicate an attack. If you're certain it's safe, open **Settings → Known Hosts**, remove the entry, and reconnect. + ### Upload fails or is greyed out - Check the folder is writable; **READ ONLY** indicates no write permission. -- Confirm the SSH session is connected. - -### VNC will not connect - -- Confirm the VNC host and port are correct. The common VNC port is `5900` plus the display number. -- If using a password, check whether the profile is set to saved, session-only, or ask-every-time mode. -- If using an SSH tunnel, verify the tunnel host, username, authentication method, and SSH host key policy. -- If direct VNC is enabled, test from a trusted network first and prefer an SSH tunnel for untrusted networks. - -### Leviathan cannot load or save a file - -- Check that the file still exists and that your user account can read it. -- When saving, choose a writable destination and avoid overwriting a file that another process has locked. -- Some tools enforce explicit size limits for safety. If a tool rejects an oversized input, try selecting a smaller range or using a tool that works on the file-backed document. +- Confirm the session is connected over SSH; upload isn't available for other protocols. ### Cannot read a folder - **NO READ** indicates insufficient permissions. - Try a parent folder or check remote access controls. +### Serial port won't open + +- Check the device path and that the adapter is plugged in; refresh the device list in the editor. +- **Permission denied** on Linux: add your user to the `dialout` group (`uucp` on some distributions), then log out and back in. +- On macOS, use the `/dev/cu.*` device rather than `/dev/tty.*`. +- **Port is in use by another program**: close any other program that has the port open, such as `screen`, `minicom` or an IDE serial monitor. +- Garbled output usually means the baud rate, parity or stop bits don't match the device. + +### VNC will not connect + +- Confirm the VNC host and port are correct. The common VNC port is `5900` plus the display number. +- If using a password, check whether it is set to save securely, remember for this session, or ask every time. +- If using an SSH tunnel, verify the tunnel host, username, authentication method, private key and SSH host key policy. +- If direct VNC is enabled, test from a trusted network first and prefer an SSH tunnel for untrusted networks. + +### The secret store is locked + +If a saved credential isn't used, the vault may be locked. Click the vault state in the status bar, or open **Settings → Security**, and unlock it. + ### Session keeps reconnecting - Check network stability and credentials. -- Review auto-reconnect settings in the profile. +- Review the auto-reconnect settings in the profile. Reconnection backs off exponentially (1 s, 2 s, 4 s … up to 30 s) and stops after the maximum number of attempts. ### Terminal size looks wrong +- Resize the window or drag a split divider; the terminal re-fits automatically. - Try disconnecting and reconnecting the session. -- Avoid heavy UI resizing during initial connection. +- Some older servers don't support modern terminal features; try a different terminal type in the profile. + +### Trying things safely + +To try something without touching your real data, start Nexus Terminal with `--data-dir DIR` to use a different data directory. From 7b92b81ecd4d4db7252257f45c3e88bcb7303853 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 17:54:11 +0100 Subject: [PATCH 37/45] Serial/Telnet Send Break and DTR/RTS tab controls; fix Help and hints; drop unused log path; docs --- docs/user-manual.md | 4 +++- rust/app/src/app.rs | 26 +++++++++++++++++++++++++- rust/app/src/chrome.rs | 2 +- rust/app/src/main.rs | 4 +++- rust/app/src/recordings.rs | 2 +- rust/app/src/settings.rs | 4 ++-- rust/app/src/state.rs | 2 ++ rust/app/src/tabs.rs | 23 ++++++++++++++++++++++- rust/core/src/config.rs | 2 -- 9 files changed, 59 insertions(+), 10 deletions(-) diff --git a/docs/user-manual.md b/docs/user-manual.md index 1b8bad8..618cf58 100644 --- a/docs/user-manual.md +++ b/docs/user-manual.md @@ -148,6 +148,8 @@ Serial profiles open a terminal on a local serial port. The defaults are 115200 baud, 8 data bits, no parity, 1 stop bit, no flow control, CR line endings, and DTR and RTS asserted. +While connected, right-click the tab to **Send Break** or toggle **DTR** and **RTS**. Telnet tabs also offer **Send Break**. + When the adapter is unplugged the session disconnects; with auto-reconnect on, the port is reopened when the device returns. Serial sessions support everything other terminals do: recording, triggers, broadcast, snippets, find and split panes. ### VNC @@ -156,7 +158,7 @@ When the adapter is unplugged the session disconnects; with auto-reconnect on, t - **Display**: **View Only**, **Scale to Fit**, **Shared Session** and **Quality (0-9)**. - **SSH Tunnel**: carry VNC traffic through SSH, with host, port, username and password, public key or SSH agent authentication. -The viewer's toolbar shows the connection state and desktop size, and has buttons to switch between scaled and 1:1 display, refresh the screen, **Paste Clipboard** (send your local clipboard to the remote desktop) and **Ctrl+Alt+Del**. Click the desktop to give it keyboard focus. +The viewer's toolbar shows the connection state and desktop size, and has buttons to switch between scaled and 1:1 display, refresh the screen, **Paste Clipboard** (send your local clipboard to the remote desktop) and **Ctrl+Alt+Del**. Text the remote desktop copies arrives on your local clipboard. Click the desktop to give it keyboard focus. Use SSH tunnelling for VNC whenever possible. If a VNC profile has no tunnel, Nexus Terminal warns that the traffic is unencrypted and offers **Connect Anyway**, **Edit Profile** or **Cancel**. diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs index 95ee1b9..902a389 100644 --- a/rust/app/src/app.rs +++ b/rust/app/src/app.rs @@ -244,7 +244,8 @@ impl App { }; let profile = self.s.svc.sessions.profile(id); let view = self.view_for(&info, profile.as_ref()); - self.s.panes.insert(id.into(), Pane { info, view, recording: false, local_exited: false, connected_since: None, highlight_until: 0.0 }); + let lines = profile.as_ref().and_then(|p| p.serial.as_ref()).map_or((true, true), |c| (c.dtr_on_connect, c.rts_on_connect)); + self.s.panes.insert(id.into(), Pane { info, view, recording: false, local_exited: false, connected_since: None, highlight_until: 0.0, lines }); if tab { self.s.tabs.push(Tab { id: id.into(), activity: false, split: None }); self.s.active = Some(id.into()); @@ -691,6 +692,29 @@ impl App { } } + /// Telnet or serial break. + pub fn send_break(&mut self, id: &str) { + match self.s.svc.sessions.send_break(id) { + Ok(()) => self.s.info("Break sent"), + Err(e) => self.s.error(e.to_string()), + } + } + + /// Toggles a serial modem line: `dtr` true for DTR, false for RTS. + pub fn toggle_line(&mut self, id: &str, dtr: bool) { + let Some((d, r)) = self.s.pane(id).map(|p| p.lines) else { return }; + let (d, r) = if dtr { (!d, r) } else { (d, !r) }; + match self.s.svc.sessions.set_serial_lines(id, d, r) { + Ok(()) => { + if let Some(p) = self.s.pane_mut(id) { + p.lines = (d, r); + } + self.s.info(format!("DTR {} · RTS {}", if d { "on" } else { "off" }, if r { "on" } else { "off" })); + } + Err(e) => self.s.error(e.to_string()), + } + } + pub fn toggle_broadcast(&mut self) { self.s.broadcast = !self.s.broadcast; if !self.s.broadcast { diff --git a/rust/app/src/chrome.rs b/rust/app/src/chrome.rs index 8de6f81..15033f1 100644 --- a/rust/app/src/chrome.rs +++ b/rust/app/src/chrome.rs @@ -77,7 +77,7 @@ pub fn title_bar(app: &mut App, ui: &mut Ui) { app.s.request(Request::ExportEncrypted(vec![])); } if item(ui, icon::FILE_ARROW_DOWN, "Import ~/.ssh/config", "") { - match app.s.svc.import_ssh_config(None, "Imported") { + match app.s.svc.import_ssh_config(None, "SSH Config") { Ok(n) => { app.s.success(format!("Imported {n} host(s) from ~/.ssh/config")); app.s.reload_profiles(); diff --git a/rust/app/src/main.rs b/rust/app/src/main.rs index 0062485..5c8cd87 100644 --- a/rust/app/src/main.rs +++ b/rust/app/src/main.rs @@ -56,7 +56,9 @@ fn main() -> eframe::Result { return Ok(()); } _ => { - println!("usage: nexus-terminal [--theme pro|midnight|light] [--data-dir DIR] [--screenshot out.png] [--size WxH]"); + println!( + "usage: nexus-terminal [--theme pro|midnight|light] [--data-dir DIR] [--connect NAME] [--open WINDOW] [--run-local CMD] [--screenshot out.png] [--size WxH] [--version]" + ); return Ok(()); } } diff --git a/rust/app/src/recordings.rs b/rust/app/src/recordings.rs index d053333..b0d8e74 100644 --- a/rust/app/src/recordings.rs +++ b/rust/app/src/recordings.rs @@ -223,7 +223,7 @@ impl Recordings { ui.separator(); if self.list.is_empty() { if !self.loading { - widgets::empty(ui, icon::RECORD, "No recordings yet", "Use the record button on a terminal tab to capture a session."); + widgets::empty(ui, icon::RECORD, "No recordings yet", "Press Ctrl/Cmd+Shift+R in a terminal, or use Session → Toggle recording."); } return; } diff --git a/rust/app/src/settings.rs b/rust/app/src/settings.rs index e65bf00..ceba6ef 100644 --- a/rust/app/src/settings.rs +++ b/rust/app/src/settings.rs @@ -692,7 +692,7 @@ If your server requires a time-based one-time password (TOTP) during login, stor ## Jump Hosts (Bastions) If you need to reach a server through an intermediate host, add one or more jump hosts in the connection editor. Nexus Terminal tunnels through each hop automatically. ## Port Forwarding -You can set up **local**, **remote**, and **dynamic** (SOCKS proxy) port forwards in the connection editor. These start automatically when the session connects. +You can set up **local** and **remote** port forwards in the connection editor. These start automatically when the session connects. ## Proxy Support Open the **Proxy** section in the profile editor to connect through a **SOCKS5** or **HTTP CONNECT** proxy, with optional username/password authentication. The proxy is used to reach the SSH server (or the first jump host if jump hosts are configured). ## SFTP File Browser @@ -738,7 +738,7 @@ Enable **Auto-Reconnect** in the Advanced section to reconnect automatically whe - **Device** — pick the port from the list (refresh after plugging in an adapter), e.g. `/dev/ttyUSB0`, `/dev/cu.usbserial-…` or `COM3`. - **Line settings** — baud rate (default 115200), data bits, parity, stop bits and flow control (none, XON/XOFF, RTS/CTS). - **Behaviour** — line ending, local echo, encoding, and whether DTR/RTS are raised on connect. -- **Tab controls** — send a break and toggle DTR/RTS from the tab. +- **Tab controls** — right-click the tab to send a break or toggle DTR/RTS. - **Auto-reconnect** — unplugging the adapter disconnects the session; it reopens the port when it comes back. On Linux your user usually needs to be in the `dialout` (or `uucp`) group. On macOS use the `/dev/cu.*` device. A \"busy\" error means another program has the port open.", diff --git a/rust/app/src/state.rs b/rust/app/src/state.rs index b218276..ca153e7 100644 --- a/rust/app/src/state.rs +++ b/rust/app/src/state.rs @@ -67,6 +67,8 @@ pub struct Pane { pub connected_since: Option>, /// Bumped to make trigger highlights flash. pub highlight_until: f64, + /// Serial DTR / RTS as last set (starts from the profile's on-connect settings). + pub lines: (bool, bool), } /// A tab. Its session is `id`; `split` lays out extra panes (which have no tab of their own). diff --git a/rust/app/src/tabs.rs b/rust/app/src/tabs.rs index 2269960..6bfea48 100644 --- a/rust/app/src/tabs.rs +++ b/rust/app/src/tabs.rs @@ -95,7 +95,7 @@ fn strip(app: &mut App, ui: &mut Ui) { for id in &order { let Some(p) = app.s.pane(id) else { continue }; let p_info = p.info.clone(); - let (p_recording, p_since) = (p.recording, p.connected_since); + let (p_recording, p_since, lines) = (p.recording, p.connected_since, p.lines); let p = PaneInfo { info: p_info, recording: p_recording, connected_since: p_since }; let tab_activity = app.s.tab(id).is_some_and(|tb| tb.activity); let active = app.s.active.as_deref() == Some(id.as_str()); @@ -203,6 +203,24 @@ fn strip(app: &mut App, ui: &mut Ui) { ui.close(); } } + if matches!(p.info.protocol, Protocol::Telnet | Protocol::Serial) { + ui.separator(); + if ui.button(format!("{} Send Break", icon::LIGHTNING)).clicked() { + menu_action = Some((id.clone(), "break")); + ui.close(); + } + } + if p.info.protocol == Protocol::Serial { + let (d, r) = lines; + if ui.button(format!("{} DTR", if d { icon::CHECK_SQUARE } else { icon::SQUARE })).clicked() { + menu_action = Some((id.clone(), "dtr")); + ui.close(); + } + if ui.button(format!("{} RTS", if r { icon::CHECK_SQUARE } else { icon::SQUARE })).clicked() { + menu_action = Some((id.clone(), "rts")); + ui.close(); + } + } }); } if !ui.input(|i| i.pointer.primary_down()) { @@ -284,6 +302,9 @@ fn tab_action(app: &mut App, id: &str, a: &str) { } } "reconnect" => app.reconnect(id), + "break" => app.send_break(id), + "dtr" => app.toggle_line(id, true), + "rts" => app.toggle_line(id, false), "close" => app.request_close(id), _ => {} } diff --git a/rust/core/src/config.rs b/rust/core/src/config.rs index 59b3525..af90e2b 100644 --- a/rust/core/src/config.rs +++ b/rust/core/src/config.rs @@ -11,7 +11,6 @@ pub struct AppConfig { pub db_path: PathBuf, pub vault_dir: PathBuf, pub recordings_dir: PathBuf, - pub log_path: PathBuf, /// Set when this run copied data across from Alchementary Link. pub migrated_from: Option, } @@ -35,7 +34,6 @@ impl AppConfig { db_path: data_dir.join("nexus.db"), vault_dir: data_dir.join("vault"), recordings_dir: data_dir.join("recordings"), - log_path: data_dir.join("nexus.log"), data_dir, migrated_from: None, } From 84eff02c157d2476ba6a4969c17d78fe5b49556c Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 19:06:48 +0100 Subject: [PATCH 38/45] Move update and release URLs to dixon.cx/downloads/nexus --- docs/rust-port-plan.md | 6 ++---- rust/core/src/updater.rs | 2 +- rust/release.sh | 4 ++-- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/docs/rust-port-plan.md b/docs/rust-port-plan.md index d9c96e9..6beaa8d 100644 --- a/docs/rust-port-plan.md +++ b/docs/rust-port-plan.md @@ -13,9 +13,7 @@ > - **Not verified:** macOS builds (need Apple's C toolchain), running on Windows or macOS, the OS > keychain on a real desktop session (none was available, so the go-keyring compatibility in §3 > is untested), IRC, GitHub and VNC against real servers (tested against fakes), serial hardware. -> - **Still open:** the update/release URLs still point at alchementary.co.uk (§7) — to move to -> dixon.cx, change `DEFAULT_UPDATE_URL`/`DEFAULT_RELEASE_BASE_URL` in `rust/release.sh` and -> `DEFAULT_UPDATE_URL` in `rust/core/src/updater.rs`. +> - **Update URLs:** moved to `https://dixon.cx/downloads/nexus` (§7). Goal: rebuild Alchementary Link in Rust as **Nexus Terminal**, a single Rust application in the style of Leviathan Desktop (`../leviathan/rust`). @@ -274,7 +272,7 @@ undoes it and migrates the data. | Keychain service | `AlchementaryLink` | `NexusTerminal` | | Export file | `.link`, "Link Export" | `.nexus`, "Nexus Export" (import accepts both) | | Updater User-Agent and GitHub fallback | `AlchementaryLink-UpdateChecker`, `alchementary-link` | `NexusTerminal-UpdateChecker`, `nexus-terminal` | -| Update and release URLs | `alchementary.co.uk/download/link/…` | ⚠ **Decision needed:** the new URL for the manifest and downloads | +| Update and release URLs | `alchementary.co.uk/download/link/…` | `dixon.cx/downloads/nexus/…` | | Icons and brand mark | `alchementary-link-*.png`, ">_ AlchementaryLink" | Restore the `nexus-terminal-*` icons from git history (pre-`1a380a0`), redrawn for the egui title bar | **First-run migration** (in `nexus-core`, before the database is opened): diff --git a/rust/core/src/updater.rs b/rust/core/src/updater.rs index 99e3d7f..f671c87 100644 --- a/rust/core/src/updater.rs +++ b/rust/core/src/updater.rs @@ -8,7 +8,7 @@ use std::io::Read; use std::time::Duration; /// Manifest URL template; expand with [`expand_update_url`]. -pub const DEFAULT_UPDATE_URL: &str = "https://alchementary.co.uk/download/link/manifest-{platform}-{arch}.json"; +pub const DEFAULT_UPDATE_URL: &str = "https://dixon.cx/downloads/nexus/manifest-{platform}-{arch}.json"; const GITHUB_OWNER: &str = "nexus-terminal"; const GITHUB_REPO: &str = "nexus-terminal"; const USER_AGENT: &str = "NexusTerminal-UpdateChecker"; diff --git a/rust/release.sh b/rust/release.sh index 03dd4f9..956fb52 100755 --- a/rust/release.sh +++ b/rust/release.sh @@ -15,8 +15,8 @@ HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$HERE/.." && pwd)" cd "$HERE" -DEFAULT_UPDATE_URL="https://alchementary.co.uk/download/link/manifest-{platform}-{arch}.json" -DEFAULT_RELEASE_BASE_URL="https://alchementary.co.uk/download/link" +DEFAULT_UPDATE_URL="https://dixon.cx/downloads/nexus/manifest-{platform}-{arch}.json" +DEFAULT_RELEASE_BASE_URL="https://dixon.cx/downloads/nexus" log() { echo -e "\033[0;32m[nexus]\033[0m $1"; } warn() { echo -e "\033[1;33m[nexus]\033[0m $1"; } From ce9c5c6b44c006f842a41c32ee8e641efdf2b408 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 19:41:30 +0100 Subject: [PATCH 39/45] Screenshot automation (--connect repeatable, --do, --answer, --shot-delay), SFTP Browser palette entry, centred dialogs, IRC opens the first joined room; demo_data example --- rust/app/src/app.rs | 43 ++++++-- rust/app/src/chrome.rs | 16 +++ rust/app/src/irc_view.rs | 5 + rust/app/src/main.rs | 22 +++- rust/app/src/recordings.rs | 4 + rust/app/src/snippets.rs | 2 + rust/app/src/triggers.rs | 2 + rust/core/examples/demo_data.rs | 181 ++++++++++++++++++++++++++++++++ 8 files changed, 262 insertions(+), 13 deletions(-) create mode 100644 rust/core/examples/demo_data.rs diff --git a/rust/app/src/app.rs b/rust/app/src/app.rs index 902a389..a5fea03 100644 --- a/rust/app/src/app.rs +++ b/rust/app/src/app.rs @@ -23,8 +23,14 @@ pub struct Options { /// Open a window at startup: editor, quick, settings[:tab], keys, snippets, triggers, /// recordings, palette, export, import (screenshot checks). pub open: Option, - /// Connect a saved profile (by name or id) at startup. - pub connect: Option, + /// Connect saved profiles (by name or id) at startup. + pub connect: Vec, + /// Command-palette actions (by label) to run once sessions have had time to connect. + pub actions: Vec, + /// Replies to the first credential prompts, in order (screenshots of token-protected sessions). + pub answers: Vec, + /// How long to wait before taking the screenshot. + pub shot_delay: std::time::Duration, } /// Errors from worker threads, drained into toasts each frame. @@ -108,8 +114,10 @@ pub struct App { pub tabs_ui: tabs::TabsUi, pub latency: Option<(String, Result)>, pub update: Option, - screenshot: Option<(PathBuf, u32)>, + screenshot: Option<(PathBuf, std::time::Instant, std::time::Duration)>, shot_settle: u32, + actions: Vec, + answers: Vec, run_local: Option<(String, Option)>, /// Apply `prefs.sidebar_width` to the panel on the next frame (after Settings changed it). resize_sidebar: bool, @@ -155,13 +163,15 @@ impl App { tabs_ui: Default::default(), latency: None, update: None, - screenshot: opts.screenshot.map(|p| (p, 0)), + screenshot: opts.screenshot.map(|p| (p, std::time::Instant::now(), opts.shot_delay)), shot_settle: 0, + actions: opts.actions, + answers: opts.answers, run_local: opts.run_local.map(|c| (c, None)), resize_sidebar: false, }; - if let Some(c) = opts.connect.as_deref() { - match app.s.profiles.iter().find(|p| p.id == c || p.display_name == c).map(|p| p.id.clone()) { + for c in &opts.connect { + match app.s.profiles.iter().find(|p| &p.id == c || &p.display_name == c).map(|p| p.id.clone()) { Some(id) => app.connect_profile(&id, true), None => eprintln!("--connect: no profile named {c:?}"), } @@ -479,6 +489,9 @@ impl App { self.s.success(format!("Recording saved ({:.0}s, {events} events)", duration)); } Event::SftpProgress { session, progress } => self.sftp.on_progress(&session, progress), + Event::SecretPrompt { session, title, message, reply } if !self.answers.is_empty() => { + let _ = (session, title, message, reply.send(self.answers.remove(0))); + } Event::SecretPrompt { session, title, message, reply } => { self.prompts.push_back(Prompt::Secret { session, title, message, value: String::new(), reply }); } @@ -807,11 +820,21 @@ impl App { // --- screenshot automation --------------------------------------------------------- fn screenshot(&mut self, ctx: &egui::Context) { - let Some((path, frames)) = &mut self.screenshot else { + let Some((path, started, delay)) = &mut self.screenshot else { return; }; - *frames += 1; - if *frames < 40 { + let (elapsed, delay) = (started.elapsed(), *delay); + if !self.actions.is_empty() && elapsed >= delay / 2 { + for a in std::mem::take(&mut self.actions) { + if !chrome::run_labelled(self, ctx, &a) { + eprintln!("--do: no palette command {a:?}"); + } + } + ctx.request_repaint(); + return; + } + let path = path.clone(); + if elapsed < delay { std::thread::sleep(std::time::Duration::from_millis(15)); ctx.request_repaint(); return; @@ -824,7 +847,7 @@ impl App { if let Some(img) = shot { let [w, h] = img.size; let raw: Vec = img.pixels.iter().flat_map(|p| p.to_array()).collect(); - if let Err(e) = image::save_buffer(&*path, &raw, w as u32, h as u32, image::ExtendedColorType::Rgba8) { + if let Err(e) = image::save_buffer(&path, &raw, w as u32, h as u32, image::ExtendedColorType::Rgba8) { eprintln!("screenshot: {e}"); } ctx.send_viewport_cmd(egui::ViewportCommand::Close); diff --git a/rust/app/src/chrome.rs b/rust/app/src/chrome.rs index 15033f1..7d38bb3 100644 --- a/rust/app/src/chrome.rs +++ b/rust/app/src/chrome.rs @@ -436,6 +436,7 @@ enum Cmd { Theme(ThemeKind), About, Fullscreen, + Sftp(String), } /// Fuzzy subsequence match with bonuses for consecutive and leading hits (utils.js fuzzyMatch). @@ -503,14 +504,29 @@ fn commands(app: &App) -> Vec<(Cmd, String, &'static str, &'static str, String)> if app.s.focused_session().and_then(|f| app.s.pane(&f)).is_some_and(|p| matches!(p.view, View::Terminal(_))) { v.push((Cmd::SplitRight, "Split Pane Right".into(), "Terminal", icon::SQUARE_SPLIT_HORIZONTAL, keys("Mod+Shift+D"))); v.push((Cmd::SplitDown, "Split Pane Down".into(), "Terminal", icon::SQUARE_SPLIT_VERTICAL, keys("Mod+Shift+E"))); + if let Some(id) = app.s.focused_session().filter(|f| app.s.pane(f).is_some_and(|p| p.info.protocol == Protocol::Ssh)) { + v.push((Cmd::Sftp(id), "SFTP Browser".into(), "Terminal", icon::FOLDER_OPEN, String::new())); + } v.push((Cmd::Clear, "Clear Terminal".into(), "Terminal", icon::ERASER, String::new())); v.push((Cmd::Reset, "Reset Terminal".into(), "Terminal", icon::ARROW_COUNTER_CLOCKWISE, String::new())); } v } +/// Runs the palette command with this label (case-insensitive); false when there is none. +pub fn run_labelled(app: &mut App, ctx: &egui::Context, label: &str) -> bool { + match commands(app).into_iter().find(|c| c.1.eq_ignore_ascii_case(label)) { + Some(c) => { + run(app, ctx, c.0); + true + } + None => false, + } +} + fn run(app: &mut App, ctx: &egui::Context, c: Cmd) { match c { + Cmd::Sftp(id) => app.s.request(Request::Sftp(id)), Cmd::Connect(id) => app.connect_profile(&id, false), Cmd::NewProfile(p) => app.s.request(Request::NewProfile { protocol: Some(p), folder: String::new() }), Cmd::Quick => app.quick.open(), diff --git a/rust/app/src/irc_view.rs b/rust/app/src/irc_view.rs index 9d75926..70107ff 100644 --- a/rust/app/src/irc_view.rs +++ b/rust/app/src/irc_view.rs @@ -191,7 +191,12 @@ impl IrcView { pub fn on_event(&mut self, event: irc::Event) { match route(&event) { Route::Rooms => { + // Show the first room once auto-join lands, rather than leaving the server log up. + let first = self.joined.is_empty() && self.active == SERVER; self.joined = event.rooms; + if first && let Some(r) = self.joined.first().map(|r| r.name.clone()) { + self.set_active(&r); + } for r in self.joined.clone() { self.target(&r.name); } diff --git a/rust/app/src/main.rs b/rust/app/src/main.rs index 5c8cd87..7c7066d 100644 --- a/rust/app/src/main.rs +++ b/rust/app/src/main.rs @@ -33,7 +33,16 @@ use nexus_core::service::Service; use std::sync::Arc; fn main() -> eframe::Result { - let mut opts = app::Options { screenshot: None, theme: None, run_local: None, open: None, connect: None }; + let mut opts = app::Options { + screenshot: None, + theme: None, + run_local: None, + open: None, + connect: vec![], + actions: vec![], + answers: vec![], + shot_delay: std::time::Duration::from_millis(600), + }; let mut size = [1360.0, 860.0]; let mut data_dir = None; let mut args = std::env::args().skip(1); @@ -42,7 +51,14 @@ fn main() -> eframe::Result { "--theme" => opts.theme = args.next(), "--run-local" => opts.run_local = args.next(), "--open" => opts.open = args.next(), - "--connect" => opts.connect = args.next(), + "--connect" => opts.connect.extend(args.next()), + "--do" => opts.actions.extend(args.next()), + "--answer" => opts.answers.extend(args.next()), + "--shot-delay" => { + if let Some(ms) = args.next().and_then(|s| s.parse().ok()) { + opts.shot_delay = std::time::Duration::from_millis(ms); + } + } "--screenshot" => opts.screenshot = args.next().map(Into::into), // Use another data directory (testing, screenshots, portable installs). "--data-dir" => data_dir = args.next(), @@ -57,7 +73,7 @@ fn main() -> eframe::Result { } _ => { println!( - "usage: nexus-terminal [--theme pro|midnight|light] [--data-dir DIR] [--connect NAME] [--open WINDOW] [--run-local CMD] [--screenshot out.png] [--size WxH] [--version]" + "usage: nexus-terminal [--theme pro|midnight|light] [--data-dir DIR] [--connect NAME]... [--open WINDOW] [--do PALETTE-COMMAND]... [--answer SECRET]... [--shot-delay MS] [--run-local CMD] [--screenshot out.png] [--size WxH] [--version]" ); return Ok(()); } diff --git a/rust/app/src/recordings.rs b/rust/app/src/recordings.rs index b0d8e74..55bdb4c 100644 --- a/rust/app/src/recordings.rs +++ b/rust/app/src/recordings.rs @@ -183,6 +183,8 @@ impl Recordings { let mut open = self.open; egui::Window::new(format!("{} Session Recordings", icon::RECORD)) .id(egui::Id::new("recordings")) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center()) .open(&mut open) .collapsible(false) .default_size([640.0, 440.0]) @@ -298,6 +300,8 @@ impl Recordings { let mut open = true; egui::Window::new(format!("{} {}", icon::PLAY_CIRCLE, p.title)) .id(egui::Id::new("rec-playback")) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center()) .open(&mut open) .collapsible(false) .default_size([820.0, 520.0]) diff --git a/rust/app/src/snippets.rs b/rust/app/src/snippets.rs index 598caea..e118817 100644 --- a/rust/app/src/snippets.rs +++ b/rust/app/src/snippets.rs @@ -104,6 +104,8 @@ impl Snippets { let mut open = self.open; egui::Window::new(format!("{} Snippets", icon::CODE)) .id(egui::Id::new("snippets")) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center()) .open(&mut open) .collapsible(false) .default_size([720.0, 440.0]) diff --git a/rust/app/src/triggers.rs b/rust/app/src/triggers.rs index eeac228..831bbb4 100644 --- a/rust/app/src/triggers.rs +++ b/rust/app/src/triggers.rs @@ -159,6 +159,8 @@ impl Triggers { let mut open = self.open; egui::Window::new(format!("{} Trigger Rules", icon::LIGHTNING)) .id(egui::Id::new("triggers")) + .pivot(egui::Align2::CENTER_CENTER) + .default_pos(ctx.content_rect().center()) .open(&mut open) .collapsible(false) .default_size([620.0, 380.0]) diff --git a/rust/core/examples/demo_data.rs b/rust/core/examples/demo_data.rs new file mode 100644 index 0000000..806b881 --- /dev/null +++ b/rust/core/examples/demo_data.rs @@ -0,0 +1,181 @@ +//! Fills a data directory with demo profiles, snippets, known hosts, trigger rules and recordings +//! for the user-guide screenshots (docs/guide/screenshots.sh). +//! +//! cargo run -p nexus-core --example demo_data -- DATA_DIR SSH_KEY + +use nexus_core::config::AppConfig; +use nexus_core::models::*; +use nexus_core::service::Service; + +fn main() { + let mut args = std::env::args().skip(1); + let (Some(dir), Some(key)) = (args.next(), args.next()) else { + eprintln!("usage: demo_data DATA_DIR SSH_KEY"); + std::process::exit(2); + }; + let config = AppConfig::in_dir(dir.into()); + std::fs::create_dir_all(&config.vault_dir).unwrap(); + std::fs::create_dir_all(&config.recordings_dir).unwrap(); + let svc = Service::open(config).unwrap(); + let user = std::env::var("USER").unwrap_or_else(|_| "andy".into()); + + let mk = |proto, name: &str, host: &str, port: i64, folder: &str, fav: bool| { + let mut p = Profile::new(proto); + p.display_name = name.into(); + p.host = host.into(); + if port != 0 { + p.port = port; + } + p.folder = folder.into(); + p.favourite = fav; + p + }; + let ssh = |name: &str, folder: &str, fav: bool, tags: &[&str]| { + let mut p = mk(Protocol::Ssh, name, "127.0.0.1", 42022, folder, fav); + p.username = user.clone(); + p.tags = tags.iter().map(|t| t.to_string()).collect(); + let s = p.ssh.as_mut().unwrap(); + s.private_key_path = key.clone(); + s.auth_methods = vec!["publickey".into()]; + s.host_key_policy = HostKeyPolicy::AcceptNew; + p + }; + + let mut profiles = vec![]; + let mut atlas = ssh("atlas", "Production", true, &["web", "eu-west"]); + atlas.description = "Primary web node".into(); + atlas.notes = "Deploys go out from here. Be nice to it.".into(); + profiles.push(atlas); + let mut db = ssh("db-primary", "Production", false, &["db"]); + db.description = "Postgres primary, via the bastion".into(); + db.ssh.as_mut().unwrap().jump_hosts = vec![JumpHost { + host: "bastion.example.net".into(), + port: 22, + username: user.clone(), + auth_method: "publickey".into(), + secret_mode: SecretMode::Saved, + private_key_path: key.clone(), + }]; + db.ssh.as_mut().unwrap().port_forwards = vec![PortForward { + id: new_id(), + direction: ForwardDirection::Local, + bind_host: "127.0.0.1".into(), + bind_port: 15432, + dest_host: "127.0.0.1".into(), + dest_port: 5432, + enabled: true, + label: "Postgres".into(), + }]; + profiles.push(db); + let mut hello = ssh("hello-ssh", "Production", false, &["demo"]); + hello.ssh.as_mut().unwrap().host_key_policy = HostKeyPolicy::Ask; + hello.description = "Same box, but asks about its host key".into(); + profiles.push(hello); + profiles.push(ssh("build-runner", "Lab", false, &["ci"])); + + let mut router = mk(Protocol::Telnet, "Core router", "192.0.2.1", 0, "Lab", false); + router.description = "Out-of-band console".into(); + profiles.push(router); + let mut console = mk(Protocol::Serial, "Switch console", "/dev/ttyUSB0", 0, "Lab", false); + console.serial.as_mut().unwrap().baud_rate = 9600; + profiles.push(console); + let mut sensor = mk(Protocol::Raw, "Sensor feed", "127.0.0.1", 42023, "Lab", false); + let raw = sensor.raw.as_mut().unwrap(); + raw.show_timestamps = true; + raw.show_direction = true; + raw.line_ending = LineEnding::Lf; + raw.payload_presets = vec![ + PayloadPreset { id: new_id(), name: "Status".into(), payload: "U1RBVFVT".into(), format: "text".into() }, + PayloadPreset { id: new_id(), name: "Reset".into(), payload: "UkVTRVQ=".into(), format: "text".into() }, + ]; + profiles.push(sensor); + + let mut irc = mk(Protocol::Irc, "Libera Chat", "127.0.0.1", 42667, "Home", true); + let ic = irc.irc.as_mut().unwrap(); + ic.nick = "andy".into(); + ic.real_name = "Andy Dixon".into(); + ic.use_tls = false; + ic.auto_join_rooms = vec!["#nexus".into(), "#rust".into()]; + profiles.push(irc); + let mut gh = mk(Protocol::Github, "GitHub", "github.com", 0, "Home", false); + gh.github.as_mut().unwrap().base_url = "http://127.0.0.1:42080".into(); + profiles.push(gh); + let mut vnc = mk(Protocol::Vnc, "Studio desktop", "127.0.0.1", 42900, "Home", false); + vnc.vnc.as_mut().unwrap().vnc_host = "127.0.0.1".into(); + vnc.vnc.as_mut().unwrap().vnc_port = 42900; + vnc.vnc.as_mut().unwrap().scale_to_fit = true; + profiles.push(vnc); + + for p in profiles { + svc.create_profile(p).unwrap(); + } + + for (name, cmd, desc, folder) in [ + ("Disk usage", "df -h | sort -k5 -r | head", "Fullest filesystems first", "Diagnostics"), + ("Who's hogging CPU", "ps aux --sort=-%cpu | head -n 8", "Top processes by CPU", "Diagnostics"), + ("Tail a service", "journalctl -fu {{service}}", "Asks which service", "Logs"), + ("Stamp the log", "echo '--- {{hostname}} {{date}} {{time}} ---'", "Drop a marker in the scrollback", "Logs"), + ("Restart nginx", "sudo systemctl restart nginx && systemctl status nginx --no-pager", "", "Deploy"), + ] { + svc.save_snippet(Snippet { + id: String::new(), + name: name.into(), + command: cmd.into(), + description: desc.into(), + folder: folder.into(), + tags: vec![], + created_at: now(), + updated_at: now(), + }) + .unwrap(); + } + + for (host, port, kind, fp) in [ + ("bastion.example.net", 22, "ssh-ed25519", "SHA256:x1rT0pV6c1m9QbHq4nWJcF5gk1Yt3uZ3w8sLrM2dE0Q"), + ("192.0.2.40", 22, "ecdsa-sha2-nistp256", "SHA256:9Hq0b2VtZr6y1oQwA7mXJ4sEo5kP8fN3cLdT6uB2iRg"), + ("git.example.org", 2222, "ssh-rsa", "SHA256:Qm7cV2pK9zR1tY5uW3xN8bL0aE6sD4fG2hJ1kM7nP5o"), + ] { + let n = now(); + svc.store + .save_known_host(&KnownHost { + id: String::new(), + host: host.into(), + port, + key_type: kind.into(), + fingerprint: fp.into(), + public_key: String::new(), + first_seen: n, + last_seen: n, + trusted: true, + }) + .unwrap(); + } + + std::fs::write( + svc.config.data_dir.join("triggers.json"), + r#"[{"name":"Errors","pattern":"(?i)\\b(error|fatal|panic)\\b","isRegex":true,"action":"both","caseSensitive":false,"enabled":true}, +{"name":"Build finished","pattern":"BUILD SUCCESSFUL","isRegex":false,"action":"sound","caseSensitive":true,"enabled":true}, +{"name":"Disk nearly full","pattern":"9[0-9]%","isRegex":true,"action":"highlight","caseSensitive":false,"enabled":false}]"#, + ) + .unwrap(); + + let start = 1_791_633_486u64; + for (i, (title, lines)) in [ + ("atlas — deploy", &["$ ./deploy.sh\r\n", "\x1b[32m✔\x1b[0m built in 41s\r\n", "\x1b[32m✔\x1b[0m rolled out to 4/4 nodes\r\n"][..]), + ("Switch console", &["Switch> enable\r\n", "Switch# show version\r\n", "Uptime is 212 days\r\n"][..]), + ("Local Machine", &["$ cargo test\r\n", "test result: \x1b[32mok\x1b[0m. 135 passed\r\n"][..]), + ] + .iter() + .enumerate() + { + let mut body = format!( + "{{\"version\":2,\"width\":100,\"height\":30,\"timestamp\":{},\"title\":\"{title}\",\"env\":{{\"TERM\":\"xterm-256color\"}}}}\n", + start - i as u64 * 86_400 * 3 + ); + for (j, l) in lines.iter().enumerate() { + body += &format!("[{}.5,\"o\",{}]\n", j * 2, serde_json::to_string(l).unwrap()); + } + std::fs::write(svc.config.recordings_dir.join(format!("demo-{i}.cast")), body).unwrap(); + } + println!("demo data written to {}", svc.config.data_dir.display()); +} From 8766846a3b292005d5773e824108ba3a36085259 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Sun, 11 Oct 2026 19:41:31 +0100 Subject: [PATCH 40/45] docs: illustrated LaTeX user guide (docs/guide) with generated screenshots --- .gitignore | 3 + README.md | 1 + docs/guide/Makefile | 11 + docs/guide/chapters/appendix.tex | 111 ++++++++++ docs/guide/chapters/part1-start.tex | 248 +++++++++++++++++++++ docs/guide/chapters/part2-connect.tex | 273 +++++++++++++++++++++++ docs/guide/chapters/part3-faster.tex | 234 ++++++++++++++++++++ docs/guide/chapters/part4-safe.tex | 123 +++++++++++ docs/guide/chapters/part5-yours.tex | 107 +++++++++ docs/guide/chapters/part6-fix.tex | 88 ++++++++ docs/guide/fonts/Phosphor-Fill.ttf | Bin 0 -> 449252 bytes docs/guide/fonts/Phosphor-LICENSE.txt | 21 ++ docs/guide/fonts/Phosphor.ttf | Bin 0 -> 488636 bytes docs/guide/img/broadcast.png | Bin 0 -> 124648 bytes docs/guide/img/editor-github.png | Bin 0 -> 132507 bytes docs/guide/img/editor-irc.png | Bin 0 -> 127211 bytes docs/guide/img/editor-new.png | Bin 0 -> 141305 bytes docs/guide/img/editor-raw.png | Bin 0 -> 127697 bytes docs/guide/img/editor-serial.png | Bin 0 -> 127890 bytes docs/guide/img/editor-ssh.png | Bin 0 -> 138594 bytes docs/guide/img/editor-telnet.png | Bin 0 -> 125635 bytes docs/guide/img/editor-vnc.png | Bin 0 -> 130784 bytes docs/guide/img/export.png | Bin 0 -> 93426 bytes docs/guide/img/github.png | Bin 0 -> 125188 bytes docs/guide/img/hostkey.png | Bin 0 -> 79933 bytes docs/guide/img/irc.png | Bin 0 -> 118232 bytes docs/guide/img/keys.png | Bin 0 -> 95439 bytes docs/guide/img/local.png | Bin 0 -> 110034 bytes docs/guide/img/logo.pdf | Bin 0 -> 72604 bytes docs/guide/img/palette.png | Bin 0 -> 92182 bytes docs/guide/img/quick.png | Bin 0 -> 72439 bytes docs/guide/img/raw.png | Bin 0 -> 141875 bytes docs/guide/img/recordings.png | Bin 0 -> 87372 bytes docs/guide/img/settings-about.png | Bin 0 -> 101573 bytes docs/guide/img/settings-general.png | Bin 0 -> 98988 bytes docs/guide/img/settings-help.png | Bin 0 -> 156371 bytes docs/guide/img/settings-knownhosts.png | Bin 0 -> 104366 bytes docs/guide/img/settings-security.png | Bin 0 -> 92395 bytes docs/guide/img/settings-terminal.png | Bin 0 -> 94009 bytes docs/guide/img/sftp.png | Bin 0 -> 136989 bytes docs/guide/img/snippets.png | Bin 0 -> 93846 bytes docs/guide/img/split.png | Bin 0 -> 131537 bytes docs/guide/img/ssh-light.png | Bin 0 -> 125567 bytes docs/guide/img/ssh.png | Bin 0 -> 120145 bytes docs/guide/img/triggers.png | Bin 0 -> 88253 bytes docs/guide/img/vnc.png | Bin 0 -> 181541 bytes docs/guide/img/welcome-light.png | Bin 0 -> 291757 bytes docs/guide/img/welcome-midnight.png | Bin 0 -> 77625 bytes docs/guide/img/welcome.png | Bin 0 -> 78089 bytes docs/guide/nexus-guide.pdf | Bin 0 -> 3845222 bytes docs/guide/nexus-guide.sty | 286 +++++++++++++++++++++++++ docs/guide/nexus-guide.tex | 100 +++++++++ docs/guide/tools/demo-shell.sh | 27 +++ docs/guide/tools/fakes.py | 214 ++++++++++++++++++ docs/guide/tools/screenshots.sh | 100 +++++++++ 55 files changed, 1947 insertions(+) create mode 100644 docs/guide/Makefile create mode 100644 docs/guide/chapters/appendix.tex create mode 100644 docs/guide/chapters/part1-start.tex create mode 100644 docs/guide/chapters/part2-connect.tex create mode 100644 docs/guide/chapters/part3-faster.tex create mode 100644 docs/guide/chapters/part4-safe.tex create mode 100644 docs/guide/chapters/part5-yours.tex create mode 100644 docs/guide/chapters/part6-fix.tex create mode 100644 docs/guide/fonts/Phosphor-Fill.ttf create mode 100644 docs/guide/fonts/Phosphor-LICENSE.txt create mode 100644 docs/guide/fonts/Phosphor.ttf create mode 100644 docs/guide/img/broadcast.png create mode 100644 docs/guide/img/editor-github.png create mode 100644 docs/guide/img/editor-irc.png create mode 100644 docs/guide/img/editor-new.png create mode 100644 docs/guide/img/editor-raw.png create mode 100644 docs/guide/img/editor-serial.png create mode 100644 docs/guide/img/editor-ssh.png create mode 100644 docs/guide/img/editor-telnet.png create mode 100644 docs/guide/img/editor-vnc.png create mode 100644 docs/guide/img/export.png create mode 100644 docs/guide/img/github.png create mode 100644 docs/guide/img/hostkey.png create mode 100644 docs/guide/img/irc.png create mode 100644 docs/guide/img/keys.png create mode 100644 docs/guide/img/local.png create mode 100644 docs/guide/img/logo.pdf create mode 100644 docs/guide/img/palette.png create mode 100644 docs/guide/img/quick.png create mode 100644 docs/guide/img/raw.png create mode 100644 docs/guide/img/recordings.png create mode 100644 docs/guide/img/settings-about.png create mode 100644 docs/guide/img/settings-general.png create mode 100644 docs/guide/img/settings-help.png create mode 100644 docs/guide/img/settings-knownhosts.png create mode 100644 docs/guide/img/settings-security.png create mode 100644 docs/guide/img/settings-terminal.png create mode 100644 docs/guide/img/sftp.png create mode 100644 docs/guide/img/snippets.png create mode 100644 docs/guide/img/split.png create mode 100644 docs/guide/img/ssh-light.png create mode 100644 docs/guide/img/ssh.png create mode 100644 docs/guide/img/triggers.png create mode 100644 docs/guide/img/vnc.png create mode 100644 docs/guide/img/welcome-light.png create mode 100644 docs/guide/img/welcome-midnight.png create mode 100644 docs/guide/img/welcome.png create mode 100644 docs/guide/nexus-guide.pdf create mode 100644 docs/guide/nexus-guide.sty create mode 100644 docs/guide/nexus-guide.tex create mode 100755 docs/guide/tools/demo-shell.sh create mode 100644 docs/guide/tools/fakes.py create mode 100755 docs/guide/tools/screenshots.sh diff --git a/.gitignore b/.gitignore index 65b0396..f6c231a 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ vault.json # Agent worktrees .claude/worktrees/ + +# User guide LaTeX output +docs/guide/build/ diff --git a/README.md b/README.md index e4ad419..fd27424 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ On first run, Nexus Terminal copies your Alchementary Link profiles, encrypted v - Product overview: [docs/overview.md](docs/overview.md) - Quick start (shareable): [docs/quick-start.md](docs/quick-start.md) - User manual: [docs/user-manual.md](docs/user-manual.md) +- Illustrated user guide (PDF): [docs/guide/nexus-guide.pdf](docs/guide/nexus-guide.pdf), built from LaTeX in [docs/guide](docs/guide) (`make` there; `tools/screenshots.sh` regenerates the screenshots) - Build and release: [docs/build-and-release.md](docs/build-and-release.md) ## Keyboard shortcuts diff --git a/docs/guide/Makefile b/docs/guide/Makefile new file mode 100644 index 0000000..ae5a486 --- /dev/null +++ b/docs/guide/Makefile @@ -0,0 +1,11 @@ +# Builds nexus-guide.pdf with LuaLaTeX (three passes: contents, page references, then page overlays settle). +# Needs LuaLaTeX with luaotfload (Debian/Ubuntu: texlive-luatex texlive-latex-extra). +# Screenshots: tools/screenshots.sh (needs xvfb-run, sshd, python3). +OUT ?= build +nexus-guide.pdf: nexus-guide.tex nexus-guide.sty chapters/*.tex img/* + mkdir -p $(OUT) + lualatex -interaction=nonstopmode -halt-on-error -output-directory=$(OUT) nexus-guide.tex > /dev/null || (grep -A8 '^!' $(OUT)/nexus-guide.log; false) + for i in 1 2; do lualatex -interaction=nonstopmode -halt-on-error -output-directory=$(OUT) nexus-guide.tex > /dev/null; done + cp $(OUT)/nexus-guide.pdf . +clean: + rm -rf $(OUT) diff --git a/docs/guide/chapters/appendix.tex b/docs/guide/chapters/appendix.tex new file mode 100644 index 0000000..7180959 --- /dev/null +++ b/docs/guide/chapters/appendix.tex @@ -0,0 +1,111 @@ +\guidepart{slate}{Extras}{The extras} + {The cheat sheet, the command line, and words you might trip over.} + {\partitem{\iKeyboard}{Keyboard cheat sheet} + \partitem{\iTerminal}{Command-line options} + \partitem{\iBrain}{Glossary}} + +% ========================================================================================= +\chapter{Keyboard cheat sheet}\label{app:keys} +\chapmeta{print it}{Stick this page next to your monitor} + +\newcommand\shortcut[2]{#1 & #2\\} +\newcommand\sheet[3]{% + \begin{tcolorbox}[enhanced, arc=8pt, boxrule=0pt, colback=#1!7, left=10pt, right=10pt, top=8pt, bottom=8pt, + before skip=0pt, after skip=10pt, fontupper=\small] + {\heavy\large\color{#1!85!black}#2}\par\vspace{4pt} + \renewcommand\arraystretch{1.5}\begin{tabularx}{\linewidth}{@{}X>{\raggedleft\arraybackslash}p{36mm}@{}}#3\end{tabularx} + \end{tcolorbox}} + +\begin{multicols}{2} +\sheet{accent}{\iRocket\enspace Everywhere}{% + \shortcut{Command palette}{\keys{\mod+K}} + \shortcut{New connection}{\keys{\mod+N}} + \shortcut{Quick Connect}{\keys{\mod+Shift+Q}} + \shortcut{Settings}{\keys{\mod+,}} + \shortcut{Full screen}{\kbd{F11}}} + +\sheet{violet}{\iSplit\enspace Tabs \& panes}{% + \shortcut{Close tab}{\keys{\mod+W}} + \shortcut{Next tab}{\keys{\mod+Tab}} + \shortcut{Previous tab}{\keys{\mod+Shift+Tab}} + \shortcut{Go to tab 1–9}{\keys{\mod+1}…\kbd{9}} + \shortcut{Split right}{\keys{\mod+Shift+D}} + \shortcut{Split down}{\keys{\mod+Shift+E}}} + +\sheet{teal}{\iWand\enspace Power tools}{% + \shortcut{Broadcast on/off}{\keys{\mod+Shift+B}} + \shortcut{Snippets}{\keys{\mod+Shift+S}} + \shortcut{Record on/off}{\keys{\mod+Shift+R}}} + +\sheet{amber}{\iTerminal\enspace In the terminal}{% + \shortcut{Copy selection}{\keys{\mod+C}} + \shortcut{Paste}{\keys{\mod+V}} + \shortcut{Find}{\keys{\mod+F}} + \shortcut{Next / previous match}{\kbd{Enter} / \keys{Shift+Enter}} + \shortcut{Scroll a page}{\keys{Shift+PageUp}} + \shortcut{Top / bottom}{\keys{Shift+Home} / \kbd{End}} + \shortcut{Open link}{\kbd{Ctrl}/\kbd{\ph{E1C4}} + click}} + +\sheet{pink}{\iMap\enspace Mouse moves}{% + \shortcut{Select word / line}{double / triple-click} + \shortcut{Extend selection}{\kbd{Shift} + click} + \shortcut{Select several profiles}{\kbd{Ctrl}/\kbd{\ph{E1C4}} + click} + \shortcut{Everything else}{right-click it}} +\end{multicols} + +% ========================================================================================= +\chapter{Command-line options}\label{app:cli} +\chapmeta{1 minute}{You'll script, demo and test with a few handy flags} + +\renewcommand\arraystretch{1.5} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\ttfamily\color{ink}}p{52mm}X@{}} + \toprule + --data-dir DIR & Use another data folder (portable installs, demos, testing).\\ + --theme pro|midnight|light & Start with this theme.\\ + --connect NAME & Connect to a saved profile by name at startup. Repeat it for several.\\ + --open WINDOW & Open a window at startup: \code{settings}, \code{settings:security}, \code{snippets}, + \code{keys}, \code{recordings}, \code{palette}, \code{quick}…\\ + --version & Print the version and exit.\\ + \bottomrule +\end{tabularx} + +\begin{sidequest}[How the screenshots in this guide were made] +Every screenshot here is the real app, driven by \code{docs/guide/tools/screenshots.sh}: it starts throwaway +SSH, IRC, VNC and GitHub-ish servers, fills a demo data folder, and runs Nexus Terminal headless with +\code{--screenshot}, \code{--do} (run a palette command) and \code{--answer} (fill a password prompt). Change the app, +rerun the script, rebuild the guide. +\end{sidequest} + +% ========================================================================================= +\chapter{Glossary}\label{app:glossary} +\chapmeta{dip in}{Words you might trip over, in one line each} + +\begin{multicols}{2}\small +\begin{description}[leftmargin=0pt, itemsep=5pt] + \item[Agent] A background program holding your unlocked SSH keys, so you type the passphrase once. + \item[Asciicast] The file format for terminal recordings. Plays in Nexus Terminal and \code{asciinema}. + \item[Bastion / jump host] A server you go \emph{through} to reach the ones behind it. + \item[Baud rate] How fast a serial line talks. Both ends must agree. + \item[Broadcast] Typing into several sessions at once. + \item[DTR / RTS] Serial control lines. Some boards use them for reset or flashing. + \item[Fingerprint] A short summary of a server's host key, used to spot imposters. + \item[Host key] A server's identity. Changes when it's rebuilt, or when someone's pretending. + \item[Keychain] Your operating system's password store. + \item[Port forward] A tunnel carrying one TCP port through SSH. + \item[Profile] A saved connection. + \item[RAW TCP] A plain socket: bytes in, bytes out, no terminal rules. + \item[SFTP] File transfer over SSH. + \item[Snippet] A saved command, optionally with blanks to fill in. + \item[TOTP] Time-based one-time passwords: the six digits from your authenticator app. + \item[Trigger] A rule that watches output and reacts to a pattern. + \item[Vault] Nexus Terminal's encrypted password store, for when there's no keychain. + \item[VNC] A way of seeing and controlling another computer's desktop. +\end{description} +\end{multicols} + +\vfill +\begin{center} +\includegraphics[width=16mm]{img/logo.pdf}\par\vspace{6pt} +{\heavy\color{ink}That's the lot.}\par +{\small\color{muted}Go and connect to something. \iRocket} +\end{center} diff --git a/docs/guide/chapters/part1-start.tex b/docs/guide/chapters/part1-start.tex new file mode 100644 index 0000000..8304747 --- /dev/null +++ b/docs/guide/chapters/part1-start.tex @@ -0,0 +1,248 @@ +\guidepart{accent}{Get going}{Get going} + {From zero to connected before your tea goes cold.} + {\partitem{\iRocket}{Five-minute start} + \partitem{\iMap}{The grand tour} + \partitem{\iList}{Profiles: your address book}} + +% ========================================================================================= +\chapter{Five-minute start}\label{ch:start} +\chapmeta{5 minutes}{You'll be connected to something and know the one shortcut that matters} + +\begin{tldr} +Install it, open it, press \keys{\mod+N}, fill in a host, hit \ui{Create \& Connect}. Done. +Lost? Press \keys{\mod+K} and type what you want. +\end{tldr} + +\section{Install it} + +Grab the zip for your machine from \code{dixon.cx/downloads/nexus}. They're named +\code{NexusTerminal--.zip}, so a modern Mac wants \code{darwin-arm64} and most PCs want \code{amd64}. + +\begin{description} + \item[\iDesktop\enspace Windows] Unzip it and run \code{NexusTerminal-windows-amd64.exe}. No installer, no admin rights, + nothing in the registry. Pop it somewhere sensible like \code{C:\textbackslash Tools} and pin it to the taskbar. + \item[\iDesktop\enspace macOS] Unzip it and drag \code{NexusTerminal-darwin-arm64.app} into \ui{Applications}. If macOS + grumbles that it can't check the developer, right-click the app, choose \ui{Open}, then \ui{Open} again. You only + have to do that once. + \item[\iDesktop\enspace Linux] Unzip it and run \code{./install.sh}. It copies \code{nexus-terminal} into + \code{~/.local/bin} and adds a launcher entry with the icon, so it turns up in your app menu. + (Make sure \code{~/.local/bin} is on your \code{PATH}.) +\end{description} + +\begin{sidequest}[Build it yourself] +It's a single Rust program. With Rust installed: \code{cd rust \&\& cargo run --release}. The finished binary lands in +\code{rust/target/release/nexus-terminal}. The build guide in \code{docs/build-and-release.md} has the full story. +\end{sidequest} + +\section{Open it} + +The first time you open Nexus Terminal you get a calm, empty welcome screen. Three buttons, four shortcuts. That's it. + +\shot{welcome.png}{The welcome screen. Your saved connections will live down the left; here there are a few already.} + +\begin{headsup}[Coming from Alchementary Link?] +Nexus Terminal spots your old data on first run and copies your profiles, vault and recordings across +automatically. You'll see a little toast saying so. Your old data is left exactly where it was. +The details are in \hyperref[ch:move]{Moving house} on p.\,\pageref{ch:move}. +\end{headsup} + +\section{Make your first connection} + +\begin{enumerate} + \item Click \ui{New Connection}, or press \keys{\mod+N}. + \item Pick the protocol along the top: \proto[teal]{SSH}, \proto[amber]{Telnet}, \proto[violet]{RAW TCP}, + \proto[slate]{GitHub}, \proto[rose]{VNC}, \proto[amber]{IRC} or \proto[accent]{Serial}. + \item Fill in the \ui{General} bit: a \ui{Display Name} you'll recognise, the \ui{Host}, the \ui{Port} + and your \ui{Username}. + \item Click \ui{Create \& Connect}. +\end{enumerate} + +That's genuinely it. A tab opens, you log in, and next time it's one click in the sidebar. + +\crop{editor-new.png}{1515}{40}{0}{70}{0.52\linewidth}{A brand-new SSH profile. Only the top few fields are needed to get going.} + +\begin{quickwin} +Click \ui{Test} before you save. Nexus Terminal checks the host and port are reachable and tells you the +round-trip time. If that fails, no amount of password-fiddling will help, so it's a great first check. +\end{quickwin} + +\section{Two shortcuts for impatient people} + +\subsection{Quick Connect} +Press \keys{\mod+Shift+Q}. +For a one-off session you don't want to save. Pick SSH, Telnet, RAW, IRC or Serial, type the host (or serial +device), hit \kbd{Enter}. Nothing gets saved. + +\crop{quick.png}{951}{620}{946}{481}{0.5\linewidth}{Quick Connect. In, out, nobody gets hurt.} + +\subsection{Local terminal} +Click \ui{Local Machine} at the top of the sidebar (or \ui{Local} on the welcome screen) for a shell on +\emph{this} computer. When you type \code{exit}, the tab says \code{[Exited - press enter to restart]}. +Press \kbd{Enter} and you've got a fresh shell in the same tab. + +\section{The one shortcut to rule them all} + +\begin{center} +\begin{tcolorbox}[enhanced, width=0.8\linewidth, arc=10pt, boxrule=0pt, colback=ink, fontupper=\color{white}, + halign=center, top=10pt, bottom=10pt, drop fuzzy shadow] +{\scalebox{2.4}{\keys{\mod+K}}}\par\vspace{6pt}\par\vspace{4pt} +{\color{white!70!ink} The command palette. If you remember nothing else, remember this.} +\end{tcolorbox} +\end{center} + +Type a few letters of \emph{anything}: a connection name, a tab, ``theme'', ``split'', ``lock'', ``snippets''. Use +\kbd{↑}\kbd{↓} to pick, \kbd{Enter} to go, \kbd{Esc} to bail. It's fuzzy, so ``atl'' finds \emph{atlas} and +``sft'' finds the \emph{SFTP Browser}. + +\crop{palette.png}{755}{732}{733}{154}{0.72\linewidth}{The command palette: every connection, tab and action in one searchable list.} + +\begin{quickwin}[Your five-minute checklist] +\begin{checklist} + \item Installed and opened Nexus Terminal + \item Opened a \ui{Local Machine} tab and typed \code{exit}, then \kbd{Enter} to get it back + \item Made a profile with \keys{\mod+N} and clicked \ui{Test} + \item Connected to it + \item Opened the palette with \keys{\mod+K} and found something by typing three letters +\end{checklist} +All ticked? You're officially more productive than you were five minutes ago. \iConfetti +\end{quickwin} + +% ========================================================================================= +\chapter{The grand tour}\label{ch:tour} +\chapmeta{4 minutes}{You'll know what every bit of the window is for} + +\begin{tldr} +Connections on the left. Sessions in tabs across the top. The terminal in the middle. Status along the bottom. +Everything else is one \keys{\mod+K} away. +\end{tldr} + +\begin{annotated}{ssh.png} + \callout{1}{0.124}{0.022} + \callout{2}{0.66}{0.022} + \callout{3}{0.888}{0.022} + \callout{4}{0.22}{0.067} + \callout{5}{0.03}{0.152} + \callout{6}{0.53}{0.063} + \callout{7}{0.966}{0.063} + \callout{8}{0.62}{0.42} + \callout{9}{0.16}{0.945} + \callout{10}{0.26}{0.985} + \callout{11}{0.86}{0.985} +\end{annotated} + +\begin{multicols}{2}\small +\begin{description}[leftmargin=0pt, labelsep=0.4em] + \item[\num{1} Menus] \menu{File}, \menu{Session}, \menu{View} and \menu{Help}. Drag the title bar to move the + window; double-click it to maximise. + \item[\num{2} Palette] The search box is the command palette. Click it or press \keys{\mod+K}. + \item[\num{3} Light/dark] Flip between the dark and light themes in one click. + \item[\num{4} Search] Fuzzy-search your connections by name, host, tag or protocol. + \item[\num{5} Sections] \ui{Local Machine}, \ui{Favourites} \iStar, \ui{Recent} (your last five), then one section + per group. Click a header to fold it away. + \item[\num{6} Tabs] One per session, with a coloured state dot, protocol badge and close button. SSH tabs get a + little \iFolder{} for files. A blue dot means ``new output while you weren't looking''. + \item[\num{7} New \& broadcast] \ui{+} opens a new connection; \iBroadcast{} toggles broadcast mode. + \item[\num{8} Workspace] Your terminal (or IRC chat, VNC desktop, GitHub list…). Split it into panes, too. + \item[\num{9} Footer] New connection, palette, import, export, recordings and settings. + \item[\num{10} Status] State, protocol and live latency for SSH. Shows \ui{REC} while recording and + \ui{BROADCAST} when broadcasting. + \item[\num{11} Sessions \& vault] How many sessions are connected and whether the vault is locked. Click the vault to + open security settings. +\end{description} +\end{multicols} + +\begin{protip} +Drag the right-hand edge of the sidebar to make it wider or narrower. Toasts (the little pop-up messages in the +top-right) tidy themselves away after a few seconds; click \ui{×} if you're impatient. +\end{protip} + +\section{Three looks} + +Nexus Terminal comes in \ui{Pro} (the default, a soft charcoal), \ui{Midnight} (darker, bluer, for 2\,a.m. +deploys) and \ui{Light} (for sunny days and projectors). Switch with the \iBulb{} button in the title bar, the +\menu{View} menu, or type ``theme'' into the palette. + +\shotpair{welcome-midnight.png}{Midnight}{ssh-light.png}{Light, with a session open} + +The \emph{terminal} has its own colour schemes too: eleven of them, including \ui{Dracula}, \ui{Nord}, +\ui{Tokyo Night}, \ui{Catppuccin Mocha} and \ui{Rosé Pine}. Those live in \menu{Settings\then Terminal} and the \menu{View} menu. + +% ========================================================================================= +\chapter{Profiles: your address book}\label{ch:profiles} +\chapmeta{6 minutes}{You'll keep dozens of connections tidy without thinking about it} + +\begin{tldr} +A \emph{profile} is a saved connection. Give it a name and a group, star the ones you use daily, and +right-click for everything else. Ctrl-click to select lots at once. +\end{tldr} + +\section{Create, edit, copy} + +\begin{itemize} + \item \textbf{New:} \ui{+} in the sidebar footer or tab strip, \keys{\mod+N}, or right-click a group header and choose + \ui{New connection in …} (it lands in that group automatically). + \item \textbf{Edit:} double-click a profile, or right-click\then\ui{Edit}. The editor slides in on the right. + \item \textbf{Copy:} right-click\then\ui{Duplicate}. Great for ``same as web-01 but web-02''. + \item \textbf{Delete:} right-click\then\ui{Delete}. This also deletes the saved passwords for that profile. +\end{itemize} + +\crop{editor-ssh.png}{1515}{40}{0}{70}{0.52\linewidth}{Editing an existing SSH profile. The editor scrolls; sections are grouped so you can ignore most of them.} + +\subsection{What goes in a profile} + +Every profile has the same \ui{General} section, then sections specific to its protocol (Part 2 covers each +one). The common bits are: + +\renewcommand\arraystretch{1.4} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\heavy\color{ink}}p{32mm}X@{}} + \toprule + Display Name & What you'll see in the sidebar. Make it obvious: ``db-primary'', not ``server 3''.\\ + Host / Port & Where to connect. Serial profiles have a \ui{Device} instead.\\ + Group & Pick an existing group or type a new name to create one.\\ + Description & One line shown on hover.\\ + Terminal & Font, size (8–36), scrollback (0–100\,000 lines) and cursor style, just for this profile.\\ + Tags \& Notes & Comma-separated tags (searchable!) and free-form notes for future-you.\\ + \bottomrule +\end{tabularx} + +Each protocol adds its own sections underneath. Here are four of them side by side; Part~2 goes through each one. + +\begin{center} +\newcommand\mini[2]{\begin{minipage}[t]{0.24\linewidth}\centering\includegraphics[width=\linewidth, trim=1515 600 0 70, clip]{img/#1}\par\vspace{2pt}{\scriptsize\color{muted}#2}\end{minipage}} +\mini{editor-telnet.png}{Telnet}\hfill\mini{editor-raw.png}{RAW TCP}\hfill\mini{editor-vnc.png}{VNC}\hfill\mini{editor-irc.png}{IRC} +\end{center} + +\begin{protip}[Tags are secretly brilliant] +Tag things by where they live and what they do (\code{eu-west}, \code{db}, \code{customer-x}). The sidebar search +matches tags, so typing ``db'' instantly narrows hundreds of profiles to the database servers. +\end{protip} + +\section{Favourites, groups and Recent} + +\begin{itemize} + \item Right-click\then\ui{Add to Favourites} pins a profile into the \ui{\iStar\ Favourites} section at the top. + \item \ui{Recent} always shows the last five things you connected to. You don't have to do anything. + \item Groups are just the \ui{Group} field. No group? It goes in \ui{Ungrouped}. +\end{itemize} + +\section{Do lots at once} + +Hold \kbd{Ctrl} (\kbd{\ph{E1C4}} on a Mac) and click several profiles. A bar appears at the bottom of the sidebar: + +\begin{center}\small +\begin{tabular}{@{}ll@{\qquad}ll@{}} + \ui{Connect all} & opens them all in tabs & \ui{Move group} & moves them (blank = Ungrouped)\\ + \ui{Disconnect} & closes their sessions & \ui{Delete} & deletes them (asks first)\\ + \ui{Tags} & add or replace tags & \ui{Clear} & forget the selection\\ +\end{tabular} +\end{center} + +\begin{quickwin} +Select every web server, choose \ui{Connect all}, then turn on broadcast (\keys{\mod+Shift+B}). You're now typing into +all of them at once. Chapter~\ref{ch:repeat} has the details, and the safety advice. +\end{quickwin} + +\section{``It's already open…''} + +Click a profile that already has a live session and Nexus Terminal asks whether to \ui{Use Existing} (jump to that +tab), open a \ui{New Connection} alongside it, or \ui{Cancel}. No more accidental duplicate sessions. diff --git a/docs/guide/chapters/part2-connect.tex b/docs/guide/chapters/part2-connect.tex new file mode 100644 index 0000000..c6529b0 --- /dev/null +++ b/docs/guide/chapters/part2-connect.tex @@ -0,0 +1,273 @@ +\guidepart{teal}{Connect}{Connect to all the things} + {Eight ways in. Pick the one you need; skip the rest.} + {\partitem{\iTerminal}{SSH: the big one} + \partitem{\iUsb}{Serial: cables and consoles} + \partitem{\iPlug}{Telnet and RAW TCP} + \partitem{\iDesktop}{VNC remote desktops} + \partitem{\iChat}{IRC} + \partitem{\iGithub}{GitHub}} + +% ========================================================================================= +\chapter{SSH: the big one}\label{ch:ssh} +\chapmeta{10 minutes (or 2 for the basics)}{You'll log in with keys, hop through bastions and never type a 2FA code again} + +\begin{tldr} +Host, username, key, done. Everything else (jump hosts, proxies, forwards, 2FA, login scripts) is optional and +lives in its own fold-away section of the editor. Open only the ones you need. +\end{tldr} + +\begin{glance}{teal}{SSH} + Default port & 22\\ + Logs in with & Password, public key, keyboard-interactive, SSH agent\\ + Extras & SFTP file browser, port forwards, live latency, agent forwarding, TOTP 2FA\\ + Good for & Servers, VMs, Raspberry Pis, anything with \code{sshd}\\ +\end{glance} + +\section{The two-minute version} + +\begin{enumerate} + \item \keys{\mod+N}, leave the protocol on \proto[teal]{SSH}. + \item \ui{Host}, \ui{Username}. Leave the port on 22 unless you know otherwise. + \item Under \ui{Key \& Passphrase}, pick your key from the suggestions (Nexus Terminal lists the keys in your + \code{\textasciitilde/.ssh} folder) and check for a little green \ui{Key verified}. + \item \ui{Create \& Connect}. The first time, you'll be asked to trust the server's host key. Say yes if you + were expecting it. +\end{enumerate} + +\crop{hostkey.png}{842}{720}{845}{589}{0.66\linewidth}{First time connecting? You'll be shown the server's fingerprint. \ui{Accept \& Save} if it's the server you meant.} + +\begin{headsup} +Type the full path to a key if you're not using the suggestions. Nexus Terminal doesn't expand \code{\textasciitilde} +in that box, so write \code{/home/you/.ssh/id\_ed25519} (or \code{C:\textbackslash Users\textbackslash you\textbackslash .ssh\textbackslash id\_ed25519}). +\end{headsup} + +\section{How it logs in} + +Under \ui{Authentication}, tick which \ui{Auth Methods} to try: \ui{Public key}, \ui{Password} and +\ui{Keyboard-interactive}. Under \ui{SSH Agent}, tick \ui{Use SSH Agent for Authentication} to use keys already +loaded into \code{ssh-agent} (or Pageant, or the Windows OpenSSH agent). + +Every password and passphrase has a \emph{mode}: save it securely, remember it until you quit, or ask every time. +That's all explained in \hyperref[ch:secrets]{Passwords \& the vault} (p.\,\pageref{ch:secrets}). + +\begin{protip}[Agent forwarding] +Tick \ui{Forward Agent to Remote} (it's \code{ssh -A}) and you can \code{git pull} or hop onwards from the server using +the keys on \emph{your} machine. Only do it on servers you trust: anyone with root there can use your agent while +you're connected. +\end{protip} + +\section{Jump hosts: getting through the bastion} + +Lots of servers hide behind a bastion. Add one or more hops under \ui{Jump Hosts}, each with its own user, host, +port and optional key. Nexus Terminal tunnels through them in order, so the chain +\textit{you\then bastion\then internal-box\then db-primary} is just three rows. + +\section{Proxies} + +Stuck behind a corporate proxy? Under \ui{Proxy}, choose \ui{SOCKS5} or \ui{HTTP CONNECT} and fill in the host, +port and (optional) username and password. The proxy is used to reach the SSH server, or the first jump host if +you have some. + +\section{2FA without the phone faff}\label{sec:totp} + +If your server asks for a one-time code, paste the Base32 TOTP secret (the text version of the QR code) into +\ui{TOTP Secret} and hit \ui{Save}. From then on, Nexus Terminal answers the ``Verification code:'' prompt itself. +\ui{Test} shows today's code so you can check it matches your phone. + +\begin{headsup} +Save the profile first, then save the TOTP secret. And make sure your computer's clock is right: TOTP codes are +time-based, so a clock that's a minute out will produce wrong codes. +\end{headsup} + +\section{Do things automatically on login} + +\begin{description} + \item[Startup] A \ui{Startup Command} to run once you're in (say, \code{tmux attach}), plus + \ui{Environment Variables} as \code{KEY=VALUE}, one per line. + \item[Login Script] For old-school boxes that ask questions. Add steps: \ui{Wait for} some text, then + \ui{Send} a reply. Repeat as needed. +\end{description} + +\section{Keeping it alive} + +Under \ui{Advanced}: the \ui{Terminal Type}, the \ui{Keepalive Interval} and \ui{Max Missed Keepalives}, compression, +and \ui{Auto-Reconnect} with a maximum number of attempts (1–20). Reconnection backs off politely: 1\,s, 2\,s, +4\,s… up to 30\,s. + +\begin{sidequest}[What's that number in the status bar?] +For SSH sessions the status bar shows live latency, the round trip to the server. Green is snappy. If it's +in the hundreds and your typing feels sticky, it's the network, not you. +\end{sidequest} + +% ========================================================================================= +\chapter{Serial: cables and consoles}\label{ch:serial} +\chapmeta{4 minutes}{You'll be talking to a switch, router or microcontroller over USB} + +\begin{tldr} +Plug in the adapter, pick the device, match the baud rate. 115200 8N1 is the default and usually right. +Right-click the tab to send a break or wiggle DTR/RTS. +\end{tldr} + +\begin{glance}{accent}{Serial} + Device & \code{/dev/ttyUSB0}, \code{/dev/cu.usbserial-…} or \code{COM3}\\ + Defaults & 115200 baud, 8 data bits, no parity, 1 stop bit, no flow control, CR line endings, DTR and RTS on\\ + Extras & Send Break, toggle DTR/RTS, auto-reconnect when the cable comes back\\ + Good for & Network gear consoles, Arduinos, ESP32s, Raspberry Pi UARTs, 3D printers\\ +\end{glance} + +\crop{editor-serial.png}{1515}{40}{0}{70}{0.52\linewidth}{A serial profile for a switch console at 9600 baud. Click refresh after plugging in an adapter.} + +\section{Setting it up} + +\begin{description} + \item[Device] Pick it from the list (hit refresh after plugging in) or type the path. + \item[Line Settings] \ui{Baud Rate} (a preset from 300 to 1\,000\,000, or \ui{Custom}), \ui{Data Bits} (5–8), + \ui{Parity} (none, odd, even), \ui{Stop Bits} (1 or 2) and \ui{Flow Control} (none, XON/XOFF, RTS/CTS). + \item[Behaviour] \ui{Line Ending}, \ui{Encoding}, \ui{Local Echo} (for devices that don't echo what you type), + \ui{Assert DTR on connect}, \ui{Assert RTS on connect} and \ui{Auto-reconnect}. +\end{description} + +\section{While you're connected} + +Right-click the tab for \ui{Send Break} (handy for getting into a router's ROMMON) and to toggle \ui{DTR} and +\ui{RTS} (handy for resetting boards that wire those to reset). Pull the cable out and the session disconnects; with +auto-reconnect on, it picks up again when you plug it back in. + +Serial tabs are proper terminals, so recording, triggers, broadcast, snippets, find and split panes all work. + +\begin{headsup}[Gibberish on screen?] +Garbled characters nearly always mean the baud rate, parity or stop bits don't match the device. Check the +device's manual. 9600 and 115200 are the usual suspects. +\end{headsup} + +\begin{protip}[Linux and macOS quirks] +On Linux, add yourself to the \code{dialout} group (\code{uucp} on some distributions) and log out and in again. +On a Mac, use the \code{/dev/cu.*} device, not \code{/dev/tty.*}. +\end{protip} + +% ========================================================================================= +\chapter{Telnet and RAW TCP}\label{ch:raw} +\chapmeta{4 minutes}{You'll poke at old kit and raw sockets without reaching for netcat} + +\begin{tldr} +Telnet is a terminal for old devices. RAW is a packet-level conversation with any TCP port, with a hex view. +\end{tldr} + +\section{Telnet} + +\begin{glance}{amber}{Telnet} + Default port & 23\\ + Options & Local echo, line mode, terminal type, encoding (UTF-8, ASCII, ISO 8859-1), line endings, login script\\ + Extras & Send Break from the tab menu, \ui{Debug Negotiation} for when options go wrong\\ +\end{glance} + +\begin{danger}[Telnet is plain text] +Everything, including your password, crosses the network unencrypted. Fine on a lab bench, not fine over the +internet or café Wi-Fi. +\end{danger} + +\section{RAW TCP} + +RAW connects to any TCP port and shows you exactly what goes back and forth. Instead of a terminal you get a +traffic log with \ui{Text}, \ui{Hex} and \ui{Split} views. + +\shot{raw.png}{A RAW session to a chatty sensor, with timestamps and direction markers turned on.} + +\begin{itemize} + \item \textbf{Send bar:} pick \ui{Text}, \ui{Hex} or \ui{Escaped} (\code{\textbackslash r\textbackslash n} and friends), + type, press \kbd{Enter}. One-click buttons send \ui{NUL}, \ui{CR}, \ui{LF}, \ui{ETX} and \ui{EOT}. + \item \textbf{Presets:} save payloads you send often in the profile under \ui{Payload Presets}, then pick them from + \ui{Presets…}. + \item \textbf{Capture:} \ui{Pause}, \ui{Clear}, and \ui{Save} (writes the whole conversation as JSON). + \item \textbf{Profile options:} encoding (UTF-8, ASCII, Hex), line ending, timestamps, direction, timeouts and + auto-reconnect. +\end{itemize} + +\begin{quickwin} +Point a RAW profile at a web server on port 80 and send \code{HEAD / HTTP/1.0\textbackslash r\textbackslash n\textbackslash r\textbackslash n} +in \ui{Escaped} mode. Instant HTTP headers, no browser required. +\end{quickwin} + +% ========================================================================================= +\chapter{VNC remote desktops}\label{ch:vnc} +\chapmeta{4 minutes}{You'll see and drive a remote desktop, safely} + +\begin{tldr} +VNC shows you another computer's screen. Tunnel it through SSH (tick a box), because plain VNC isn't encrypted. +\end{tldr} + +\begin{glance}{rose}{VNC} + Default port & 5900 (plus the display number: display \code{:1} is 5901)\\ + Display & \ui{View Only}, \ui{Scale to Fit}, \ui{Shared Session}, \ui{Quality (0-9)}\\ + Security & Optional SSH tunnel with password, key or agent login\\ +\end{glance} + +\shot{vnc.png}{A remote desktop, scaled to fit the tab.} + +\section{The toolbar} + +Connection state and desktop size on the left. On the right: switch between scaled and 1:1, refresh, +\ui{Paste Clipboard} (sends your clipboard to the remote machine) and \ui{Ctrl+Alt+Del}. Click the desktop to give +it your keyboard. Anything the remote machine copies lands on your clipboard automatically. + +\section{Tunnel it} + +Tick \ui{Enable SSH Tunnel} and fill in the SSH host, port, username and login method. The VNC traffic then +travels inside an encrypted SSH connection. If you connect to a VNC profile \emph{without} a tunnel, Nexus Terminal +warns you and offers \ui{Connect Anyway}, \ui{Edit Profile} or \ui{Cancel}. + +\begin{headsup}[Connecting to a Mac?] +Turn on \ui{VNC viewers may control screen with password} in macOS Screen Sharing settings. Nexus Terminal uses +standard VNC password authentication, not Apple's own login method. +\end{headsup} + +% ========================================================================================= +\chapter{IRC}\label{ch:irc} +\chapmeta{3 minutes}{You'll be chatting in your project's channels from the same window as your servers} + +\begin{tldr} +Nick, server, rooms to join. Tabs for each room, \code{/commands} as you'd expect, \kbd{Tab} completes nicks. +\end{tldr} + +\begin{glance}{amber}{IRC} + Default port & 6667, or 6697 with \ui{Use TLS} (use TLS!)\\ + Extras & NickServ identify on connect, auto-join rooms, room browser, SSH tunnel\\ +\end{glance} + +\shot{irc.png}{Chatting in \#nexus. Rooms down the right, users in the room below them.} + +\begin{itemize} + \item A \ui{Server} tab plus one per room or private conversation. A dot means unread messages. + \item Commands: \code{/join}, \code{/part}, \code{/msg}, \code{/me}, \code{/notice}, \code{/nick}, \code{/topic}, + \code{/list}, \code{/names}, \code{/whois}, \code{/who}, \code{/raw}, \code{/quit} and \code{/help}. + \item \kbd{Tab} completes a nick; click a nick to mention them. + \item The side panel lists your rooms, a room browser with a filter (click to join) and who's in the current room. +\end{itemize} + +% ========================================================================================= +\chapter{GitHub}\label{ch:github} +\chapmeta{2 minutes}{You'll see all your repositories, freshest first} + +\crop{editor-github.png}{1515}{780}{0}{70}{0.5\linewidth}{A GitHub profile: just the API address and a token.} + +\begin{tldr} +Paste a personal access token with the \code{repo} scope. Get a tidy, filterable list of every repository you can see. +\end{tldr} + +\shot{github.png}{Repositories grouped by owner, with the latest commit and who made it.} + +\begin{itemize} + \item Grouped by owner or organisation, sorted by most recent activity. + \item Badges for \proto[rose]{Private}, \proto[violet]{Fork} and \proto[amber]{Archived}, plus language and stars. + \item Filter box, \ui{Refresh now}, \ui{Visit} (opens in your browser) and \ui{ZIP} (downloads an archive). + \item Works with GitHub Enterprise: set \ui{API Base URL} to your server's API address. Refreshes every 60–3600 seconds. +\end{itemize} + +% ========================================================================================= +\chapter{Your own computer}\label{ch:local} +\chapmeta{1 minute}{You'll stop alt-tabbing to a separate terminal app} + +Click \ui{Local Machine} for a shell on this computer. It uses your system's default shell unless you choose +another in \menu{Settings\then General\then Local shell} (\code{pwsh}, \code{zsh}, \code{fish}, whatever you fancy). + +\shot[0.86\linewidth]{local.png}{A local shell, with all the same terminal powers as SSH.} diff --git a/docs/guide/chapters/part3-faster.tex b/docs/guide/chapters/part3-faster.tex new file mode 100644 index 0000000..bb816c1 --- /dev/null +++ b/docs/guide/chapters/part3-faster.tex @@ -0,0 +1,234 @@ +\guidepart{violet}{Work faster}{Work faster} + {Fewer clicks, less typing, more getting on with it.} + {\partitem{\iSplit}{Tabs and split panes} + \partitem{\iKeyboard}{Terminal superpowers} + \partitem{\iWand}{Never type it twice} + \partitem{\iFolder}{Files: SFTP and drag-and-drop} + \partitem{\iArrows}{Port forwards} + \partitem{\iFilm}{Recordings}} + +% ========================================================================================= +\chapter{Tabs and split panes}\label{ch:tabs} +\chapmeta{4 minutes}{You'll juggle a dozen sessions without losing any of them} + +\begin{tldr} +Right-click a tab for everything. \keys{\mod+Shift+D} splits right, \keys{\mod+Shift+E} splits down. +\keys{\mod+1} to \keys{\mod+9} jump straight to a tab. +\end{tldr} + +\section{Tabs} + +\begin{description} + \item[Move] Drag tabs about, or right-click\then\ui{Move Left} / \ui{Move Right}. + \item[Pin] Right-click\then\ui{Pin}. Pinned tabs stay at the front. + \item[Rename] Right-click\then\ui{Rename}. ``prod DB, DON'T TOUCH'' is a perfectly valid tab name. + \item[Duplicate] Opens another session to the same profile. + \item[Reconnect] Does what it says. + \item[Peek] Hover a tab for its name, host, state and when it connected. + \item[Close] The \ui{×}, \keys{\mod+W}, or right-click\then\ui{Close}. Connected tabs ask first (you can turn that + off in Settings). + \item[Switch] \keys{\mod+Tab} and \keys{\mod+Shift+Tab}, or \keys{\mod+1} to \keys{\mod+9}. +\end{description} + +Lots of tabs? Scroll the tab strip with your mouse wheel or trackpad. + +\section{Split panes} + +Want two sessions side by side? Split the focused pane right with \keys{\mod+Shift+D} or down with +\keys{\mod+Shift+E} (also in the \menu{Session} menu, the right-click menu and the palette). The new pane asks +what to open: \ui{Local Machine} or any of your terminal profiles. + +\shot{split.png}{A fresh split. Pick what goes in the new pane.} + +Drag a divider to resize. Click \ui{×} on a pane's header to close it and its neighbour takes the space. Closing +the tab closes every pane in it. + +\begin{quickwin} +Split right, open \code{htop} on one side and tail a log on the other. Now you look like a hacker in a film. +\end{quickwin} + +% ========================================================================================= +\chapter{Terminal superpowers}\label{ch:terminal} +\chapmeta{4 minutes}{You'll copy, search and scroll like you've used it for years} + +\begin{tldr} +Select to copy, \keys{\mod+F} to search, Ctrl/\ph{E1C4}-click links. Right-click for the rest. +\end{tldr} + +\section{Keyboard and mouse} + +\renewcommand\arraystretch{1.45} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\heavy\color{ink}}p{24mm}X@{}} + \toprule + Copy & Select text, then \keys{\mod+C}. Nothing selected? \keys{Ctrl+C} interrupts the program as usual.\\ + Paste & \keys{\mod+V}. Pastes are bracketed when the program asks for it, so editors won't auto-indent your paste + into a staircase.\\ + Select & Drag for a range, double-click a word, triple-click a line, \kbd{Shift}-click to extend.\\ + Links & Hold \kbd{Ctrl} (\kbd{\ph{E1C4}}) and click a URL to open it in your browser.\\ + Scroll & Mouse wheel, or \keys{Shift+PageUp}, \keys{Shift+PageDown}, \keys{Shift+Home}, \keys{Shift+End}.\\ + \bottomrule +\end{tabularx} + +\begin{sidequest}[Why does the scroll wheel move the cursor in less?] +Full-screen programs (\code{less}, \code{vim}, \code{tmux}, \code{byobu}) use the terminal's ``alternate screen'', where the +wheel sends arrow keys instead of scrolling. Programs with mouse support (\code{htop}, \code{vim} with +\code{mouse=a}, \code{tmux} mouse mode) get your clicks directly. That's normal terminal behaviour, not a bug. +\end{sidequest} + +\section{The right-click menu} + +\ui{Copy}, \ui{Paste}, \ui{Select All}, \ui{Find}, \ui{Send Snippet}, \ui{Port Forwards} (SSH), \ui{Split Right}, +\ui{Split Down}, \ui{Export Output} (saves the whole scrollback as a text file), \ui{Clear} and \ui{Reset}. + +\section{Find} + +\keys{\mod+F} opens the find bar. Toggle \ui{Aa} for case-sensitive and \ui{.*} for regular expressions. +\kbd{Enter} for the next match, \keys{Shift+Enter} for the previous one, \kbd{Esc} to close. It shows +``3 of 17'' so you know where you are. + +\section{When the connection drops} + +The pane dims and shows a \ui{Disconnected} card with \ui{Reconnect} and \ui{Close}. If auto-reconnect is on, +it says \ui{Reconnecting…} with a \ui{Cancel} button, and a toast tells you which attempt it's on. + +If the problem was a password or host key, you get a friendly dialog explaining what happened, with +\ui{Retry}, \ui{Edit Profile}, \ui{Known Hosts} (for host-key trouble) and \ui{Close}. + +% ========================================================================================= +\chapter{Never type it twice}\label{ch:repeat} +\chapmeta{6 minutes}{You'll stop retyping the same commands, and let Nexus Terminal watch the logs for you} + +\begin{tldr} +\textbf{Snippets} store commands. \textbf{Broadcast} types into many sessions at once. \textbf{Triggers} watch output and +tap you on the shoulder. +\end{tldr} + +\section{Snippets: \keys{\mod+Shift+S}} + +Save commands you use a lot, with a name, description and folder. Search them, click +\ui{Send to Session}, and the command is typed into the active session followed by \kbd{Enter}. + +\crop{snippets.png}{633}{384}{611}{402}{0.8\linewidth}{The snippet library, organised into folders.} + +Snippets can have blanks to fill in: + +\begin{itemize} + \item \code{\{\{hostname\}\}}, \code{\{\{date\}\}}, \code{\{\{time\}\}} and \code{\{\{timestamp\}\}} are filled in for you. + \item Anything else, like \code{\{\{service\}\}}, is asked for when you send it. +\end{itemize} + +So \code{journalctl -fu \{\{service\}\}} asks ``service?'' and tails whichever one you type. + +\section{Broadcast: \keys{\mod+Shift+B}} + +Turn broadcast on and everything you type goes to \emph{every} connected terminal. The status bar shows +\ui{BROADCAST} so you can't forget it's on. + +\shot{broadcast.png}{Broadcast is on: the status bar says so in no uncertain terms.} + +To broadcast to only some sessions, right-click the \iBroadcast{} button (or \menu{Session\then Broadcast +targets…}), tick the ones you want and click \ui{Apply \& Enable}. + +\begin{danger}[With great power…] +Broadcast doesn't care which tab you \emph{meant}. Double-check the target list before you type anything +destructive. \code{sudo reboot} into twenty servers is very efficient and very memorable. +\end{danger} + +\section{Triggers: let it watch for you} + +\menu{Session\then Trigger Rules} (or the palette). Each rule has a pattern (plain text or a regular +expression), a case-sensitivity switch, an on/off switch, and an action: + +\begin{center}\small +\begin{tabular}{@{}ll@{}} + \ui{Notify} & shows a toast\\ + \ui{Highlight tab} & briefly outlines the pane\\ + \ui{Notify + Highlight} & both\\ + \ui{Sound} & asks your OS for attention (a bouncing dock icon or flashing taskbar button) and shows a toast\\ +\end{tabular} +\end{center} + +\crop{triggers.png}{725}{610}{725}{625}{0.75\linewidth}{Three rules: shout about errors, ping when a build finishes, and one switched off for now.} + +Rules check each complete line of output, with colours stripped out first. + +\begin{quickwin} +Add a rule for \code{BUILD SUCCESSFUL} with the \ui{Sound} action, start a long build, and go and make a cup of tea. +Nexus Terminal will tell you when it's done. (Or \code{(?i)error|fatal} for the opposite.) +\end{quickwin} + +% ========================================================================================= +\chapter{Files: SFTP and drag-and-drop}\label{ch:files} +\chapmeta{4 minutes}{You'll move files to and from servers without leaving the terminal} + +\begin{tldr} +Click the \iFolder{} on an SSH tab. Or drop a file onto the terminal and choose \ui{Upload}. +\end{tldr} + +\section{The SFTP browser} + +Click the \iFolder{} folder icon on an SSH tab, \menu{Session\then SFTP browser}, or type ``sftp'' in the palette. +It opens in the side dock next to your terminal. + +\shot{sftp.png}{Browsing the server's files next to the terminal.} + +\begin{itemize} + \item Move around with the breadcrumb trail, the \code{..} entry or \ui{Go to path}. + \item Upload files; download one or many to a folder you choose. + \item Rename, delete (it asks first), make folders, refresh. + \item \iStar{} bookmarks the current folder, per profile. + \item Ctrl-click (\ph{E1C4}-click) or Shift-click to select several things. Transfers show progress as they go. +\end{itemize} + +\section{Drag and drop} + +Drop files onto a terminal and choose: + +\begin{description} + \item[Upload to current directory] Pick the remote folder (SSH only). + \item[Paste contents] Types the file's contents into the terminal, as if you'd typed it. +\end{description} + +The upload picker looks like the SFTP browser but shows folders only, labelled \proto[amber]{READ ONLY} if you can't +write there and \proto[rose]{NO READ} if you can't look inside. Keyboard fans: \kbd{↑}\kbd{↓} to move, \kbd{Enter} to +choose, \kbd{Backspace} to go up, \kbd{Esc} to cancel. If the SFTP browser is open, the picker starts in the same folder. + +% ========================================================================================= +\chapter{Port forwards}\label{ch:forwards} +\chapmeta{3 minutes}{You'll reach a database or web app that only listens on the server} + +\begin{tldr} +\emph{Local} forwards bring a remote port to your machine. \emph{Remote} forwards do the opposite. +\end{tldr} + +Forwards saved in an SSH profile (under \ui{Port Forwarding}, with \ui{Active} ticked) start when you connect. +To manage them on a live session, right-click the terminal\then\ui{Port Forwards}, or \menu{Session\then Port forwards}. + +\begin{description} + \item[Local \iArrow] Listen on your computer, forward to somewhere the server can reach. + Example: \code{127.0.0.1:15432}\then\code{127.0.0.1:5432} lets your desktop database app talk to Postgres on + the server as if it were local. + \item[Remote \ph{E058}] Ask the server to listen and send connections back to somewhere \emph{your} computer can reach. + Great for showing a colleague the dev server running on your laptop. +\end{description} + +Active forwards are listed with an \proto[accent]{L} or \proto[violet]{R} badge and a stop button. Toasts tell you when one +starts, stops or fails. + +% ========================================================================================= +\chapter{Recordings}\label{ch:recordings} +\chapmeta{2 minutes}{You'll have a replayable record of exactly what happened} + +\keys{\mod+Shift+R} starts and stops recording the active session. A red dot appears on the tab and \ui{REC} in +the status bar. Recordings are standard asciicast v2 files, so other tools (like \code{asciinema}) can play them too. + +\crop{recordings.png}{712}{392}{690}{415}{0.75\linewidth}{The recordings browser. Play, delete, or tidy up in bulk.} + +The browser (sidebar footer, \menu{Session} menu or palette) shows how many recordings you have and how much space +they take. Play or delete each one, or use \ui{Clean up} to remove recordings older than 7, 30 or 90 days, or to keep +the total under 1\,GB or 5\,GB. Playback has play/pause, restart, a seek bar and speed control. + +\begin{protip} +Record the next time you do something fiddly and rare (a database failover, a certificate rotation). Six months +later, ``how did we do that again?'' has an answer you can literally watch. +\end{protip} diff --git a/docs/guide/chapters/part4-safe.tex b/docs/guide/chapters/part4-safe.tex new file mode 100644 index 0000000..89597d2 --- /dev/null +++ b/docs/guide/chapters/part4-safe.tex @@ -0,0 +1,123 @@ +\guidepart{amber}{Stay safe}{Stay safe} + {Passwords, keys and host checks, minus the jargon headache.} + {\partitem{\iLock}{Passwords and the vault} + \partitem{\iShield}{Host keys: is it really that server?} + \partitem{\iKey}{SSH keys}} + +% ========================================================================================= +\chapter{Passwords and the vault}\label{ch:secrets} +\chapmeta{5 minutes}{You'll know exactly where your passwords live and who can read them} + +\begin{tldr} +Passwords go in your OS keychain. If there isn't one, they go in an encrypted vault protected by a master password. +For each password you choose: save it, remember it until you quit, or ask every time. +\end{tldr} + +\section{Where secrets live} + +\begin{center} +\begin{tikzpicture}[ + box/.style={rounded corners=6pt, fill=#1!12, draw=#1!60, line width=0.6pt, minimum height=13mm, text width=40mm, + align=center, font=\small}, + arr/.style={-latex, line width=0.8pt, color=muted}] + \node[box=accent] (need) {\heavy A session needs\\a password}; + \node[box=green, right=12mm of need] (kc) {\heavy OS keychain\\{\footnotesize Keychain · Credential Manager · Secret Service}}; + \node[box=amber, right=12mm of kc] (vault) {\heavy Encrypted vault\\{\footnotesize AES-256-GCM, Argon2id}}; + \node[box=rose, below=9mm of kc] (ask) {\heavy Ask you\\{\footnotesize then carry on}}; + \draw[arr] (need) -- (kc); + \draw[arr] (kc) -- node[above, font=\scriptsize]{not there?} (vault); + \draw[arr] (vault) |- node[pos=0.25, right, font=\scriptsize]{still not there?} (ask); +\end{tikzpicture} +\end{center} + +\begin{description} + \item[OS keychain first] macOS Keychain, Windows Credential Manager, or the Linux Secret Service (GNOME Keyring, + KWallet and friends). + \item[Encrypted vault as backup] If there's no keychain, or it refuses, secrets go into Nexus Terminal's own + vault: AES-256-GCM with a key derived from your master password by Argon2id using 128\,MiB of memory. In + plain English: very, very expensive to brute-force. +\end{description} + +\section{Setting up the vault} + +\menu{Settings\then Security\then Initialise Secret Store}. Choose a master password; the strength meter goes from +\proto[rose]{Weak} through \proto[amber]{Fair} and \proto[accent]{Good} to \proto[green]{Strong}. Unlock and lock it +from the same page, from the palette, or by clicking the vault badge in the status bar. It locks itself when you quit. + +\crop{settings-security.png}{581}{253}{576}{285}{0.8\linewidth}{Security settings on a machine without an OS keychain: the vault does the job instead.} + +\begin{danger}[There's no ``forgot password'' link] +Lose the master password and the secrets in the vault are gone for good. That's the point of encryption. Put it in +your password manager. +\end{danger} + +\section{Three ways to handle each password} + +Every password, passphrase, token or TOTP secret has its own setting: + +\begin{center} +\begin{tabular}{@{}p{0.3\linewidth}p{0.3\linewidth}p{0.3\linewidth}@{}} + \begin{tcolorbox}[colback=green!8, boxrule=0pt, arc=6pt, fontupper=\small, height=33mm] + {\heavy\color{green!60!black}\iLock\ Save securely}\par\smallskip Stored in the keychain or vault and used automatically. Most convenient. + \end{tcolorbox} & + \begin{tcolorbox}[colback=accent!8, boxrule=0pt, arc=6pt, fontupper=\small, height=33mm] + {\heavy\color{accent!80!black}\iClock\ Remember for this session}\par\smallskip Asked once, kept in memory, never written to disk. + \end{tcolorbox} & + \begin{tcolorbox}[colback=amber!10, boxrule=0pt, arc=6pt, fontupper=\small, height=33mm] + {\heavy\color{amber!75!black}\iDetective\ Ask every time}\par\smallskip Asked on every single connection. Most paranoid. + \end{tcolorbox} +\end{tabular} +\end{center} + +When Nexus Terminal needs a secret it doesn't have, it asks. Press \kbd{Esc} or \ui{Cancel} to give up on that +connection. + +\begin{sidequest}[What gets written down?] +Connections, disconnections, profile changes, credential access, imports and exports go into an audit log in the +database. Secrets themselves never do. TOTP secrets live in the keychain or vault too, never in the profile. +\end{sidequest} + +% ========================================================================================= +\chapter{Host keys: is it really that server?}\label{ch:hostkeys} +\chapmeta{3 minutes}{You'll understand the scary fingerprint dialog and what to do about it} + +\begin{tldr} +Every SSH server has a fingerprint. Nexus Terminal remembers it the first time and shouts if it ever changes. +A changed fingerprint means a reinstalled server, or someone in the middle. Find out which before carrying on. +\end{tldr} + +Pick a policy per SSH profile under \ui{Host Verification}: + +\renewcommand\arraystretch{1.5} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\heavy\color{ink}}p{48mm}X@{}} + \toprule + Strict (known hosts only) & Only connects to servers you've already trusted.\\ + Ask on new or changed keys & Shows the fingerprint and asks. \ui{Accept \& Save} or \ui{Reject}. The default.\\ + Accept new, reject changed & Trusts first-time servers silently, still refuses changed keys.\\ + Insecure (accept anything) & Exactly what it says. Lab use only.\\ + \bottomrule +\end{tabularx} + +Under every policy except \emph{Insecure}, a key that differs from the saved one is rejected. + +\crop{settings-knownhosts.png}{581}{253}{576}{285}{0.8\linewidth}{Known hosts. Click a fingerprint to copy it; \ui{Remove} to forget a server.} + +\begin{headsup} +Nexus Terminal keeps its own list of known hosts. It doesn't read \code{\textasciitilde/.ssh/known\_hosts}, so the +first connection to each server asks once, even if your command-line \code{ssh} already knows it. +\end{headsup} + +% ========================================================================================= +\chapter{SSH keys}\label{ch:keys} +\chapmeta{2 minutes}{You'll have a key pair without memorising ssh-keygen flags} + +\menu{Session\then SSH key manager} (or the palette) lists the key pairs in \code{\textasciitilde/.ssh} with their type. Copy a +public key to the clipboard (ready to paste into GitHub or \code{authorized\_keys}), delete a pair, or generate a new +\proto[teal]{Ed25519}, \proto[accent]{RSA} (2048 or 4096 bits) or \proto[violet]{ECDSA} key with an optional passphrase. + +\crop{keys.png}{725}{523}{725}{546}{0.75\linewidth}{The SSH key manager.} + +\begin{protip} +Not sure which type? Pick Ed25519. It's small, fast and secure. Key generation uses your system's +\code{ssh-keygen}, which comes with macOS, Linux and Windows 10 and later. +\end{protip} diff --git a/docs/guide/chapters/part5-yours.tex b/docs/guide/chapters/part5-yours.tex new file mode 100644 index 0000000..07511c6 --- /dev/null +++ b/docs/guide/chapters/part5-yours.tex @@ -0,0 +1,107 @@ +\guidepart{pink}{Make it yours}{Make it yours} + {Settings, backups, and moving in from Alchementary Link.} + {\partitem{\iGear}{Settings} + \partitem{\iUpload}{Import and export} + \partitem{\iPlane}{Moving house}} + +% ========================================================================================= +\chapter{Settings}\label{ch:settings} +\chapmeta{3 minutes}{You'll know where every setting lives} + +\keys{\mod+,} opens settings (also the \iGear{} in the sidebar footer and \menu{File\then Settings}). Change things, +then click \ui{Save}. + +\begin{center}\begin{minipage}[t]{0.49\linewidth}\crop{settings-general.png}{581}{253}{576}{285}{\linewidth}{General}\end{minipage}\hfill\begin{minipage}[t]{0.49\linewidth}\crop{settings-terminal.png}{581}{253}{576}{285}{\linewidth}{Terminal}\end{minipage}\end{center} + +\begin{description} + \item[General] App theme (Pro, Midnight, Light), default protocol for new profiles, whether to ask before closing or + disconnecting, status bar on or off, sidebar width, and the local shell (leave it empty for the system default, + or \ui{Browse…} for one). + \item[Terminal] Colour scheme, font, size, scrollback and cursor style. These are the defaults for new profiles; + each profile can override them. + \item[Security] Keychain and vault status; set up, unlock or lock the vault. + \item[Known Hosts] Saved server fingerprints, with first-seen and last-seen times. + \item[Help] Built-in help: connection types, shortcuts and troubleshooting, when this guide isn't to hand. + \item[About] Version, \ui{Check for Updates} and \ui{Import \textasciitilde/.ssh/config}. +\end{description} + +\crop{settings-help.png}{581}{253}{576}{285}{0.8\linewidth}{The built-in help, for when this guide isn't to hand.} + +\section{Updates} + +Nexus Terminal checks once at launch. If there's a newer version the status bar says \ui{Update available}; click it to +see the release notes and a \ui{View release} button. Nothing downloads or installs behind your back. + +% ========================================================================================= +\chapter{Import and export}\label{ch:transfer} +\chapmeta{3 minutes}{You'll back up your profiles, or copy them to another computer} + +\begin{tldr} +\menu{File\then Export profiles…} makes a password-protected \code{.nexus} file. \menu{File\then Import profiles…} +reads it back. Already have an \code{\textasciitilde/.ssh/config}? Import that too. +\end{tldr} + +\begin{description} + \item[Encrypted export] Choose the profiles, set a password, save a \code{.nexus} file. Saved passwords aren't + included, so the file is safe to email to yourself. + \item[Encrypted import] Pick a \code{.nexus} (or an old \code{.link}) file, enter its password and decide what happens to + profiles that already exist: \ui{Skip existing}, \ui{Overwrite existing} or \ui{Create duplicates}. + \item[JSON] \ui{Export Profiles (JSON)} and \ui{Import Profiles (JSON)} are in the palette. Tick + \ui{Include secrets} to keep proxy passwords and TOTP secrets, which are otherwise left out. + \item[OpenSSH config] \menu{File\then Import \textasciitilde/.ssh/config} turns your \code{Host} blocks into profiles, + with host names, users, ports, identity files and jump hosts. +\end{description} + +\crop{export.png}{894}{357}{872}{380}{0.55\linewidth}{Exporting profiles. Everything's ticked to start with; untick what you don't want.} + +\begin{danger}[JSON with secrets is not encrypted] +A JSON export with \ui{Include secrets} ticked has those secrets in plain text. Use the encrypted \code{.nexus} export to +move things between machines. +\end{danger} + +% ========================================================================================= +\chapter{Moving house}\label{ch:move} +\chapmeta{2 minutes}{You'll know what came over from Alchementary Link and what didn't} + +\begin{tldr} +It's automatic. First run copies your Alchementary Link profiles, vault and recordings. Your old folder is left +alone. Recreate trigger rules and SFTP bookmarks by hand. +\end{tldr} + +When Nexus Terminal starts for the first time and finds an \code{AlchementaryLink} folder next to where its own data +goes, it \emph{copies} \code{link.db} to \code{nexus.db}, along with the \code{vault/} and \code{recordings/} folders, +and shows a toast saying where they came from. + +\begin{checklist} + \item[\color{green}\iCheck] Profiles, groups, favourites, known hosts, snippets and preferences + \item[\color{green}\iCheck] Vault (same master password as before) + \item[\color{green}\iCheck] Recordings + \item[\color{green}\iCheck] Keychain passwords: picked up the first time each one is needed, then copied across + \item[\color{green}\iCheck] Old \code{.link} export files still import + \item[\color{rose}\ph{E4F6}] Trigger rules and SFTP bookmarks (they lived in the old app's web storage) +\end{checklist} + +\begin{headsup} +If a \code{NexusTerminal/nexus.db} already exists (say, from before the Alchementary Link rebrand), it's used as it is +and nothing is copied. +\end{headsup} + +\section{Where everything lives} + +\renewcommand\arraystretch{1.45} +\begin{tabularx}{\linewidth}{@{}>{\leavevmode\heavy\color{ink}}p{20mm}X@{}} + \toprule + macOS & \code{\textasciitilde/Library/Application Support/NexusTerminal}\\ + Windows & \code{\%APPDATA\%\textbackslash NexusTerminal}\\ + Linux & \code{\$XDG\_CONFIG\_HOME/NexusTerminal}, usually \code{\textasciitilde/.config/NexusTerminal}\\ + \bottomrule +\end{tabularx} + +Inside: \code{nexus.db} (profiles, known hosts, preferences, snippets, IRC state and the audit log), +\code{vault/vault.json}, \code{recordings/}, \code{triggers.json} and \code{sftp-bookmarks.json}. Keychain entries are +stored under the service name \code{NexusTerminal}. + +\begin{protip}[Try things without touching your real data] +Start Nexus Terminal with \code{--data-dir /some/other/folder} and it uses that folder instead. Brilliant for +experiments, demos and a portable install on a USB stick. +\end{protip} diff --git a/docs/guide/chapters/part6-fix.tex b/docs/guide/chapters/part6-fix.tex new file mode 100644 index 0000000..cc0bda0 --- /dev/null +++ b/docs/guide/chapters/part6-fix.tex @@ -0,0 +1,88 @@ +\guidepart{rose}{Fix it}{When things go wrong} + {Symptom first, fix second. No essays.} + {\partitem{\iLifebuoy}{Troubleshooting, by symptom}} +\label{part:fix} + +% ========================================================================================= +\chapter{Troubleshooting, by symptom}\label{ch:trouble} +\chapmeta{as long as it takes}{You'll find your symptom and the fix in one place} + +\begin{tldr} +Find the heading that matches what you're seeing. Try the fixes in order. Most problems are the network, the +password, or the baud rate. +\end{tldr} + +\section{Connecting} + +\fix{``Authentication failed''}{% +\begin{itemize} + \item Check the username, password, key path and the ticked \ui{Auth Methods}. + \item If the key has a passphrase, check how the passphrase is stored (\ui{Passphrase Mode}). + \item Two-factor server? Click \ui{Test} next to the TOTP secret and compare with your phone. Check your clock. + \item Saved password not being used? The vault might be locked: click \ui{Vault locked} in the status bar. +\end{itemize}} + +\fix{``Host key verification failed''}{% +The server's fingerprint changed since you last connected. That happens when a server is rebuilt, but it's also exactly +what an attack looks like. \textbf{Check with whoever runs the server.} If it's legitimate: +\menu{Settings\then Known Hosts}, \ui{Remove} the entry, reconnect and accept the new key.} + +\fix{It just says ``Connecting…'' forever}{% +\begin{itemize} + \item Click \ui{Test} in the profile editor. If that fails too, it's the network, a firewall or the wrong port. + \item Behind a corporate proxy? Set one up under \ui{Proxy} (SSH profiles). + \item Going through a bastion? Check each jump host on its own first. +\end{itemize}} + +\fix{The session keeps reconnecting}{% +Check the network and the credentials, then the profile's auto-reconnect settings. Reconnection backs off (1\,s, 2\,s, +4\,s… up to 30\,s) and stops after the maximum number of attempts.} + +\section{Serial} + +\fix{The port won't open}{% +\begin{itemize} + \item Is the adapter plugged in? Hit refresh in the device list. + \item \textbf{Permission denied} on Linux: add yourself to \code{dialout} (or \code{uucp}), then log out and back in. + \item On a Mac, use \code{/dev/cu.*}, not \code{/dev/tty.*}. + \item \textbf{Port is in use by another program}: close \code{screen}, \code{minicom}, the Arduino IDE serial monitor or + whatever else has it open. +\end{itemize}} + +\fix{Gibberish on screen}{The baud rate, parity or stop bits don't match the device. Check its manual.} + +\section{Remote desktops} + +\fix{VNC won't connect}{% +\begin{itemize} + \item VNC ports are 5900 plus the display number: \code{:0} is 5900, \code{:1} is 5901. + \item Check the password and how it's stored. + \item Using an SSH tunnel? Check the tunnel's host, user, login method and key, and the host key policy. + \item Connecting to a Mac? Turn on password access in macOS Screen Sharing settings (see p.\,\pageref{ch:vnc}). +\end{itemize}} + +\section{Files} + +\fix{Upload is greyed out or fails}{% +The folder is probably \proto[amber]{READ ONLY} for your user. Pick another, or check permissions on the server. Uploads +need an SSH session; other protocols can't do it.} + +\fix{``NO READ'' on a folder}{You don't have permission to look inside. Try the parent folder, or ask the server's admin.} + +\section{The terminal itself} + +\fix{The terminal is the wrong size}{% +Resize the window or nudge a split divider; the terminal re-fits itself. Still wrong? Reconnect. Very old servers may +prefer a different \ui{Terminal Type} in the profile (try \code{vt100}).} + +\fix{The scroll wheel moves the cursor instead of scrolling}{% +You're in a full-screen program (\code{less}, \code{vim}, \code{tmux}). That's how terminals work: the wheel sends arrow keys +there. Quit the program, or use its own scrolling (in \code{tmux}: \keys{Ctrl+B} then \kbd{[}).} + +\fix{Emoji are in black and white}{Known limitation: colour emoji show as monochrome glyphs. Everything else renders +normally.} + +\begin{quickwin}[Still stuck?] +Start Nexus Terminal with \code{--data-dir} pointing at an empty folder and try the same connection there. If it +works, something in your profile is off; duplicate the working one and compare. +\end{quickwin} diff --git a/docs/guide/fonts/Phosphor-Fill.ttf b/docs/guide/fonts/Phosphor-Fill.ttf new file mode 100644 index 0000000000000000000000000000000000000000..b02c4cb241bf88c7fc7b43af2f34e0f651c864fa GIT binary patch literal 449252 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 qc1!k4pu*0fnZO7&i>6 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%0A_oI)(GY<;d`rcgnPsT8c&D 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%0A~`sc!AR78tRZ(qAcIIgw#dAaQ&0dru5 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

%GjXSUmSe1N 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 literal 0 HcmV?d00001 diff --git a/docs/guide/img/broadcast.png b/docs/guide/img/broadcast.png new file mode 100644 index 0000000000000000000000000000000000000000..d6b517f01839fbb1993e52f4e9e09c446a6f6575 GIT binary patch literal 124648 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 literal 0 HcmV?d00001 diff --git a/docs/guide/img/editor-github.png b/docs/guide/img/editor-github.png new file mode 100644 index 0000000000000000000000000000000000000000..6ab162c2d3f9b03ac8c05f4e943f39296e674a4c GIT binary patch literal 132507 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�OlF~583nL<%2DT?w?xA1)A>v;<`!O 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
JQ+wzVf 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 literal 0 HcmV?d00001 diff --git a/docs/guide/img/editor-irc.png b/docs/guide/img/editor-irc.png new file mode 100644 index 0000000000000000000000000000000000000000..728dedfbeda20464e3f19f7962e77a745626c2ff GIT binary patch literal 127211 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

c`| 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%0Ay2KK=@o?^y{LEBPo> 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 QKRAW1mXT($`u(u~1w*h63;+NC literal 0 HcmV?d00001 diff --git a/docs/guide/img/editor-raw.png b/docs/guide/img/editor-raw.png new file mode 100644 index 0000000000000000000000000000000000000000..69ea6691f7d9a0e9dffd03bb9a307c5e53b21cec GIT binary patch literal 127697 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 N8)+q}GKsf-{|hT@G6?_x literal 0 HcmV?d00001 diff --git a/docs/guide/img/editor-serial.png b/docs/guide/img/editor-serial.png new file mode 100644 index 0000000000000000000000000000000000000000..0768672036e1bdc706d5a53af0603fe163cd7aad GIT binary patch literal 127890 zcmcG0XH=6-*KSlgA_xKs(tGc{2BfzDp*IBq2_Vut!BCXmL3-!`1f+>{ktR(yg>C`>-hu!C>Nx-){Q&@=ev{LpFAD(N z%>ZeGRoK}%d3gB6#ifA2XDTWh>gwA3{DLAP;>^sf^z@8WRGg0} zAW6q4&cq}|BmRs^*T7hvN0Ub{DCjL6ixeA|XnA=p2M0%UbNj-=a#vSxadBl%PEk%C zLL&Zv-zFDU!);5B{<}NOt?(W|D`bGr>C9$!|a&o}v=(v%QvDdHtEG=!tyxV(w2h7Z@!o#C^ zRm>Y3o1L6o#bZ!Ux#T3m=eZ;_M75j-2S?t%4JUWAzaQl#>0T-t@{9h3wWMR>lP9z+ z;<{7Q3*6kwVp3pXQSH*QPl9R&z28TD-zL3J%2U&GYH03yZECKTW*E-!`a5f79sb8c;RlcZ`$uDgcTraY^v=`Pqx*g)8(H zsa3`Ur|Yy6UHDh4R3<-ZiE^=Sep1yD=IYN>nkZNMk-)d{Nww;|w3{IZuc3jskB4Gb zszz%)yNrwyJWFS|TNvU<&%~nH`9&@%_E|-dR(A`(xv6Shr4%fLnwmjcR60{l1E)3` z(3@tat$6AT@s<>qmbcXc$67gl_6*&24_SBg_z@U&>l=QetZE)6Ct9T`HwM=EqOH4W zrMYIQ5vL^xv`>s|IlaBT^{+qu`14XdC45$>r_T=1s})#fP4A^TMN{7Ruh&Lc>{#{O=aLoLucayK(NQ(&n z+emG7E-!YC0W_Wg!G5zl=qNkxnc%WGLq*C|+Vgc86u%Yg!nD*Q^%ixdyR5A569o>L zXHtf}1fSw%9ZcC8oUMdEWMKfY5FsZfOCa#0ccYn|%}{cT75y#cUkDO3jz`h`xp z3FVs05BOs(Z=d09!-9P(h)-ETCe~1N$9--bO02K|kRZu|(miovyK3=}{xLMqC&C^YX12KtZyTP!2Fr#; zF&QxGdypa1?g9Vy+w76fun=x?FYk}Tdjvoo7zrhEw||08_TmlzK+jrQZsGW~^hK5k zbcd+bdTP8~?$W_oqNoU?rI$mP_tJ&Css!is)TS`Vm_I1K1GDz5ItqcL;`~<9KjFAP z32)u)=RIQlQh2Y$Pu*v$^E!NZ+)dh1cEKrm{m0n+5wC6&S%3IbTrH0929HZ~vAzC0 z&A27Ju7^wy6{gCHn|Cy@=Dkx&RN+18v!GiPgTwP7+ZhI#$ zp?3RAOO2+zKM-9q)vHvBlNQa={Gokr88%xa(A&dR}2?TVKdhF|;7w<-3J%)V4sj44N2(6bO&j(C$`|TRv{rQvr8L~v9LR>MmiH2TPJ+pgLblMb2)V@54PC=6nPe2NcNcc0mA`@2xB{M~nC?b(Y!4!3 zH~GPV*a*LWnJvx~ct~oI{u&Vn%}Df@4p{2mVZU`G0|P>-3UM3%#J6>!FXLs{_9ea8 zh;>gpg@LXbLUKN%2GaN1Z7MH0xw!}+W7h^TF6A|Oj7lJ#7H9j9v8J%WO+PE?`YhSC zX9Ot5C%_UF{76moiN_yUAI|AohE28&2%0AF(Gt|eqm^&h3ky)BAM%|d_O>)lb$GMY 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 P8&g))cvAVu>eYV%Lt87g literal 0 HcmV?d00001 diff --git a/docs/guide/img/editor-ssh.png b/docs/guide/img/editor-ssh.png new file mode 100644 index 0000000000000000000000000000000000000000..ae4db85a2c35019f5dece8ff23f0c99ff8820f5e GIT binary patch literal 138594 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 F)ZHNZ}pA^LUI+#>rMt!^r3;j%AuO)s9j z@lEc;Fm>}j3%picDak&i6Qa#C*iSv|b~-^esgAm=LX7s_C~Z0L?t(J+DpqanE$sj( zXtbHO(x)(GXlq4|9hA!Zr-EX`W}k~irl}9D6fpAadSM-XN11n#U52tO8IAI%&z^9f z^N!TMiB)u{K7xSbCG-` zNvIEWFSL7>w<9$}49YL-CUura?|Uve`fYwCm~}=la3FWn_M4i|*FAP8B^-U9`zxIj z(hewM@qV5P0$(-NB%)>XmZWaJCXNAGCP-iOvCBhz1^QNYyFZ#D>%QWgMndr`S@x&i zWN4u7`;5_`OjTDK!U=(yK9922M?yTz-|X#~3^nN1z<2)pP?ZxII(l#!Qn|Jfw)h7J zGb4!Ih$SpKTO%*~fl(pXYgK0%QdBiILmuYjNTQ+YziEA57@<>j8E(6dX#X!63RTG2@ zUoJgaenW{uyZnE#UXl))x`UggR9;YH|9s*iKIs4=y-{=t^a{Vn(2$)Z6;P|%-U?E# zMeRil?>^Ld4^6qv*G{tAy=`5X5c))?%IBNsRjOqs?$mX4so*^!3Z+IZFF-I!2lGFoCOtRCgaJH;Q_k1@L^Su8pw31^C5*ir z@eTU*?rrJzqfd=?N?IeXGFxC>y#vk`ValTiidN7Sf|=g=Sr!1|WpWtunW5**l<+CP zi#y<__r@k5IwzZj2q)n_u!f^!;fa>fRPAK`tRNovotaK>tLb#oyP!XJiU1=qMO$+{ z4m+c}UwzuXgC3{G$frsNrNN273A9{fOLa7`VC*rpb?+9lXsPTQy9FZ}KFMoRm-dK$ zc-()0bA^8qi=a73h5ZIV8GxXf^xm*PcBFg@sD$u$lKbQ2^7DbDF*5b!<&NJM&$6WN zd+=q;Fh;)wJ1OG7+hzm`%0lPS{7=!eiuPr#6eDu#6kL{Iku^z$hHGO zUGMDIue)c}Q{Ee=O&EZH*+}VaK34~4ZO7Db0~C!}wvxMLBWGIMTGEzSlZM(Jl^^l< zCpp|miKk@`haxFawoUoDi3Aw<+H!6J^`%EDz$NS$X@*AedZ)I%bPUP|sgptehFvN~ zPu1dI=Bq5sHXCr{@(cX>(7y->Yh{n^O601?s zijGZ~pYBskLqCSG@5;p*bDyWYR~qV!YZv!Hi4tC`(k>>o z2kBWP{gA4Q7e}9`8Bq_-mAnS`T6R@&n;zbQ^G>?<*ycCa%GXVjH7P~eZJyZ5+X;Ma zfFpi>sdL`$SKMHGJ`3tI;N6(I=p4GNi8se|@kz#gLI&Us_hI1qDW_IOPSFq7$mo3B z%6mBBDz)$q18bW9rss4l(mHUmXC>dAD)2JZs=slv=PEB=xn>f!RyM?;dlT8ev&0v7 zT733Q-(mzvP$h&P-LwukKH`HZYzQruxApbq(TCa_s7bV%et@I3tm9N!{wm4L<9dH@c})AHN(;#&XA`B%AnnQ>ST-wE7CPyT1Jpj5vM%{h3Crc93A_cZAll+ z4qba|El-A#AN21AJWnRD>Tu&{`+^b3rdYgebshChoVi6abpoUa8dyvl8+G8Ya?8B9 z0yg|7_m!Pka{~hbtI%`cNfT^s!;2L2kaW(`%NNrkVnhg=yw5261rAJ{(>YI^D-s>7 zz#{)A*((S7iAay%HqOC3fF=dnC19x+R2g7S;WwxOt`AQgXjY}`tQ0$;wdXa@c)$vQ z?qHiHT*vL#AeO;ez%E$9;b!tbU>_Y|!`C%8zwws%!5dIT z@V~Fp3|i@>e277$dPuu{CO~IB-!C@PqHVaQ&P*^#Wm0pM{7tzf>xY+;lFy6uit-Jr=a4$suKiBzBK;*xaT`T;HMphs{^H25@ zYuN;n!+$6h|J^y`AA8@|f8|{}*al32#wdO~> zUy^ArFqNfsY2q>LLjCWUU*TajYOIaxrfn+4)hn3Se~LQelLBoc_1x6-TyM*@__0t6 ziGK;)3UYU<<;=u|e8Zhw+HWN8)Wz$0{#Ze&j8o&S_iRYL;=o!WfqGq%J!^N|lbU$- z3}=q?Aat z=22o95(RZ_KZ943I^13gGen)Oh}zrsI|)mJo{qxWjW=ZmlBN{7y(V_wlC&DRo zMF5ckNdm$k0Hf>_V)(Y-VD{Yhv=Q_eYcq#?-9FAvEvErOEsLp7^7CLWoDW#)c!)P4 z>?cv|`bc#AO{B$LMCiGB`hW|ln=XpJV|bo4lB-v69o!L5y}|;~pDOniped2>VHqp# zYiVVt6h2bD@b{_z`O4&FUvM^cMT^Pki?w z>h+QUc!#2{D?=Oq8VODp&^|9hFcBQ{CV12?8K&(1YS- zV6pq^YyFeW-%gzV=PiT&DqT%jIzj92AFmLpx!pxJW;joZK!IQtR0^JOPZ!Mkt*X{^ zAzR*8c&NuXUl3U+)hz9h;}Q z5lJtsD@8;I5yHcD)w)Q^qJK*llI3+Ewj;ToWwr`f%P4@&_Cn3ha0MUEJy2!<1r|l} zdvxkh*VfLLluf|X_3_Vq3OF8ZwAsFPYVbOA@4F@RS*J8V^7p5I>JYt1j+mBajV{%8 z#7{;-wCs}{7Nieod+E}eMi5v22^F^w;gQftauiz%AZFsV>w5xj7q;U}ghJwS)h=q_ zm#Q2t=J&|j2i5Vym9@>TT-c*i)8|vU(%t7x3sUrB8baQI zSmxANR%<11Du$)Foj$#1HHW5%LiSMM>d4Ndz>!Av;Lp>v8T7Jzc=SZ&n(XJB6YS3} zDXx0jdq*~YuH0)0t116BLdVrT(J*;;1sGXr;1#$+kCf35BzN#!`^EG!4h%W#;a=5y?`?|Nf7~f0RQMU(wYoR4eEbO( zH+Yf9@A|5~<*KkEB}S9pF}IBR@?>`KQqGs{Eeyfo!JzM$W?jYlsK#z&RYx;1u9!Ti z>0c~i-`5-8lPHKC*AtpSF`yYJ?wMWkxdjK3Nk8T#F{$&NJhG6um@s=sFV_AloG;1XC4wyietG_Q-rjdvS@R@*?kyrz#_cfs$!pDAy`rTw z8`ZC4kyIj_0kzcbueZtxuTY}GKhGTX?z>ZQA%~88`|(wFbW*qAqft!E1+2Dhjat>@ zW;fe8E}V4PG#PRM=a)Pl{r$3hBB%}RfnPt2@ltj;rbRuy8+1d3??_?ngT%w1FLfLY z-_0lxqWhx@f_U$_g=XJ^(`l=Te8${-mpf*zN~0?c~mLgDTY?6oO`Pvov{;7R6*%*xGQ0(`6DpU$%i)+iwt@TeWcksq0A&EeFI7(L}KUuZ&#J zRqn&{_92B_0lG4R&dGJgP0Z@`h&(R^j5wx^J2$y#<6R=*kzcOSTtHDKfpbs3b6v3+ z2Pd+q<6~Z6E?2dlvmxduMp4bP6c0Xrk?Hsw=nsW<3cg|VG)Str>Q?iK_?+Q&7b0ft zPV2wdMtCXXoyHIR&iu?AGzo`@9y2syag4aJuShce#zgOC+ir-J1zi%rn<&PjI`KZ_ z+nJ;dt}9YL&Iu%`ejrobydgKJA~tvT60i+E&B19d2h(6qX$xPo>o1XTJ$0wgqdA7*bmNkQ^YwsV^lv!DT5 z2k(?EX7Pk}C*!#H156@Q`hRT%pVzcfgW09u-)#oHSu!olt_7L?E7mGldX@Q=&bH2l1#+UiSf5B>B|wc5#~*{6jOcnl(_{OT+`Jj zQ#56}$til7i;P--5$$i-o7{->&ZnWjrpu?h^lDcj`l=73F_=nfs;7w;xtDe|D3Mrf z4=j#M+OwKV%ak8}D7o#1(i)0LOWDkF*|XCCvZ9X{JEUy&C& z@nZ=^1P|dxFUZ&pToPJ@`MkcFGyJJi1G^WD>cXweTI35MfkE`qBDIJ-t9JEKo{W*T#BNL% zLd8aW@aH65$(PM5X*5m-z)K%;me6yR%?Tw6X$bAia}&~x9YU`JaWq}o^SHdh_6`wD z5;veV?t@zy@Uo40fEjylLC)(*a2GCMr?kf@D?t zuatO>-k#%qjHZor&LJ4%0SafAaD0G}vORcu%Hs-QeEg6TK^5yxF>2CVM8$%97nfPlnq=fX zHt5SggNyNLSz9M<_~bpsCucYfd?&xg>j&e1!V$bw7pv-@iY92{!Kqrh>QHscm`C>f zA++b28IEw{O>z`|q?M}^IZ@aXg0->ddP%*_VHFvy>da$$mqqxm})5YNLY2`l!f{SZrUifKQYg4znyho`i6;e9&_pM z!c(Wq2Vy`t5ub5$44M4p>jg&#lQ2@D&P;8WB0=i&@_!W?R~I0KY2iXf8sSg9-yn)q zDWCoL^#HmN8!tS^fCCrch~Ilnj*=Sm>Omx`s(x}R`CgTGVy3U%%35gj!uQC^_M7VR zsl8KfmXlkOV{z^6EaE>`c?*o4qHMb|=%Jb=l*AHudh3C@zvKNq6_C(qbd&gPCo(%F zmyD36cd9d%b6k>cU^{yU_bQ|Jwg6kri!{f@`3o<{sM{^K|C_WX)92gw#U4V9E#>S! zG{ZZBd5MbR0{90{CelOw!wcSYQ_zfB$PRj+eXY1Nm^`)F^LuFja|i!kXlK;z{N8V0 z=Pq6(@2=#@yj~6x4v{Imsz8KRfu=Iv!N(lRQwnYgxD{mtZHz~;Xh2i0Rgckm$?fmh z%uI!bEeqkgUi#PHQ~>;K%xBvMO=p;5zr#vBw=wgM}6MMnBPAUwD!YRzyuHEq`$ zOm_2Ws$`#k`)bI2?Y}OE1V>H_iN64u{CrHXUEYQm6L>=37rG43TV2v8Z3p1|%p!dS znw(Q~WyPn1`_iA8*^v=X_cw3fweAwKz|&*nC{|1s)`phb%@_zU+NI>VSLIIefP)oi z3@N01frk7jgGDH-D`kz}CFOZx1Ub!_3OvZUCZ;=C6RSM)>k3-O^D+Dix+oR$Y}w%4 zUZrN$vFuIt57pZ|UiPlvAK+IiD{&-4Cic@_E2DIo$ec-O$#v&H=A~s)>2gG9D$=m` z81@IM)oFj$TrM|SNlY&}lU#&C~RH{t+*x!(3Z%bdQI&#qeYaaD44ykHQX`UXU327pDSlCy`gR2NnS7ncmEl{t7<4BXn} ze{!VxJFSza6nef?=T)NjkK8E$g(w&$&f~W!}0R3;RHa_b4&{|HH&g;xbZYze1cI)N8vszvIqZVFV95F z-^b&y0q_uhA@vKs$A&Ww(5u;7gcWG8Bb%Py&Ty3P$nAY`=X=i-`8$65AK%@KmYGZ1 zSkf0D^OSStp-u0*0y^A><|L64&TsE>CarU-{;ZXx2f?XY&gC$r%lboP-2q-~4$Xxn zYYo3%>8ZaVC4+GcNh>JNiLPS=iKBg!nrI%CdPmeQoKXSv`j#IWbtcZ2O_h6peu zu)^?+oKMTUSsXkBRY``fzChStEL;R?lqdK-?DB3 z5jonTJFJc(mNV|Sv?#oXz)^({ECy9$qzqGya23z|;O$=7 z#8sPEX0T6|I!?^`fpx9Z0f|16ivQe$ZxA!;`-Mw+;`~UYXE$_bnX-JgIlrrk88AFlUhRq_6@a|adBNgf%xdduU-I=J~!) zg$TwKbX*B<9pV~%QaS+Wj`85m^C{ZDJRu@B>23L`C{QA%Isf2wit8;T9&O51BXdaQ z07Y<;S8iFKe;FIWY(BZS38CYJz8vcW@efR~f5Fw6;=YWz2fxE_q5QSsDk*W1y;X6|S2-@tB|m}`0xo%s`pIierXLK?)k!OwdV44$4~!Fiv}?}} zpT0PM>wnENLIr6mXn6=5JhR@<2&L9FLmI2s$cdG2o9Mvv)O_X6yl@4cehT%+emOuA z5ysr4z}c4LD-vro?-nG|?uKQuA)CT1)np>niw(Vvd2QCDw5oksc9PxPhggT)tEhDA z4t7Ufi=*sKpleCS@t6+e{4L^a&FvvZNTq5zKP-1CLl`PAPA*I z9p`{jC*!#1ilo%kCXKK5w;ZpCFyUS4&`0_cG}`VPKMn=h%z4zI;eLlSiq@K%0kp^7 z7L#)Ih{03V%T;@O2C0%ee8mgbZh+A%yk&Nn*4(={VdK$fU_F?B{^59C!_Wv@&(FaJsp7++^-o{*fUlr_`&YM3Tcm=j>#N`Rv14>sLHX z^T$d0`&d1Qp_EqF-=~2MxqmralE^Z+j74PNH}k+^MS;e~)nrcuQC`|k#67Q>SI6e? zJfq!=gLEIrDfvuU&DiN){knxg^hOvBi9^e5n6u)q zfL&qd1#<|EZo@`?nts7l6!^s>`=i>lBT7wLN>tL4lsyKlRLY;zlZ>F&blAJ_hQ80z zusqJjFqa5c#&*_<1MfIob3DVG`IFFQ!M%D$PfoAE(mAw1jqe^Ea2dkT9_gel)fUdl zFe#l85=lW$6<4Cokojkn=&n=HXZK}Y;<=jZ$5z1?l&E+Io2ff5g!aSWs!jvu(%jg4A=t1 zH$7%F{Ht9^jvu*SH5DKrS&TY0d3ZP&7nR4G6Y?oQ?_1?3H>yF|9BHn*idWQ8O%r{c zj-Eu5%}_`~vAh>+#kWq}2rASSgP3$lQvV3!EN?>eZ4i?Ic9n>W8Gc_b(U}FgzptE~ zU<7pfD|>N%N+z*qqJ)sj^Eh?Cqb7Ufq}MQAqMJiNigr{-m4UC+J(Q7Y#*5=b+x&*t)vj4_Av3_$?+>uJ8W+AiW`fs7 zFJZ(jQ9|n+P?xArKB)8RSySKB%pA?PbM_M=%ad|o(j`PwXjh9{ zha^d~wJvK^r#eVV(H_3+eV*!QXScz?&f|0iAWF}sN7TivWy0Crk3%$T2Uqbf$^Fmg z?BZ1#sXOKNU+PF*+?s0tHjp)BzkN0xpPHUAxuiF=lgPT(1^S7wpEyKGZG7V078Dt- zJ^(GPwy84@RJ5C@xs)Rh>05xv{RdrdgC|7zJ8uo)ic&R_$`ATRSt|15D~{S%goaf# zB*h5YdKBMuvU$2MbHe&Q-k(k~eM1?-;}#TChIvTFi|sJ7q_gLRt*$couTCko1t6?` z9-j>z*ov_>7XuDH^BH=m>2$5w-)4-3Ko!tT{?tC=QxRq6FNyLOAHu!7T$-6wDxUOD z<8q6=yoSn6Itco7)N;TP*Fkv;8E=|nN%K~U?{3Mur@iYV_kkiaPt$uiAxtB`4~N2r zt>E5x5TTzROHaVtAGD^tge`pWl0qiRxbquk$@^49UvR!docXVw8o!?%_RS%oymN{C zT79fQAoq2FwFmI7f(}b9dHAa3_1vd8qH zer0B-4%9kThh zx66CcjhzvCXP!C%riVGgJ;Kkz0>EL2Css;^Gll##|)F}2z z*5c;NgHd1J3E+P$>t=pc>G^;e)yK8r8hK7JVCr40lAtr!q}1sgoq=$hd*jn6g8nN2 zqU{~9t^z8AVGno~fxwstoD{KgsXjfFnBOm}R{CtWyH)al;SRO#lZQz4bohKe`U&pU zS_SKaUwV`jawUd131h1nf1IlTNjdNnd~uKIfqI%%7;|xJ)^&I-W~t~Q4i174<;+V4 z(KCo4ZrG6*T|q@uHOEVCXxQZtql0c-Vb#Ko^H{R?XkzCMl}ON6b+^CdmO>CY!)LO& zbSkiszWa)bSs6;u$jl?gu<^R%`(F82MaC^%q0)65k@ZY+RH33fE2xmlBR;%T_#k|y z2KPFQDR!arT3mT?qTRYAdGCCe5MYX($pC|4|MY&qPc{a8 z=nsd{T7SvC(=Rl{D=$gyk0X@rXmDLudQAdwa30)m>8$w+&$W~+uTv!dG1O$l5Yyc! zjT9`eCquKQU3#^aqX0C}!>wPTM{5E-5*hb@cqL-cXVt2EW%&Gzrrle~ob0 zT08LR4!=}7=qJ)h0{j#?8L()@<_I7lV==wCdL^U zg!ilQT=QwhrnA=24e9q?toc6G#ih$lofMObQs+Dti2Nb%w+g(0qww~$>W{C7f7b09S|uWZtE zuEqX5uv&}>(XjT}>8@Ogz%D5r8fm|(yP?qBN&bV!Z$VKUwC^p^`F9i+K_RYP?4&&s zwm(LPKKo8QuU{9Fg);8gH`j^gDnneYEbF#Z-v0V%Qj(RMi5PVF+1{JuV+n{XbD%Nq zDpf7lHL$MzRhbN2V3&qnL8c<-mq{piYS)nSq7TyVPx&_=~>zM^G3Yrga$th-)a-R*ZuQq%f&!{VClmpOWrg7DA8^#H&Hn z{RP&T^f&P?E5JBXkRHjridUIu7>t0YK-}@bcyC2~06)sR&%aXJX@j$kXuR=Yq9V#w zrC9$gykhk)fbfi1=fqCz*B>0Ul&h-{MtbFMZ*UHK!d%N?JSQHG!)@YO2Eb7;7ql)8 zevkNTXrXKEYXseVo?#`aDCe*_ELhzyqBC4(f_|^Gs#_eFnkx9uQgJhfFwcCLh4NL3 zwPnufVWk%HN`+}5_`3A}xkC@2$~>mUx~?mDI4e4ZsLo}XF?vj?#>xN~zBQlfC`MG_ z`u+A*@Xcu>MFVwr52g0}3yj!!*;dEgft!>*p|K7W|@g~2*=qH6F-Yxjuc5jG=8^vpmF&1>Z;zY*giSoab z2%aAxtB(2ne!9$2*k>UD7vpi2Se9pzUvH3H_r;5Xkv1=IDV-pvoeW0WDzk8JY*MbL zM%-Ivy3%NxvnMZ9>*I)2Wufe5fi1T{)(z?%WWpA=zkQLCz&4Z5iJqhxqz{)m_H~0B}P>D&3e@ z-o+&{fk73xu3~vE8lNpdKIuFoth&XLz=DjRu;xh{u?dQpSp!^59w4jGj-HwQV{1wD;a~n3!lpf z~pbs6d?NfX*?l>&Ph;Cb=&0Qy_9SkT|*0qx%#(%&?W)*UOz36-nz@BvGg@ZHzn91+&;OE_xRQ=MfThEHT=9a z!<t6~%1 z%kRPXq7a`l|aiYjGPnRcB0{B?mgmRs3+ zlfUg_GFVdwwmUvrP|m+}m~>qE7UXm%LS_q%bMQf`s9fk1FeQ~uZ1XO8(`5>94fm+rdY$uOn7 z9|RoF81Kjjl%GWQsBuzRGmS~6E7m;57$}InXP%7N8syrSk6f&hq#=4wrZdyLW#itP zbm)5x^} z#$*|Kg1EM~PVAWc{hG63FNR1=wByM9DVglcqP{MM%#1{0HUvF7Ye!qAoGAO;T6SEx zEaD$Sx$0jwH^37;s=_HT;Sl|cD;10=GF61KZ?AOF9pM>jb=IiqVU2t0z;jWLUB$Wd z@43W{+*fpY;k%Qnb6QqCj^QN>mmlhL1RJiS)kF1kL4w0PUa8@~}O@=CCszzAkPVm2d}dWqr~UD$$)k zCqf~-H8i`v+?IGC2VqpSSeP$4J5D-K#CT0a;XftK=&Z>V3av}s2|m(^Idyv~-4n#- zhiMkE7^z9*BStHYJAS;41T7BgBmJp3%RHj9*w&^->m8$YXGS==ln074IZ}B=9t&N< zXwCDoFb{?KCp4_*#nQ{EVr$d-`aMVjs zNjNkA_`8>$d-v5ZOu(cE_KL(K5~J^&C+{tPb~B-Eo7La`9hVsxUzNFWqrlb{MtNS( zfminJq0ow;j{5zi=XPI@Z#9`DbhA3XG5)C1G{~>8p@WIl+h{-(HEy1 zmBA2L!>+ky@xo_i|MPJ5m+!M#Rg@X{QFj>zEg%O%lsn6uie21v%>Bt13Xbi+T`;~?KR5y z*^Za;t3Nhe11O(53R2YUsc6rA%dc8a+rBls3`dv5W!5UHzq?nn_{8&yP>0HUB3++P zn#Navj5__`G~>k*3Qgvyz}%?F|4YQ0=l&D!TgYeP7O;4xZd8k=z3+|g=gh{)N?NPF zOu3@<+7x4v$wy5}+R$clPDX1tG%$XPfQWZrmU}H@g|WKLwA@JdE14ZCNS3dU?@Qi%1>CqVSAUSm^JOHdiTa^SNzBJkCzpdq+PAAG6;VFXA4gp z2oKy?tR4BIt$Ik~o72`baY~Gu`iXhdPg^mjR6qM=^^=_#hLE1M)#OH#QBv@g(u5v~ z%*r8x$2qL0snjaEC6c2!XG5&x%k90T%@|rY!XYOk_{yb|*;;8q!vU-E*QJQ9uPzm; z*qG<`fAG;r*<^_|ux6|6Pw{y=w1wS*bZsqPwg)0)_`?FRcG@iD*h6Sx0^bw!P9AdI zj16~6@9$@UjcSLT!uLj94b~U;x!unUC4r%}onS(*-A}uTEk@K1Z~DdNSLzWbFP?F` z2{u33mW&)SBiB#MU1P}3 zWe+zexMFsH6^G(d>>xCL^iHSDT2rB_=xyDiWZLtGeF}Ctgo;nLJ7TZn#`#ds@OZ-S z=nS2DZ{Kc;T8LzbXKQoBQ{U_A$90vK%5vKDyo-EUC-3ZSKjqE02^}{3ZLS=j3saY| zUp=<(FUxzqk)&`JX*?(*M3z)mASr`NkCo!aa9h(Hu|^E}Xa4YEj7890OqUD!EI1Qk z6`i09Sv!>87_z~fez_|MuYK(pBwPuht2tWsh1WiVr+veRd!3hi+!#AA0U42-WG+L% zS`8(HLE@F0wDAVWf-1V`4nq;6YhTFNZZ~j_+|WeNB(HbvAL|&~Mfl1*-yy9VVy8D@ zUFH;9-#2V~UBvw8{j2fLw`t@i57(up7Y|)W2V4-DQwt{Kj~2L_Z*MGp+Io}2pFXno zj!gS4>jdji1Q*M~_-n_Q9h>UGBTF^yf-->?^*2b&u*JPvL4Un@O5|dv?v+9H4}EOj zt+TfMa&yVoei-`SY$QF}j7h7JbHnQ&u6}l8!E$D|B!5!6ceqxYzAsPC@odI8uiUi7 zWGkpIM?m<;6qDf22`-n4F568en{E2H(xR#XR(vHtMp(Ai6lHE~kCCRyA8B418)AM_ z=N8M%%ldT!fa;tf|0NCG(@jL^`^r>&SdS$_+jRw6eBTc}%XPhVBjVMB?KhF{yA9M2 zKipn!sjZ^uOlaYqZx3He!SCYZS1Q37xOx!vT~Flq=AU52loZ`h+=n|bsr73sxL>*z2$vx;`bz7vVJ%j_nFL57rdoB z)C4sjTJ7mob#i^UXiwDZ_cp~5q@6vtERw&cV_2nI`9P^zE+dvqdU}L@r+Is zg`Ml=CdVy=^$36NqM^bRS>6Ys=n1^LdS1C&v;rjj2}c8Ce)T4XHE;0P#~$~6lnC3M z?$17Se>5&N{~mm9#_OmhsZb8A8mnYB%mNcY)v@-R zuw>-9l6->YtoOXKg7#2^$;GJP%1`NBj(udi*~1Wqz_AN=_pHMNio5t32Z&7e+E*Cb zyoi*hu1G)q!jji&oH*4@2YJzE8yiiNNVrM<*g-wzEoDz&b|UKGxWGWRu+&DV+h7;U>HBMN zCb#mfYo7Ya$$JKnlHZ`FaDy;TtoiYe<~P>{)RVKV7_Jk92V`HZEulgGaHp-}2G81L4b8=@rFA~!=U zqxK#OL_uQbO*&jf?tESa!IoQKuMSYHa}+lbMBr2FEDA6)s$34T99~SmC7B?y*DvOL ztvi$T4IBH=RR5aB?`dW}%#Y)5`ob}?sZVA4*7{nq&7QD1 z#6&GXHh!B@^ErFdHCX-g0p8P3wb0toi^uL9}AUNl==x6Es-v7 z?;bR{bNKNLXi`L%UZK{ReBBp8f>1CV&Lc*EaeKK%=C)sF&C|{cdrt{Orm3+3LE71J z@4uCbgpYFih&ul!_Yw^b9Xrd^fQX7Kco_9nVV`SDuIXA+7pO&|`Fyuei4@^nn~6kN zJFzhIUi^>Nw+iq@OADw`%*0kdh*AMy-PLo}M4>Jd>I(1gU)Bw?0zl zy$TOiaU#FzY|R}OH`IdqgjwCUi0pLuMtJxs|4bW3{`-Nwe|Q@Sp@@_@>k+!mJWC7J zAMt==$sf_m8WPA+^zis2DeEGe5Kp*qPYfbd21B;Zwfo8cF>^(*Y2DU)s}t534A-i` z*dA}^UBeLcUVubI0tv!UBXgT~%HI8f6nCg)X!nv~cq-brRtth{Mfp90=p?TC3Vmh< zCJIMcx6=!FcmdS>OG3IIb92-!nmx@pawD!i>}hu=`f_JKgRG{Eyc~U0W0`Wdg21&7Ew-BE0Kjk-UC?H zuNMZe+vT|*8g<~o2^}L(9chut;ki~y12|3h1!CU57Dy;xS0e6E!P#D#yvOx4BHgJI zMeS2KwwtAX5-d-?*%YgF@_9?^#0Y+8bAIHftN7+>V8#JGk~EK%Q4-F{`upv=VEi;6 zFkYPRO8&8AVY`U2NkNAjU=eg-T^yqb=k>1Xl0k8lDLsqm_E)h)qQ3XRTobw#5t|GG z-3K>PWKy@HHCmLf2JVyU5)6hIx7;pmwoXicGW}oj_OJJ^?^~M*n35z!pu!rsyg~Lm{JJ5S1~- zpi5xO7VP${B~gjokpA`H=jC(D42P#9+`e5Qy|c7@LUY3fFG74h9}QI&W}2qit(0q> zY{}4Mk$7KOyK5$WDK;*xCQYOoL4Ve`*<{>`Lpy-``5P`#%RS9cs<<(D`+We z3wB_->0Mi_lWt{Pwn7o%VUZ0Sp%r-v_=lJT1^9p?D~nZpT;`W##YnkB*ZH!y4c}Y& ztC_nhsdP9#8?$P{vXBd{Rg1#8TUA##B?rYIDLf-Ap22qa{$c-sCKMXVUp&sY=GYHT zk+?hAf-=gjmn1tmi#@(+>R!)0z7bbBriHpw-r!7&bdTyzx3zwEWnH?-KThRn;uWJ| z5_+p**(;D0(}vc}cr_^hQ_9#(=3Vy>=Nq2iOES@8J9Mj`2=f-WSTTXpF;QP$7;`)J zytu9Rp-O(JC3^3Q#L7#lr90v1e(?=+`nZ4w^=SVbeFEK<>cyiy-zk%u7*?(ccgB;b zkOi^e<6F3CDN4N+-sRHcFuI0`4FhgdgA>&cYF0geEy8dJV*;O$d)zM$>EfId5kvK~SUzUg zXV@GSTxTW3`A$Z^R&8vdnGN9LO|aF|H>t7s7W@7s3Z#hceSEOxvz8;x3l=>~&7wQl zweTYarG8hqoNq>B&JnYYpd3)!BjLRSIV1%b4f8_+BC%agPml(IvuB*T6 zC&D3GgmD^G(_ck>Z()44nsXnQH+k-SeKev70S;T(YvQ~&5#=OpORS2nc5Q)B%Qflh zO7tb)l_4r@ebC-lpHJwB43kU?AiY1qR_qKdX(3xfrQPskCkp^oR?U^%%z{k!I+E8gC#3dPNba>hFk72q#@X@y7Zp?$~4|v5WZVqNv44xF@%N~D2#z^|ecEXA~3faA52U%zq5b}~-j(_R! z$glib-e3L>VDn(a{k@|Ut|hzuYaat}?^jojocVZUx^-Nx94vj)c~Yg{-%?b23Cy^D z@X#8yZ4{^$_VslNp)Nc>khN1+1hGl^{Yzu>lXtXu)6;L`{?mw(5f%(D)v%2z@6nY$ z5-&M3G?|XG&=Hc9nsnBb=MA1l&A$Lv)aU5%n?m*Y9JC+4qTAK;mD)6Tf9~F3EAk(T z7aX(m7moUmJR)p2Z!L-WRcd=@1trhDC;z2_6LOye*K)BS*zMB%moof^sC;ZXH$o;|fvR9nNyYoA026t1J_Y#|Ibbw z5WWEa1INi}C@t#_Py8xwC@xzUT?}vo(3_K?oV%$Rlhi-@^&dNHn9| zeqh5vx+X$aEPInYpQ94ve1%P#!)`nZEkjAEG4hFbpN|$RYc!I^#I@E(GR|mATS6!c zI!Ceic?bEBPU~_=i~vqb{he*^XV~D-{%cEs5H-wTt9fcLyz_2?rN%auF?U>wkwK3e zQ|P5rnJq6}tB7ssC5+4Tm=N(~S7$Vpc1V^RC|-YnsL{oL6$IYs-l z(JX?wnJ_JtfqJ3KkL6_}BX|Ozh8$)t@h8zd&#Y6D<;J)`r%NeXnVuvf;-|OR7H}slz`l){IM$QGNp@o+3j#)Rd zVy+I)PpBUz)7;D!yY_(4mHJP=`O__7YZ(UtjuFQDgsadoXX9a^kAlq0*oCFh^o$Q- zzpc~qwc!Cadhc_2{3IyUu~`G@A2u2a+b;W&{-2X*!-d1dIPe+PRRxlZ==qflGH9YG|2;*};Cq*e#wDBc>C* z%K5TaGh%k3LADx~UNCpbgH_JXa3$R&0c@5J;j z=V?jLB2;=dm0TA8E0KLE6dryaRK^ z&q17ttlaH@oK+a8A704Q?XK~q98fH<)q9x>z5jSyC;piOWw6Y)D=`l%X8HzLrh1<< z)C4ZuMK0shdRxQKrB|O0y5Qi;Sq-O}93r7!4|amVyeq}>cNTnS+$2!@7L~mjWIUkn ztv_fJOp8d@n>fmej&D6>JNsXU@fUn}g|$|8;-t=&=7+XS z?+dC2k~G*FRH1G&YKXDHJyh@eCTKb#2Q=4J%2wyix1?Ym#gONl&~ z0UMO{M%+vg)cwd?Q(>>2VcAdcl_qLV&9e-mng3i$!i^3K}$3YM-%uk__JA3j$>Bt z;Xf8Sr*z=P>#NHIb_$EhC(a;H^7?$vb9~sHN8Ncg*25}fcrUWS7^cst1Bs%e7J%uU z_9&VwnjPOMy~7W>&8zdyIHC{7{c5H}mRF+B1eU(28134*8Ny%M)r-k5>wQ$kePOe- zmzV?wb0Cu4uB&t(->w_4Lwbl_#ad3@hmM!--`*X7!66za+qO3_dV?REH7@9*^~Car zqD?a`hON0VcJ+ZAQ&rN`%K}#Z;|8HzAl%5_u;?pvr>QJ(_|v^d+HWDKdX9F};8z`q z`)?XO6eQDv=?azup7qqxw}qDNj+Zydo%7chnaFr-QgI)6Fv$IZ$QRAh{3)et*`s+hTIr{MBA`rpOT0#)(XL>ETf_vf3<6r(nkT zjgb9Mk8(k%&c~Y&V-90%cKtg(W7}G@9G69r^vjoLZDi>u85&!=8E)Bppg1tm9=kW3 z`Q1z8X>$2SQdIx%+bc#%+_NvVM(({F9LWmH2}Pyj!E{6AHv5|M&8yQ<#R|}wvJVIH zR63SyFy_QvuTU*Kis%|~PaT+ulvPx0#`vx9M>}uS8Y~EJ+-f%HxQDK?8N^S#KxUMV-%Y(8D!1 zB(SkPyqP%_qjK0xi4&|j3WD%uYZM*JkViYRoo+Ove9mVGKCLd~oZnHO67*zqzkxre z>WFU?ce>u!^c#mxMk?_g_qkc=$No58fO0V$9`+)sND-`7(N`U}_P z-bz(7^K$D_$<1D9ErgmYCh=iJv{~vKGO_|6B}l%s3y34()8;Hk^lLpV6C-!b5U*|J zU043@F|!%Z=2+C$+E=bX;r4qpxKx=oU{2Iho}jyGG9Z!8#OocpK}XTff?8*@4;5{6 zU8%a}7wA%BUb`ti_q)Vjz*2j+^_BB%3x7vlY?^IiVxUO~unG9Jg(L3ht)7UYiA!Huv=H zlt25KaLwV=p%s#gEf;#ckpU4{m@_Z*?F`V}gU$OTsT~Ee@-twCLubuKF%_4w*E$GQ<~+!tgdP!D^o)*IrJrzDyRravS-oT+ zjw7z+VPH#H`g*Wi2iZNdJpby;{SxIHph|OeyLqlxzl=(7=`9}8DWil`@J%FnIV%>d z6a=feO^sFc=uzd|=r9_TSmKh2W7< zy>A-Z9Bk>64@}=QUKWnjprSc*tb41;&zuTjX(aA4{Lpd*ygsM+v9E;jj=fXY_l+;F zu?2DZj=nhaVPFfN9*5q*dAC?-yL@itJVS;gjUVRD5OeayrA?oa3lBo_E}jm^qx2L& z^(%_)t;l|z;j7p{HxH&bb@egl4_i)MOuHO)+{#yVwCQf)he@r+Rej$n9ceKQz4X+$ z*pX9sEZ=KVbBK%HrC&R$?kS*>1^gD8*lR6cQdB~Fsg%5`sUT5Fkeqp9F zDfgrr?Ynb4BkR-F`+|KRLQ@?|vUI=ykOa}>`5|OzVZr*Ew+h4FeFpY8o^T`h4R`mn4WUL2j-_gOLgb!}LONm1fQ ziRZ7d=jZ2Oqr)A_*U1s;GSmbjdOh=fE&N9x)up>9z6005;|DggrjOu>DBpQu*HYl-oCcw4~odf;9Xc&04~7#YmE+pNaAGzkwy#rMd59Fm9S7*6<=?x31&^b3aBO=Yj1!vT9GB9HshK=(euv>x6OM4grNF?f^O~ zzB4yUw5$0x_G}cjp^7^|b+n;wr_A!-b}^(N3O0GGbuycf!BWUJ645UA<)@nv`ljM0 z^r}xwMExAMFGr$%jn0j;^l9(5!vObvDDwhvFIqaZ@xNeCvHx~9NqlVg4L7y%#q_qS zcLYp3@vAa?5tC<6+n90}6}p7PZ8Wb3-_k-JH6O4){6^i(i7dfd-NT;MI@01mNxpG= z(t-8<;TN>HUf-?lDN~6M9QYq(q2E%9h(3XR@eaWwHGG#D<0P}(ftTX;+xMAU`Tg|y zKYW|(;pI-qQfbc=e{gW1*a5jUM&(`rDqQp7QD%Ds%>e<6rSU@$>I1y^m@#X<*k5nJPYy% z?IpHf)lD_!Udl}LDU9<( z4yL7+*4W{wb7S;VQ{XnbzXpAavoYR@+$5xCMos+^HTD|v18}21AYS6fx$_aYu#h6$ z9hVYrzd}D}<%e@XWt&!$o{1x>RYMRzY+y*30Ir~#wi1gI)+p04hrC=#EiO3}m z-M;d;E zG})$9AzHis3Eh?-Bcx}UkzD0Pdbgi-7Vv_Pb&Vc&sYerT)Ha!(7(qlO& zjVXf#>lr6wNL0JX*~GY+M{yS)j^vXRJ#R{@|H56SlQzq&jRmM7e*dlD*52pZa0|$? z6Va>PuUc>@n^B;t6wds)v~uU1SlVU_<%^(_oZBT@jvTiPX8Z8DgmLQnWcQXfWgaUd z+&K~CF)3~$o2Fj$M(`=m@LJnMj-Rd3uhA+k2`3A8`_-p#tfYZ?en&am58ycQ_$K~% zB~6Q?n&o%KH~3-m{uvnb8NRXd64HA=2clec5|v_&Yo?^Xq|le|d%hwbhXjzl+nWF2 zd!>NALIrLmu#>S~);0PI2?8Hlvm1{DSCjKTvhT*c;K&6N>~#G~Te^Z(ah&3mOiotd zd+Izzm2|PP#N~Qjz33X^N70M{k_FGibZWvDTXh$Hfw?TkFh$=oclq1;RjmBz-4Jk6 z*2+f{v6G~P#7kv~O{7rIPtmm9gUR+Bw`SFf3{w~AueG9j{*hohoVI$O#nWArUDW-7qajk(6!&~frMo=|iK+E=0c z5*aLuEnC|&&9G>$Sa(!Y;Z*Xr-fV{mI&SSr$xpG_;gzNym%LN592q6xB+dwmL)4@Dkj<;*(N2b@p7QiQvS?{R8p8*kK4$dAZ>c>GW! zXTf;$C0#bzDCFr=33fahZxE{b`EuQ9=P^4^=f!0HmL|%{JBqhy_OyGPcjXBNnyv5t zW)+{)tWiB{F8FxCgYDvGHWgNOuEjP}UFEVYog)qQ@B|h2cG3HcVQ99|{rSRf*0B4o z*_Ac8XK9OS-BNN_fJf-hc1ydCDf^-?ZLJF;L{*5<7j{17fOs*dgjSk_eme?OQ@@sHDKkkcc zz~NW_M4iR29(Z~95BeP)PaTgc-6iJb#~nug{;>#}2OR;B4KXZRy z@h;4FN22374|ca?9NlfdF2(qWBx#^gm`w`)JSP?*369r=`C zsaudz`|cJ{yJL@rGsoDwyR~=r$Y4I5u^@JY9%+;-S7@uBC%nGGQA+|@a=kVDz^iR* zL;H-8x!u1A%rfP4%U_d>@V9c^I*@5eMsQSzR+reKsx+QGG!HJr9L zp3L(Luvm=;ljXf<0Xkyj{mLu;nP=<_j>bt6pAtZ8zVbk$P24H3$yj^l_O&oir^&Tf zvW6Zjd0&k6CWzCJ7?MWbG#i{LHP@nn(%(OP)SG3P*&nt>9srJ*5mrfY|o<^FUCt5R&ftGcVV#{y751!(a zxW=XsiqzPy3BU$V51V|7Ieob}F}M^eodr~I6epoBOD(W9f8_&tt)2L~YO6wS5TE^k z%~u0eM8r#aRH&Eyy;&L-$DXt)6l@r&FeK-fB)b-pb~k?0k|&218Rb-49s_>zjaIH- z65V<}#^y+cwvQBiVuVKW>r+37Of5ZZzw%NxK3)7(B6?^xm}T+oHG!mxFU@Wl=h;C3 zDlJNE0!8_w9hGyC4NNCEP;3wTy~UK43Y#UR)~XWT{JS0>HbJ@-i4HNj#((&Jn!`^* zQxV?kS8YiKYKWw2qB!DzXPJ1YT}c_x!0?ZLUT{cQXO+d?S#H111~*YuC10qnlt$c!T%wa72#(V>kV)9-7;HtQ=R+nyAUr9S3Q8c_7A?7_mtHu?8pE{D&7Bffs;!}6IRfuyEJ z#=$6|bGmPd0-9zwBLKOaI5pk^wfs3j1-lcHcNhe)deuX@vYLZp3SU`!n`4xU3f+F> z0nrdU#xla(W0JZ{l}ZuboRav#vccdzW+iu_A-zllD`m%dF_XMHc0^VU%b#AT2-a`F zV9zJ`YeVFQY1ge0Bk>SZ?+=b%wZ0OcDWY=F{aG4dmKYsM;diNduhzQnv-cCB-qrw( zNB+>Yd}f|^a($TdHKcs7@htDO#Y%7bxQlM`lEGUtgf+*l9u)YRIF0q6F)b}={q|2H zbJ^amQ3tnOXJG0L;t`5(DTS>fd2Si}fX+e=Hj@rgF;`J;H*7Wxt}YtFm0=uAk7h1w z->jNc@Nbow^3-fqgWI*}a+v-fQjHvb(>yTIiw-r3zHU zstP<_w(C=2O*uhr@38RMgyS`8opWa4NpfSqjo;(WA~mngr_A1NqF(=+`b2572m}CO zw?AC({#~O@<^xaVU1v=ZY+SyX1E{9$dxyoB%RHx7 z11j>YtPdY50|Fz8R!3EprcwAxm*5W&v5>lgtl!v8@u+iq(Mool6u6w7x*=(L;Qers zbLf?`iluj%)2(c_`24(j6B}~8+Z>Mo0*9N^Y}T8GDl4~8c2dyj3{Z|x?l!H* zAh}jd+cz@y2)g!1*EPl?^->J)*{mc}5bSDs=ogtyxO}yRi{T8b$ZR=~sY7rhv1Q65 z>K~Fz<2aqAARw>!-eNalhw%uT)fC;7Z0iy(+C(G?g`m`puio~SR-Z}GV5`YckShj} z8&Mkp18Mp1$>Tn-k%8I6?3T!ls7{rYEx~ z&<*B_1pN2npZoFDm(2}S_)3~LB<248ptAT@z-l6r4*vtYX7al!6rTEtN0fpLAymjQOr45NIuQPhp-?2UXVvOG?E$NL##7n`F!o}N?69_poZA}qZ90}e0SEWS9h z%Q(JeexZqRju*4J-xd9U6tSa}bTy{0VW;PPd3MUL8-6u|=hO{;2ErA&>v0d!0`WcT zGA={h_R!ZIzxNeU^?YRT4w8qr^pjl+)Rn~t9y)zx0{U4aMpUb@WJpCjf+uU+!b;%h zO33r0*XxmA*jDu+*ruW`v0$qou>&_Sb~UqnuQl!= z#H&DMPYF4F)vz9-Ct|r#jjr^BE~_wYK|LjSq)(%rPgjtD#&T{4JpbCFY3m3Y5(uzOk{D5vMy#}8(kDwBej_~MmS?LCE zObnV&vT1x}**huM7aE4vizUg3CtczeETUz_jEDU4-Xyo!!yE;|+ zg3u=kxw_q3vqe!Awh*iuCga20E3WZJp7DH4>~T`$>L=r%rs!W?ZUy1sHJic9Pw}j& zU7TXKUhWY4Ni1z%OH$3fJ(Sr?G^%D<>$!2Z}3S1Pb6)5=%Ta zUix9vN``3NH&F4BQOu(~@Yzkk(s8wLEoq9A`_;7mE`);U4uM&Pi7`J!Y}KieZc8EW%pR4pj!;+rXqY{53ZXSIlDhf)h6QseXe7WA!E}9r8ppQCXOA+N)eq80-PA=0_QRr zpf?ny(rM-%<~_0`#(xru(Oa6&Y7~eRc+iO;^8GH_!q?H1Mx$Din1i61%@H1YUDxJz zD`ynewB@ID^1t4Z=oO$R)|{m*Uq{r8#w+y=$DSjXvH%*3yyA_4x5Jl78aciLYO+4)_i z6Z{`ZMADiH(<`~OOEAJ@Knwg8gGng4Wsx_fP2b*q2tosi|LU#Yy2Bpz8^45HjL(uJ zl7(i?pBKiWgt?J}=W9jNIe*Vl!qdF=@fs_Ta!}s?C-_1~;Wl5R5zkav8qSp%|Jw0^ z^Bey(Kk4?d5Kg}_9w^BE2>Pcvd2nk<`zXy&9Mmm(efsjwN$v3Qw(ke`v)bJg)$O={ zSNyAV(i;)4c-ESd?>6UV%adJc@r~z^Pjp3K6YW_;G-xIak=Bw zAm3jA*)rN)lCkc1wC(k6K>9@~yWcB(9-OmQdKK>-fgdRgpjN?~C55@okm5Z6&7Sx- zXGQBbpXmiW9?-xHe?{W~YgIC#Y`GR0*CG+~yx?>HqIGC$f0)Zgj+x<++^A z=H*Dz$o><|d^fHEm$JT%sTdkiUEf;VzOPm_u_3jt{%gS0t0?3vAr(+7fDwPqIiV2m z6??L$#o?EqDTCX+KLpkH&7u#zi4mWF7s`PnsGRA~j{q!o){F=LX~(-42sWgg^nTr$ zTu%_1?mq+f&%>9dfr2fQuj&&$CUw^mUwVd*{jX#Gy_MoLw8Ga2d-$;TZ=Y$8h{LV^TJM|dPTNV>eGnmcww0T= zdkjyeRoA~%`zo9{%vGfDjAD#Y9+UGirFH!E9rG{68ye~Atla6VsIXuDcLRbe@Hq6g ze2D+9!dWOUw0a35*J`Wd$r<68xEfqg_3w=xsPuxbV zjH8!tVp`LI*H=P?sl)1l^8NzeG?3vEHJLl1900%HE-zi*o9aC8YW(&2YQeC;dvrg2 z4kv2?eO<;eE6u|X0M}(tlsrN>m8t(|VUJdmE54*m>1TMm2ANV7 zW+>yS@)IL6fg*h6e<_O@FS19vtz=OOQ^7R!2040n)W~(wwwMH zkFl|~f=%(zp4d~Woqn$#0+Ez}5RI6lq3xdL;0$lRf;S7_?wRt1jV4fIh1cJ%b9?VY-4_I}gW2oslef6z>COZDsg38HcXm7)$mY9_l-sOfC=X@k=5d za2JPW_gr%t7q^?Yr&bqCRgMQk9g5IrljlLTQ)j=18>^O5j=-*9%Jl`4ZwnJAhECGF zvt7a^w?n3S=5;^)3?6TK5s+rgz2d!zsnAc?(fujraesF=_#W<}pP>L)i`cNzL+><$ z=z>ew=t4!;YYY$e-x58sN%1og{f5g%vGgL_QH?VnFQ7U_2zBpnCOg3D4L>RSQ#LsYfQlGk7O8yeJqDHSG}gla7F zCD=8UiP+@l)wJ=P6iog!6^o^3!w@X5kyC#QWR65vN*ATDz%0B}KDg0mBkuf|`{Kpf zqeM{g0tg<)mryQ6bNWuAL|7eY zKqT_L<(|VF!2GaaH;)IGvzI@ZxD8w{U#fZ3;ln&^QzBMXxUAPa5x=H+)!YeI;obOc z^4m$ndB>CH&SG6f*3hrGW}xTpGeTzEStWVZ`_{S;E-ss z(jXcM6+JbSeY_c?ezMC}n3

*fqm_)*GyKq6+qVmfEl`8_&}_WKG&5>TlP!a`lg| zwKn=ql?og*o-wWCN#Wc|Z@Lc9mma>{(mp=;iF_RQ7OZbya^Gqgbt4q6aapP{AdEA) z7rr``ehBJUPoqJsofSOYILm&IqzXIfSQ!rtZR#1yP@LgDqZcN@~xus2Bso#pA(tn=#%1>0z;qq1wl{K^&NhZZh0mZ6 zQJKVFwFJFWR^q6Eqmwd5+W=O3)*EgQWxZsD-{-h@fD zMvCNXH3=-ss_(8uh5(Uy^aIm5SyF_}eJAlOJ6@S~;)9ox4%c55fOwR>%m4_8UMtrl z*c~=9j<*#y5>IKksV7xWJ)YpK)&GnJsrkXDXRoun>NZPFT`)>?%gH2x&hEZsme2?2 zYcyZbF=o3+N8U>6#1z?W5gVX^oBxTlK!%^hLyY}E(2f_}@WB7OY*3*jxw|>Cy}@zf z$Z0k&3Z3Na#*VRYJW~E*lu)rbet-X>@BK&Cdn0tz*qV_PV&2T!V4b}YJr9}ZMdq%R zcNLK>Ma&82m&8$z4)u^QxI#ZoLk9nq#L_!AF(OgUPjbi*C-?9k1D)$)VcCUy*N3iP zr`TDu8x#85*6;l)D+_cxT=5uzR-D$%i1ZXjEPC-_wqLx{Ii1g1FqRUWZ`EY{;5Iay z_yEK<=D2#nwCnFhPPNd8r7@dK-K~41L_y#}n1&8XKnfo~K$x$@qMgjyYGZT(?sT&q z*T6~wzLtCFZ?8RPYowo|VFGW-cs3t$VtRqVV9d7Kw(J8HmcuEH5xb#iY!+}~;(F9@ z$6H0VMEWbR-S!E-lJ9v_g*xN+74RF^_j#oqt!Fitb_8O&1S@*Af-?1EhLbE1buON< zNgL0e?RD7d!aI-?67?KpNg4(e*{{DFMNxZ(cE0%HXi%oimtU#=L3aw`v>O2-dXf)U zmH4v{@T>TMKHK{l8JDG`3UR8sAW1@_^K;k;hK$@3nJ+wg$tZC;9Em%fk~tF_BXk)I zxeNrLtFT4CG3SX9YpuARckVANB;T zNl9+emB6%5##$V}#;qcAz}O1ko_mfRbBVwuAqt?sacwoY)t`~Ls%sDh{9F;%Y?$9cc3NZkapUx$Y$|C)9CnBE3y0V2 z-B1YSo3_1xiB8313C)n&dHL(KbXISP7k_|ljj!tC6!-j3)+!qMQEAFyf`gQoEGXf~VUrmKuFe>~Qy6%6B%~hwRpmY2*PQafh9t zA`!g!fCxv}9XE;8l4TZ`7xr=C$2mWq(O~l=(tWQS8KVNExxUEr)(w6*VvYMM>&zP$ zG^k^3YP8%XzsMvkm6K{cOF5;UK9MB|4JFBA^DFuoy5g+`&-nRE?m02uv>?1od?F6) z?p|9I+`=SYQhsgxx?{oaunA)yARxiMBnH7PHx8KNjy6xHios<37@neSGaIP{&yPau zO}vYs(vcuR@W-Gvuo}tc;%MdT?ih3g=c#POW73#krx(*D)q&B#GGr;#&9$gINNSTQ z>EnQ`yiG|k)Z_ZARaU*8#lp(JBkcBo+UwNUBs}4My^BrU9qWRRPw!kEnDRbp)E+W} zgWZTVnF<68hMCLI8Yquh=~F)P6*WoZk}VqCRkAIl!ypV-N-4e%&rf3qh5`$vjrqabqx*Pg& zr-C@uIa7Ig7Ym+OX<@UGz-W$;P^Q5GJ$vurVX)%aj@Ah;NREaTJfqfG%2~Co=hRjv zMzj&c`S4#g;vMa=tJ5u6M<|iC}D^W*d^FK%hF&D>T&)0T=0&1 zoA3AMT>w!RP&Q3bSXiJKvzYI^kh<0uw)*?f7p*zH$9xlmBXa34zJ)4sI(p2Pt)qim zZP3A!OC+jT<5R<$n!ZKzMa**XPXvIQ@_^I$z{g8uFgm9!*p{hyX_$XG=NZQBx3w+= zTj<>T2|aYhp%0`rQi&0clYzal^hYjCUH)tlvgq0Oo0GYrPc#5z>MUV9wxnb!X%i`) zhVD{YLUFY)UnE#ddTEwc-;(-)}{@2hq+nvP~=z}XZ&{2Zrxx33mDqixXcyN z&2PN8lh{38j^sOLsIpEUzp?Zpvhb5d&3lZkQ>c+|taqwWk+YQKJHD_uuT(6juiRvA zr9cQ+a>N>p*?8#I1w$F3t@xR@G7jQ?0>lO(Zuw^pzBWXQeId|&1}4ybo!h4xO854at#AkxeC#Wt~vKu8^}Y_TW*PnmlabX+d(gx+fz$cY+Du#-WnxBIj6Ss(zN{uC}pDn z{Dj}qHL{-K4K5x(-@?M5HJgJkAjokIu%@X1^&ea3&~&?H{tiw+3moKi*%fm;fXCA^&qdk!u8sNls#tXftDt7A>KX=rvI0%$Q3O!#CD zDs74mI)C@=O!Dvx-{m&_5P_lb+F$SxUTXN6K4ao>d@d^IDZpcx-UNw)gO35Ibw9K% z^uLZpuauC{FOGS5wEP$s37dQ7zls;L-Fvl5L6|Fqs6mtKcj4A{VqoQA;eQf-|C1H_ zQ|H_HJ;MIP_O8E{__cZ_QggERC$$%UPvXh)Xp0+wh?D<9#30hiTx1jYIk|P0JZ=)I%Gl7A~)IQD&9 zXLo$|s*z2yqcH{}mI8UVTCzUA`BRaKMf9*oxw4DbM}q2^SK2MaHHMAKzlF$>?<3 zIg|llr2Ice)89lMH@j5Nio3561KiI{Tci`n@_xhK4Yd@JF1_iRRe@bKL>MT#2o%#h zmDu~Nu-^ZFY@KynlkNZZQKVZ^!T@OjX=x|jqdO#Jl#gBDC zVv&j+SvF@dwT#7rkAF{_u^152Fc?1(oS2*W<;X4p3HmVv%I6W|R2Bv22kv@$B}2&i zz|vXOv%d#}z{}__-I!m#%s=t!-F2$*RiAp->=0mbrN>v@6g!U^b22fATTp{U*N&8Y ze0zA-1C0Y!xRk(J)-UIC7mkaZY;Qv2TM{;a%JU-FM2Uefg?u3ymwGoGMCSfpzvfBB z!x|*;y}TD%ZA>3E^nSi>SA*2jaiHmo%F`WqBy`U2@#mqtR_Qs>@9#ls$9k_Leh9)t z8vjJX6#l*!5WcNtxSPM#ah#q6fVa&<>s0ZiT~+ahqMN+x86IV%CyAOTBIqlCMSg?c ziArDH1TAt|zvU@BOc~6W{|_(6&{Hs!Cv}Yb$pee5h=RSZ+8Cyrw9aYCVWuW55Yf`Q z26Y)3`j-GneSgFlH>9+;nd0v$@}KT8QU(jK@#hMU?2@dW9W6)gt&pqaD_j?3(Z=#&|euEhYdJCM%6v$Pq z^mGYHsjB~NrM%|C9R_fnfJX-__Bxro0)lquU>N=%GULzs_5XsqkR>iSo@^{pSU&-l3xBX6 z;Vu-1br_5fZ^&d8y6++m6Scsf#~@bb!KMkzvJFGCCiY^Y6H>L5cJddFn>I>uDsNy5 zxcUBSNOkz$C&9bO6=dP)l*mdca>KyGs>6GVC_wz-RQ!YH{9{`gQY!^5Dv-aWz%GUV z`L$H+cQ1*e&S-Bh+5&0nXXhuQPT)OGG_$EDQHJ}4euoZv86O?fjeDV@a98Yt3oGvZ5=4m8+B=H@=oJ;v9RM26Nx)TU=X3?3F*yq}jd>-~dBuGFbVS7)X;D5f(k7(6SCPw$Zj*eGTf+qJoLmvl)m{w5YF7Qio zbQxdKjNp!ozDhrcM5%F?rc+5c38muS6Y3P5|BPUZ7J2ilmExqzq`=gao_?Rep3&1D z?flK`)dOP@_!P5BoXgUM8whD9SW#)=y}NO4MrgZ=X4`kvq4ElRtG^dj_(}FO5;`Ge zyE{M}ISN#~H)7Ia6};0Epj&A|8}gzBCyYl22^X_?&+KX-*(xoSK}D;-FY*hJik#<= zEnA>$a^)#cP~6zH=qjyNhm75ewU6V^TCWLPv2#-7@T!~JqzuKW^ES;yVmOGBho2sM z4gDw&xW!f!wDGCcBL$-Up%Q2d=Fk$ILD`w^?fXYfMaLw#KP=}IS+JMa6GGS%D%94q z4v7*0ndOn~>A2+1Zh9x>8wjb3tcV+GFLeJKS8yOiw)~r}BYcj?^0`bKPNiR$ExFPi zEPNiQa<4XCR58BUkJ3zLp~vkQd@76v_+ctfkmjLJ4e(B;GIM zxX5gR+g2ISg`sc%mCJ@@mJ5i`%v7ihAiq|#bEy@HZ30pVFou=5sVEZ`2Q4GZdct5` z;<$8d(TJ+o_R%NQlw~e@f@`hi8e{iGY$C5gsbdy=y3D

$RndnmReo>;`AY&YI+g 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 G;{O1v??ssa literal 0 HcmV?d00001 diff --git a/docs/guide/img/editor-telnet.png b/docs/guide/img/editor-telnet.png new file mode 100644 index 0000000000000000000000000000000000000000..7afebd41348caa456f3a1d077a7f5730addae309 GIT binary patch literal 125635 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

mNe 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 F|3Bfa55xcf literal 0 HcmV?d00001 diff --git a/docs/guide/img/editor-vnc.png b/docs/guide/img/editor-vnc.png new file mode 100644 index 0000000000000000000000000000000000000000..9b1cecaa0b563c5b73e225a789be7c68b1519759 GIT binary patch literal 130784 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�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

P!oL1OjzkEt-2EM{)%wDJSk^*CxWnNoyzCxB`D8 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�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

T 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 K#fx9+`~4q1+RZ2c literal 0 HcmV?d00001 diff --git a/docs/guide/img/export.png b/docs/guide/img/export.png new file mode 100644 index 0000000000000000000000000000000000000000..7e693d595746d61e90057d9074fd584d38b4406a GIT binary patch literal 93426 zcmeFYcUY4_w=bGdRX|h(1SyI%=^zoLCL$n636Ri>h_uk8gkGZ9=t>n3h}008paB5^ zQ96nu2|b`F3ZW<^lu&YB^t*e1-`@M|yU)4*Uf?0mGw(YyYu3!H^;^HS5(hWY=QzTD z1Ox(c7+k$#4gwv)f

<4lx2PpBKWdfR8gSSIvzz2y+(%q5(~qIS>e~00L32 zgFtFeKp@^*Pn*m%Kp+NlxDi5^hvyVGzkrCyIdO4GX=ynD0ik2Zj&pFF5)wMYc}(!c zi8Fj>Wsb@!T$B-);g=UXC3*F#5#MPsH#e_ zFYwNtySBFWnwl^*HBBWY<@@&^Mn~WF@dN~^ z?(@n5#@b3SFfE@mf<(lI&;HZJk&)Rg%rQzC3jVYUr*yqB1+0iUj%I@ z9_%a~?ICkXK|qY3C-$Z!ANR@Yh9akxmCjz(6}PvM@%It8wK^pxD(UPX7a4p;SBFPL zL^k-=d5@dY-X84i9OvN1B1Q&+msA8b)lW%FlSpG?IyecoR5Rb3T z$;oSMY#AIR_4a-k8za}&51Q_!?a^gv}=Ls|8$#Lb$yus_P#~o|Mqak$J_#DDf(!*TL)isE|Bjw-T! z0gfGOkb$u-E9D5=2{HL5wX7>ZlUR_hbG%W2=hkvM{s7KsW|Kt$2sqx~F4Z?)J zx}Zd^s>#!0iQbLH_eT)Oazn8UN}b?Do>8Q6YH#bW<(M5UUbz#+hmM~-L)zpj)3xV> zS{~jd1-91#^8H`@$ar;EBk*sQkc;F(Iu9cV!+JC8R=fb>A&wcpmq0=h3TuvmFf!gY zdt$9$Fm_xqNgdjOi-mN#wg%t$GPNR}s0bb6&-8JbTGDQasb$jCQ&akETk#QK z9_>Yhw5|ldh730z{_ANubnk7=I6Z~_v#sgb5yG4oGw6$3FiB4B(--I33)IW#r-d=h zLJZmJXr{d{vAN@039XhOkgw%4R;g82SJXE@<@kN>&_jiorW{=;GiZ{H3GqD<6TW!} z2f{qKr?}gbyQNOhVn7KAp`boZ5kVd-m`!tnZP3$pofmr0D&>t26|szKH`>fT57McY zF0cy6b);C8Nttq%XZ+>nKM@>58Hw?qwYzJ5Kd z?!(l?m{;>s%YezSO0j8(bW4Z+;4N~iYXj>->k@ZCxAS_ny~=C>qyF;sp)}ANSs4yo z^Sj62Ldja(l|3Fw7&Twzr{DR@22b(s^*=JPxA7WyOXBre!_dUI>J53}DSlH+8(@M+ zv~@NkUhw4Sfh|9&z@`bl4(+$~_NxBm-l_yPHbW+2dQ7OXA61_flxxca-${O0mcRS) zUG{zaFEfWo-eJ!fVF-WARzS_*@A!QO>lx9o5*o^MRdQR^#?#)X@_5&0A0c=wBF72p zBQ4c!@L?i-Fi65nd1X_HnHW5cvu&t=RmBe(=Q%FGci%sOtKeYVFvX^xT4}ZuurP8% z%govtEQ4g(ItCTzp_Yj*Yu4$Wmt_;ls2=qpPPDit^U2DGNy|L0Z$Jc%%umhRDdLmU zBC?w`R~}HP<^@yF7w-OgapLvK7}(RsHn0~JhUVAl$3GQN=C9&fk=2&5%e#3q0!%GF zZi*x6js80OB8i5jU}!b;B-n*c{U3d{)ndD@D zIWu|6D_W>OXX`Ky6#qyF-aH`bG2Wo*{#3~Irm~r@jeM9p80~$>b7bCco_%JmDYqf@ z8DBg0kZ2 z)X6X?IiHmauDcwhx7WHjah7hQ#0wAMVZi$Y8#xZ=0IMEU6|x)N3QhBzF?U|8|gz4v%x8xfu9V5Sv zd|!6j|IqlB3ruKGxy2eGD9CcUPUpwUv&D?_jz=q<1HW9{nMv~yY>F)%?-n)PF1jIX zG}&J4XGjqybSiS~$>(yQ;OJ3yMwF9%Y(%JzZv6tw1=CTNH7jy%PX%8NDWmk&t6(_! z(m2<(2abzDtDKIG`;OYL9^NM%7Zcd9%x~b{ki<1D-)=pz7t-bXu-~_0e3JZya`Zz4 zrE1^XJa@}wmKltUykd#mT`^V@6YM|qrQLu7m(S(*Rj{nVDFPN%9MX^7#xkH_#=Do= z6iEb~tjc(yPDVW86kJxKinS*POqljq|LK%5c##nW9Mb->1TeKBdVx-@uwkII?bL03 zWVMvRM*p6T@j+R-D(rFa-RS%nb1r(nDdPmZ&11vT&SeIS=4UtM?-k>NZ#qD{uU@Hv z>H6?3uD{n>m49rIzpC$AXtnTr^su41>rWndY+%jumYOxroLx>||J`F7HAC;f!lB+) zTrKBi79wNI>(yV-W7mpssH4wWX>+jY)OLiE!>v9`6v2b?^EeSJ-gm)xcjg==H$nUv z!z(UNFxp+tw`6!|>8=CkQfswN1*c~BX=WEZzD~Nxu}&APBj{4q`tfekWfkGekfBk+ZW}yy}yq8T^1U+K9oc{~V8NcaCi_sy&L@h63x0YPWI8;&@d46k zTsCpbZ_e}B#YI-Xw@gHxOCJ4RbJ-`32KA+%Z!A|avCL{b1?QU8h2VTv>Nh$kQ11B- zY#`sWve@LGG*t5E@h#bIK5@%YlP63hMKdeCT31}f_|3w{uN?&8RLA@b^TcJQ3COi( z@t)43$H3@Q6Q_85#4>}C6@((WVJ2eq>#UMPxYGAquH7{~0tMlp9RjO*G5k-VFA^Eii~=T$ZI`A-h7=j`H}ycUzYsiDw}pVo(P_ zUbyrMd|Yxt6gOPYi03~~@H{XQ!n>53eam_bs`tpybRuvGva~~jKHw6c3P1e4?Yf{6 zn-5w8Q>S~$!Id#74~-sh3re_3nuC!a?LK`%7a@_?#w+_e%6_=SLst67 z4InKR`n(jiW~sKmOKNL)7&jQs+>tqu*}vG=@6kYO59quC^O`wIk*Zb;H>l2|@asol z_3NJb@fdmVmyOR1D8Fip`A-J9D8tgsJdavAo~0bph)>6i{>SPuhYak^ zA1AM6vRh7#rtXBQafIDiQg|Gst5_7zZ6x%GYa+N3Ul#bN@-mTi-I%=+f8_Jy0q?r- z!Js5%3H}AHq<8D*`z!Bfxk<Uet z9?F)D9s@2Hgu#KuLw3O{4hX)1^MJ66JHR)9yLZH`^XdY8=5;VbNPql`s8> z@%W3q#B&n2%ripk4o5!mn6bLYM7p#0t>}3cCJ%yTF}j$@fD#kn*S=*nGACToHwn|( z9Mw4LR#$^#A5o-Y>uG)p$@n_1yz2R*K^4tKKN&q(nI~^KPDEa>!j>XgiLhg^T#7v- z)vTV(MHrd8zp`H&<{ZlzQ~g>2o1@*`EXc?5We>fBL6whwX1mC|^wWVmXE7gtmuzFhx($Jg1RH!0u4m5z{%Q?6cPE&2{vIgpnc)8+?%7j*n+Trja| zR7?;3dg^kvrl4y`#9-MCYHZWw<7n2}w4=t*3ULNhD1+aVRUVBs*m{s+%be!shuh-% zA=0HDg761}%`al(5&AyLybiy7wfYw(u{bOzKD2=ji<0ZbP-L-*RrRm#PZ=J63Q>XH)YZ91=*ggd_aQJt8(}0}`v4*z@L%G{q z5jOOmQN1ddSi3GMK&mMue?o5-Zh(%9yQByXm3citKdLUb%45piw9Fq~m*?2D0;5$H zPu(h%`a=NXxV1Od@4L-V}P=E z)cKlV4Ml$LYUD=5s-cQc`Rq&AEn#dkMjD+tAFRjGVvw}AtP4y;r-t`eqsUW!v~(8Y zr^uGRbp?mHSbRjq{)BwAf>r2AI2X(VrMKeswZHywEnrJsjCW0*JFpU0{9jb<6bHXx zAqIQr?_YuX+zekvmJD4idOE{rYV|0-f_?upZXc8ui@LQ@%FWT5!cbxGD(&>0THkLq zcT+~pnUiJ;Dtx9AC8lm485n$O-LI9hZMM5Mdk;*g*`(AggTSCm25h+I1@=_)O4LYD zbUGm{pQg>_R!MoTzAC^4kKN@eNR@mTvQj%|DMG&IppHJ=%XsIBTRF;z`bnCq-Dt6T zB@V;SF>l4opYDmWnZ*@+-L132??BvV1nHsF;eIV8t|3gb;4y_J}=r`G8Uy$|_Wh-5%Pl;EfrLv1i4Gt*b2{pl8D}K zrC~R9@fzlkx9Fk3psJ5Lyc$l9r{KmdlmQM+SMV9(@*$&33q}{XKgkZkl5&b9x?Tp& zQtY9|UD+=&IZwxi*dogC(ze`im$F|21(L_%MKoLPjY16Gs^UDf#a~9us^v@7`vT$- zQ?3G5h#9!)yR7I74@zHn==ePhe^+y`2GyU3U@gDZzOz*_ zi@6Gk_$9h9b){~;@9wt`%tX|Uf=*1+3r7yh4cF4FIf(@^lb0qOsL816EO4geXU^wbszN357=pgyv8F+P?=5;cPI=8eVVF0Opc;$K8)uo0!;c z{wPgTq7|LVo_qZS_wuEYI^7=1(R`m}4J-b_`3Zll1CQD*xx=_-6uz{B+HIPCN|c~J z%?g8gD)$$_`DFwObQ3emXD?XbOTcc^YI5(zFpVl^yy0{pRnX%-Y=UQQf-<2)ainhKP z@9NZ`u(xiEd^ZRo8H9XbE=ioRN<-fBYrjym#rwho#T9d_(Af}5FwRylbz1cJ zTIgrIA+@BJqLyJ1ha@RmG|z*PADSTr2_qc1favHCc59f+-ip|+pW0-}XFrBI{F<`y z82tU&ghP0@i{wF{{kGE8pKWZxyPxS3v?Td#j8x42kL2Yzn%~SleK69PM{FO3bHB`C z#hFT}T8xz{&?b!%f{D0N$NV+yQN-u_ zA28-yD)?(Z#8b)qnk0_8aZ|y==Pr0p<83ysy7Sp-4)rP_{lbIIE}6M$UU-=a*u2vtPk@oFzq}H&1sM?-r`JmalXgPrq ze{MQx%ki6ODnEn;M)D1H_A=6czHVUd9F}03YP(j%f=QBL!-<^ky|kz=a;W!HYASqT z1!hK=I}XR!efTaQKh`0v#b7X-*9czk>XR_&xGf;4k6Qf#IEsK2Ru|lE=mwW{#>x*AA;PI*?Fg5nNbt_(JZ_`bN&I)7L zgS=QuI8Yhe#{O!m*{-F`GrT+5PvJv4pNH0|ALAW0!oek#`y=LCTI=s9$f`m|OZwYG zrKg_sC@oz-f!7Oa^2mD3?bNIXC7igx{^SaO{yXB1TDw++ipEQlq0h`8{ME9R%Y^Z% zsJ_q~0ckmk)Rc)nc{AJALDyDO8jlZH43nUt1Ep7H|)1NAzvN` z|9K>Z6;UA}tgZcIt|4kiJbL97il2cs5TJ_2B219aAQ> z6ryzU-rWw(DqZ`ze0wtFV?v&ZDvva4M3i)6a1v%b$b~DH1zB1j>HFsTiCrC(#Mb*1 zznq&=Ei4cl$I%WytB$e@9@P`KSwRq%xXgp%?sv!<8Tfu;y%WJs>yP8m#IAk*H$=$ z>tQ^2@S-9y`f_!LQ+XlXvMuDw@-8ocW@BK`&Z=W@@Fc-O?U{&E24(q248Rllv|Nnl zyXmFtBn<+2;SdYC?kCQ`KN8=jME2kZooplu%x@n7z2Xh7Sih_rT`9$lV}69hi4^Yl z)$}kQ*X@jX%S->fSQPw>;eaq7B^^1;9ajE{k#J1y+0?^Gpr^c)DB}B-J=e>}hYfMx zEkJMf%*sD=a);rpDl+m4>1qJwpNQ)K+FNcU*gM-Hif%AcYK|}M0C_?A<_xLlwMV`c zfk4pc;H4vHxliK|-nN_I|MQdt{JFlKap=i3pG)C+^;*`vKK&tdVEg^At0mF>0v z@XOlIL`3^K?Uq=4-NpC^~d~1IjGg6X$tX8n2 z8v~!UeM_?$Kp?{+|B81swy`0K{4&7?O1N6ancA{g!+ap4Ya<8GU;iUfZR`_>H2``lXhNfe+81$Sml zuVi0KIndFMF%C~PpHp>tt%^RCDl)yP)Z(J9TJHtnUT(Z8r8ZKw!i=lAH0x6clj(kp z>%2|jAq!sc$9XG#rBdHVaP_?XCC=7aK2o<12StQI3Iux6FmZLrR(`)6GwvtQ18oCVJOtCt$~MV!CQFii@t7we$S=!9hRL;4za$ zHbn{(^N$o~hTlc#TwGBZo{t?zH?@aP1ZIk5NWK*axpUZRQ@y((rih7n;w-R-7;axh zvo^kyVoF`GqQQCPo31D%XAVki(})en-JM|hOKl+47i4yK{;urzOdqwg>!xaX0|uY8 z-%$F7WAx=W)CCDPt@VOq{-nezN>F&FMDS!mg+zV$>Rgn1qxIndha}>JVLT2xtXbZRIxPuPVB`TEh~TD=VgckT+bDro&|#&vH} zvYcCJVOE-hPoAWK;m*q24%oLXmD$~`E~bz zw9|nyj>8|+6%tXs^QKXV{N)zc1pP?W`JbuzZS(DjDPB35uur^VCm7ms~hNzKib#B+gX zO)BREEzU)H<4q!HWo7dHO*>=HP2)sQ>gdE&)4Kjx=-I}5AL zTNrnqOZ2=dY}20VD7*S>vDPzN6Ri`W?IAjSxG;ZqJ(%Ps=;afY{*?)m83nPIwk7r| z2R|CCe76)A|6)bDT>RrJi(DXp)D>oBU0BdkWPB4m{on}V&ykqD2qvK*=B4@C)-qCM z7~2n)702!KVFo&v2ubB@x~6R{jd)Nldix8;XkW^y{X0UtEc4;pyjZ`GN!EL z@FmT^yYkXDH>;NTVaO=SFW*gSru=u93~Tr4X0-yf7QE}7sd#>(f|p*|%haJw_iC82 zeVods!0l~*qI2P#H5Ae~%pU=hUD44$*kE0NxI%U(@C5tjG* z)HF?4i2G__$nboSui{*6lN5Hq!5{|7MZY|_2EqOC3Aq?%JKO^MGmOnDZs?XWoE(^` z8Ka!2@D4{YYk0o-w)Se2WT`Zno!&V=`0Tbl=pL{x~ z$g8ByiPErMz@W1EEUOxS9@2mxI4pR&*oVz1J|oTT0?MfWXVz-{<9b?*#09Hx69unU z0U1)o#`_mV;S`uTDv=%yF!{XsqTDRR&;~1NWLfmCfw@F&w`M2a!GZz576sVi5+@=<+}yyTnjw(9QkCn%VxBBmC{ z*fL$CN(g>G!g5ZFmgEcJ1uFE96q{V&mTt+lugru~;P^S5}ZS(_GhAXJvN?H>28U zXC*IR4&y#+fox?Nxh}@@^C&PU;g;Bs#CBZuel2R4Gon{K{wO0pyVglF@k^+0=IDd? zzdPcEWGR(EP)3E64(^g6`Bj z(LXm>izb8d86VgHSoA81g8}bA@QYFCOVYLncHjvI14YhfmopPtwagR60RnxAA{cZO zXD+qRr0{@1C*9L9C}hP&G>-F;t9*rk`y&83jJ75*{O~2%R{eTpCVa9VZ7hCPOiF6^p|m-JFxvrMNvZk0@oh*7G3gOxI^`{ z`!O=`D`ceKrbgx0UV>K4U;*9Xb(_*JCm=<<{?lxIJ7Isfe}6M@VuyEcn|CklWYm74 zHa)jG3lnctw|iN`c8!IKvz!bVUic6}FZ>AfiIMnK)A28F*U|STH}-X7fIlJKg0{Jj z^wI?}tOz5E0T>Vsh$a45s!=TlDWyN2>R*}wu?_DXC>vvb;L|}^#DStglH9=C*Z-*I zzwY;64E0YsuFc6-7xKVuy2$AUTi zqRv-GoRCdSjUzT1LpA-$22%wi5y=M|QD1`qqqS`xfk{gb61ZxE0(*&A-_w7>5T|lR zin8@Mnhe|EM|}Ucw;^L4(?LKPgIcwEuVd`NUPgEx8ztF>`#1PxqAk6%OLUOGBcWBCcN9&K zBWNRN75jU|qVI=u0$@x;NAE#mZ#-sv(9762?}^LPLhVREl`^(%arBt&yy@*cx~Vr+ z4d9|Gt~FQH_vQ~jTQD(ss(JjVPYe2%vKo){>41euC?Ul@L|<0fenbh3%=|Pj6!)Yl zSL60#jar7w-eyGJ889mZ$JH=Ty>gazYNar&Fc^PwZyu;ey$-vtg>yQ77?No);xW^p z>xht0C7xR?D;+lP=qt*p>=hUduP`LAfeAmGx>3$wmPtX-rR*^?{ibbg4VazoRC%@) z{stZ%i=hO6{~T9+nQCNAkxPTScAo*x`SU0n76TiRXL=PQIGE;PxA;1AwRdjmrfmf> zLtBc{R+sI#59G&TlS{oUQE4}1haLxE!#VCs;4TWTm?Q^%s7!3F?U@K>rJW)VVfpMk z43rPCd5QcnEB!!42u*!uW!H;|=z-YlAX}uw@|QTgMX1!vvk)_B zgC$e+=ZvZ?skHmXyX~Dz<@B7^vf#)@moATH6b1( zKAPudmlSTuiL1x2y+jC;6tKzsTo58k-k(A2eMQwxC`8@(ra58q)O`cjD+m60q9GUx z(S*nBRM1x{oc8bnG6X#ilxnzB!%WDXBPK50>}7uPY`D5CeE54$h&Lf*U2?x2%1oSN z>KnWaB^7PG!!(t-F+B{5-`@f*nSvj1P?4JV=^^Pg!^1~#EBwUU`D#*)FCs<*@RssG zwk3|{lVQ9=&_tFV#U($zOnGHhBRB<*wFf7Bu#VHwDA5eY?ext8GyX zYa(61554^1;ejK(z7xQSTY7xv5DNe{aBJ0x@Kmj86H z@?}TO*7Z@DC+!=}?ZFW8zT}fd&oB04! zuZ8;W1^v%H?;j%6!Jy=`ldU2D@ME!$E-ms#t-SL3yNJ_F2tVbv(ix_v0DnmGdA*M;~=*GZ~?A3~N^6nQFm_Q9Cl z#>kyNUhmfim~_O#U6K!8rBEKOh8srkWO&_=!|*X9qxEvs!-;MqipU9scox2?XkF zL_-O8D1%RKLa72ZS|eRG73R&(u4QGZlvIqZg~Y4#Sljj~7Zx9%>KQ8WW9RZ|U>8GN zZI1Fy40@?&@7^DH>ik^nIJ_cxh^_FZHvdIeR-I?>waBwz5`enRs*|cGY!*=%4x6t9 zK&kngJUbq^zR43f?|6idypU;&WFJtGVil~iL|ONkH^RqQo4G_fpt-CCL}%yMs!1I+ zKN#nb@%v|l)2HgRaO|nTiYFRmu(pnm;7grn$zR`uT{6Dj0#5KOSKKO}f9Go3L`KqaZhac{um)KKgm$*-qf|)Mh`aK71HArY~Ll z1y@wgo%HP&v+OQVT-XaF_kaUr_T{R)%wc2?uJ!d0(_cfgiqg-6FNs;)2zKFGeNY$% zCmn4;5h(Y3b{#(jx9M@U#T8ik0O^V$Bjy&iO8k3V5r4p${Vx&y^?yjLjVcdbURa5v zQ>tl|WL#GV4Qp$rO~NL{bO#5mW($i#?16-A$>$XDTw5|L>A4`w#`nv+y^q(yAn0RD z^yJG-ZF3x6SoNk5GxtLvPd+nfiZ_MEc$8`z8(lz}jhPF(&GV||Uyl92qX@7Y}D;`=!o}!GcC|y}qR|6U~lY{=oKFQ{VBEdysEow2iBj zuqLL289ST<*R0#HF?4}k^3j+0p0Iwe6WN95pM zG*+Kug;9z7wi?fpXSkm?OGwlo!67w$@l}S;#eclA{5$`f@BHwVGuV2vrhAa+4Hsl} zeP_Jo$FV@Nr?HRFE7q3BvLnqAI{{IzAKkwsW#b!hYcg=WRI%;Z5pM#Lq1sZnktDSS zCo}&A3=<|?Z3B;yX&Som$d%gJCY=@=iBHLRJ!II0U_RrKJne)-I7XX0q+r&W?g3~B z!}GTt*My1ce-b|v4SC|xT&lVZWHWA@+uOa{6Y$(JH#+$l%?tuV=E@{Dp8!);CE*#9 zBh-=zH6bqeFZRC%FF0o_Wqq|86&vz4DhL1L!qwcbVybZvp!6Z7f&ZS3RUP`C6qvgF zcZ z7be=PM)rYmIY#Xu@CRuK0Oz76a?Ryiu9Lw;t0*@AZfin?Cr-Sn9^lek9}y#= zf(@~Z!zx{^P}{=%pe~?cF)AYbO8~h@xS~t0yw3 z1+g89&LF`jab{IlpbkoAZMH-}S*H+?k~r;oyMkTE!K?!r%8_8Iv`Bh1`Q>8F?7|J_ z+KiYnWs9cK*y|_T_==}3xK>%g=n*vaMak>$&&xaY^9GoSZVHKbV?c}ziD^f#HowT- zomK76!jTqMP0{P<+T9(e=C9DdyKl!0Z#%#0`BL-*MTIxmh6hf%V*brT2$5l=-(FN; z#MM?VRVA^59j=5uvR|qfYSre2t5>UMUh8&ylunsHveFy1+AHB@+<%M>cgF}v_xeN% zrLO$7gwm@$`n_rvHU&)znNBx9J~lJZFhJF5qng~zCCA*?uYVWFo_!t{^AVl9Q#AZ0 zFE`Xsi;tkWv}_tH@uZ89Vx2MKOEt-9m)wtg_&Tcjz8_F&HLP%+3*H&C|9g|zrruFN zRcS4}-%+)E?k{E1AcBpieoNt!ZFSn&`c^aWYbd$vr^*cou1KfFw|JAnx`Uflz*@c^ zlvuk^w87dA&bQ64fMpqGF7)RlAU}xAfj{pItL(6>0y=H@}{d80^0I?tZs$ zP32{=KY^*;as5^;D;JtsVW$%SJ*vt_l@1qhB7KhDEWuUJCb~Tv*Zdrz`))tB;vfKs zHRyk@GArr@*S(R)J{kN@2~4<%YW@`>P5%|8Wb@MyOsK|iCTtTL)tVC>M~G!{Co4ewt8#o=LjII$DRR*!msMn$+JU#Q_AL2m#$Kz z>o!@q%Ya8R7rkfnJtL*xm#L(r#j_IqBCU&909%$0un;stC(K;MI4zDL%4;c--)n{* z3D~$MuEx46)KD}EdmSsOTB#8lE&O_fzp+kL+g`sC}{{#4%5K~qU+K`zGA zC-6ry!T`bu8s@!)8?q(&3n1^4)&y!Q#8X$FzL8W-7@bQ@4V>Sc#iP7CKl#5d)J~3A zJO>GlRsT$8Cf3Zwgxm#6TcXbVwF`YKQ*F}c`bV^Azu%>_;vHp|71I?Ft)s{N zEm$nFRU`6wSP=c*l^p{ULlyVG##ie=$sH=%+uu_xT0ZU4oPfFo^rj?{L%Z3vB0J@B2^cUIYPWNWP@ zi8=~id_UUy1emb?_dx(VPP4n>L8n^ncBv(2YW&7IY^Km(PZ|wIZ+FxpwE9COh6 zkj}0B5Mvwum!xcDCIUo%Y1|Q_EqO8ZDXy(BWwmhc5U%7OB3eNO)oTwMzZ1e)j-+uH zpKFv9MoY0?Ev^uOVYFk{x=+la8OU~J;9?I zLAvy0M(g{)N?zqnKPzl&yuGP2*ftL`_mRLhEk_Ufu}{2d*{wdgm&ftiG=4vd^||B- z1|@7PO<@L8Z_nsOv&wk6yYs$r7_ap(Vd@hbneN3eY!KNPD2%#L^2L5BS^|Hl({Z!g zN&rDWdv?Z?T#W!v*2X(X6f(F12*l9EJ!C|V76H68*4(VFJIx4Uy2ydEEL&>weIf6t zR&5!_fI|CyXy!%1* z#G@S(`-XW{;}IY3y;6TW_SErGGP$rV@Af8H&Nvck;LQV%sE#-j6ECTTmtBYDy<)VJ z6KwKXnHC($>nmXld82Li3m}qsmOjx>O6C}!(}tfo?WTK;^>a=&PoA(SvK(KMyg*4x zwyiw5m+ud7qE%stqT9lfj!_L=K1U^EBc(3vR8O4LmNJ8C#^v3hG}YUV3GwU&*8-$< zy+-&=v)e~bew-q_`UrNt;fFaNRZ+Y@|>xrf;_l`c+B^n+m@@Lp}zl@5*aI_Gdm0)#pCmi6vs1E-1d+0N_v9 z1C(dm+~zJOs$!1qO;u0X?OzI4j!51n=jn?guuPV$MEbht`xx8F<=#v1l!#`+yO$WL zJD$z6h^qXz0Z!G3Wdgrnnm|HS71Nbi5koNZSz(5FH zRP)(#wm(fIp-&4Zf~7c?N?|SU94-OA`*NiD!9bl(c z&n)e|3RCU9se=aGJ{KdH|g&cDy5hTQxOz zZOUXAqJQ0BK-I1{e066+VA+F1w9h?b5PL52u?Mu4H8|rG&0qZFs=n4${KzHcqODmY z^P=c2(em31Ci(-!#2MZ&^rusK4`tLP0Tm*qPk_->h4U*liQ2W5^xgR~dPLPm3$PS< zLffvFjiL!u_EB#cFwMy;P_aL=VE5w=U8u;M9dvuTKX`?em}x)PDTKzWHpS6LWsc&Y z<*o{5G@kPPqHB8 z$LhTPwT+#cf_Q7%h+0vfvtJCASE?vlCC3Q(PI~v9NFe#1zrAlWI6dB(Lu;kKnoF?$ z5%=m90QPTB;xLBXQgBrU%DdlreqA&3Dp~aMec>3v51uC?dTs7%EGP(o%KS zaySmx)oSTJj1FC2-_7pv`}T9cv&v_YeTMD5s)({t+>8?xS`-Ri9vB0#Rbh+(P%+7kvcbB&a`Veryyckf~_}L9(f(YF^2^OLZhTs%h_=v&$Jx&^Q5>Yor_4H^tL0-%2p#<+2u4Pk>rXVDnpD)}lse2UIc}Vywm0;}tSlyH12>d= z$%Mie{D3@&Q%QDvS_gndWDR~MW8}qp`)%i2_5e2YE$q36Y|j6NNZ7akl`qgk*B9O! zj6b_e@Bqr<_Zo#d{qE}?hqJBD4XVW!2SFVS#Je@Vjl; z<;oaM_D!!A?gs;oLOCob!chBKc2BIc4h>E59521*xp>HVYY`zj(jWNIKKM(ykgV8d z95dpl;mJ_{;Y0n}p}PS;KFZ}&(|#?7h4$QlW^|(Q#{QG@Yrf9|BDc&PyCw$SHm5eX zDMe@wwJ`B$JxTb2pT1(XtrVQmav^NY)CGcL{VF{f1LM+0;-Iyb+D&`fQT&0qkvaVE z2O_3Nefsf7W19xzt%s0cnEKg1kC>u1=?LEza_YU5SJr~KP zRn{BpdkFXOZEJB0DxlLl<|}y~peLwrjDi#{3|WVj!C)V|1AkFehaC_sgTZ~QY3w*Q ztFi3t*))aBY-?;O?r zxj*0Md0xLi{86uKuIt>-^Elqe`#7~DZY&*J{&Z7` zIm67=TIu|BiPn-Dxa1#95gD^7swfzoauADs7FwhO_ZH4uSXFI4ibGlMHAb3ccG-x8 z#ua(tqch<5uW)aZR-4S`hi1gzJbNlPp6UZ>dL>ErXTO0xo)+>P?B-tTh-KDysL6RYrTP; zp-`8%YYZv6ct-!U$~)Wal~lvwxa+Tj-i*!2am$Oe+=d4VufBqPl&B%Ot$q|xy!oc< zXgKwo_{h|4PafabgL8TQSh+msf#5}_)P!B96?kD4(+tcN=P>!|es8Oy{9+GaJ7Mb* z2rE^B!uza;j$cIJS|%f}AVMJ<%Bx3POc{~i4?ucRi>aLEdl_%s9HS~%CtxIdWo*+m!cnZW~RjgT- zAeXrVxUDNIt;r8cJ}LT5YEy5by}3K~9P1Z)8zfVxVf-Bt(T?!afBepO_k+Dy!!7@< zTwN=knEm+g^0kS# zdMn3v-tTHDJA+GUO8f$H1(&Z{CI9>^aGl7W9*4_vGP5H`p$5Z@vs5c`v^{1OL~0r= zjv5`*GEr!Ad!hITvHd1?Q!Vj+sc z2bSB;Ckj|@D;g|s<-`dbvmtv$L1L?c4Z1Qt?y`HBR6TBAavzKZURZZlLI~CzQOsG* z?vH6gUae=}C6sUgGr79F~I}mN(iS zLXJPH&9*<#Cq$I6I}Do~j~VTe=|4X4J*=j2?~+K6l1Q*0l;9b6v6zTYO?@T5od2*{ zy@s85nHTa}6o>~WJw{%;I%1I8AIV>&w?5{4)>Sg1uaa-h@5ff|@jrFZ3HCHw`PrI2 z?m)V;-NLmNBtCyw`}7q<7W~VWv_h0+NCzi30&^G^)|{uT4d8+2mpV6~NBq9EMZ)C` z9a!kT(0-?)&th5sm2HiA`F_g{*L_&mr-X(>n?^?vgm7G^yEC+AjnfwH`7N4vOV!ty zq;3nw(0QY})}%sltpxmh&lvW&G?1R3g-m@GaG&l)YnISD)UXz5D_oS#A{X zdh%XuJAs4iPCd$U$ycMU_ZS9uXOPYDETO_kVR7yWt#(+p$JzZ96VlAAy2+BXFd(yS z&MdCK24J`kC*1~9FGnk@psG_E>1X&seOH^k$95dD6EVa zO>TIVCNd=%s=wxkwYc%2`-|bKhCW}TaP8AJWI%f`C@h>W9LL$`dp1bM2x?Q^ zI`FhFeDtSn?l}MYZ~!NLFK%*VK&+h;BvGi3QG;>}1X=FHlwzx1IgM--GJ#Z8$x?Ec zNAGZ+O5s)5TwxRKy@oM6`t3c#(<`&B{CHRKxp{%E^px%n1NXd~7mJpT*M_ZY00hwZ z6(rtmdJFA^FVpI90T%%aLgj84qlGRVVHT)%GjmjMU3J6B9Pl zjkapO&QDr+9>=uOSW|@co+kYIc!|MtS)3qBbK4*{Oli2lmyaEUJ#Zg*&w@DL#KF6X zNpQ|=GWfDvI<`ZiA~CONDQs0a7x7RP$ApwkeWC`Fur(Aus@8wMd5-wiEFDob`WfGT z7*>Y%_lzhVZh#>4dph2ctw~F8L2UwB!#}gtB;3f%#1ACYJ~3K!$z`4E>1~MckGAQ^&;iJD@H6|b!JaGn;e6n7$%`3mR zrmD$UR-@BT8nj(zLyWeuy@qtBoSsJQUR19<{t2`IWL5sti)XzTFE_~vj;o$DNh+v!HR#D>k@FIDJG}SE4~vSu=@RM zW#y8?{dT>Q&mw8VFGqh#?o>kw>YHaK#euD??NLgt;txIud5ZK~np+q4HqvLqrdzyk zTY2n_V0)ro6H$ZyiH^_a$jsukeQPr*Y8?QC$rn>CGKC@@YZwocP?|qvIEa=v(&zmn z%)VkyaXqv03<4mhD90Z8o zu((C>c#>st2u9fDD|G}}cLLM&ybbKr0svLXLite&2x@|_>{})3MZ$UFaq&L}??kw*$FHZ%O;f3L= z(2*GGW&1XJ!jq+z{B*6lV9YT)X$M{zsAxCm`HJ_kg2`aUM442QV48s;FK76xN(H(t}nF90zg{SB?5$pwRepEI8T>f*Zu@^K8$ z_?3wAVD9Z>8`oYyIUz9iKR@yogfZmOIV?K71ws$elk{={b_Bc?^PJuyU}@5P?VPDz zmE)Yb>z_v(Y`W0!Q$vW~yYt&WO|ksq zfLimM*#bpsfKaRC^V%c3OkEBX8WchHn`yEzqqxwE!s*rXpI>?zvUFGL+N`&?D1)S& ze}=|xxau*RtTGGO{Dl#zSAL0dod2m}1uuhVweKMx;RAxM^$7+;9#6<|;0bN8)HX!F zMKRJdGeIZ6QpK!s3@-4Hx#ngsQ1v}-jLo{d!CNK8|Lv8m&d7L2V(}zMa9OVn_6k~~ zUWG^`dQb*&;6Zz{df(&8?eK^#X*3HQOU>hixini9zz@NwMk(Or(f)hn z?c*BHJVWb_URm)DpIGC#bV>gz_O?S)&C1HqWcYV-#cdsP1X|g4p6TA4iKBbaXf3+< znoC5nB(;LpzHSAUlFEr(Gu#2JD+pY9IGy=31>8)aYO0ViGxRI=&MppK&X1M~*lcIF z6rKM+XRm@WXeW9wO6TNYp7sfN%%&sn>{-SBS@+D>BD9&T8?)Fl?SR1VlYMfr7o zhKQ3z%%v%sLbX72MJ9hZ_2G?E&@kCWsufCJ?mHd?>Q^U6-%bm+?en>5-)y>4~ zCN-$~jA`CzbWQ=G(JnPL2i=_Zx^{j;8EashnyMwJH%acaTbjLoV_Y1^I>x&la+c$C zp*+TRvsUdF)iIxeVcq3KpZ)7X@Dh)4q#NQ=~}7gzR-h zav8tid+KzCQ(3%|C2YDGXtNwH+X}v(d@@|eDUoRfxLCvXCt2d>)(Sdq$g?O1wO+d| zLKA8tI60Q+Rn)Jn2!AelN_lt*8p!J(R=ASAAthU3sR5Nr&0rvDdhyn*j!p5y?4BK7 zRmzsZ)cz{Dd=OSZD$2GCv_EgS2m7I>&=**B$i%qeb6Qa1Cx}-TFt$Y0U`K!u*N48A z`-)hmRDh!(ip=msgrIK?Lti)=LR*$!UU?mke9hMkQGx{bD<4TptrJRYbkYvLt?&*v z{fuA`@Th2DZfjGURhHS$k37Hia|4qT;qNC;tsQtH?eX#D=a`73T5DFgiOmrACWms- zs<$jM)SP_ZkY`O^Rb&3uuPAno(d2j?V|z(cJ7GaXy<9b8*UPW`r4(y<;#MlZzVxiX zMR}dFwHBVIezRWpy7hTxNY(k-9OvyM;3{bJ+JT`Y>%@5c(vU9;K8_U2C&IlO?)D^X zZgwJmNkth`yPB$KWf%~A{(PcciBSq74A z;k+QJ*Uihg%!A%-y?P{q-rJ`d`^n`J_6d95Ol09M4Bu?>c9id3CX>ILOSP_w*XOq1 zUlG(qS6e!4TNsRgm6}Jaf z8~oY{T7&H$Bi>~?gE=3#T{w(Son6e&stuNk8>)qTNgQ%q2|K_5WDE~9rS@Z~=JwTU z5w7tZ*z1wKmWWB%A=svJaqB2J-%@&$xxw4VI1rL5s9DkQK^LMRvhE*&>7*=X}v7 zwmC;^5HICp?Ft%0@QeC^Czc)A1vxF73($jofiZFZ(^DNGY=)j!*-Y9w=?m0;EU)sR z6qXguNgmkKT7D4+Mxw`dQCew;s9&1FwM^S&vXkdmnrr2e^6LnC+^oidfw1!F5mKZ# z^jY%7kvDiC1P;BdPC)e5npfovZA^MG5~;;12P7J&-9Y#8M=CD}6NS>nV{7(nlTwon zAGQy0Jna{5xi-L|9LFC9q`%$0DlB_tlnowfxvJ)8_F>L{TFmXA?!pzVtyCAvL_m}# z=RC{^>k&C2ooNMKS>L&_@MD{MKi^R^y&$jjK<7jFF@vz=cEs~f3%l{gK*g+`N&HoU zyV4p*R`wNf;DmV!k*G22{xdo0STa&1_YrqjK|tLXmFpivB*jhw+9=+-P7!a>h za%xkSZ>`JEpp`=kM&iOkF7h%VU+~-IRN}8)@JhN{W0#41xDY{ZPf5A161(9;c+Ox|}TaJHj!7W- z0OAz@GC#0TS<)V>qojDU1P=60u++17j2CC*eoRQ+&7Sm|C-~Mt(~+8M$Br;7U>EL9 zL?hvC>@a066R72h~#7IV@(iI(QbCmA+K-f~oXpsT-a|fFO zR>a?5|~74>C|U?vIXO>lfe48+zui1niHg{B98W7SQUg| zjZ^>*?h4*c-jBH^iZMCMWKV)B{18oBuVoUFFU4kz(Boydnt&LE!Gnz~tO zb(;$oy@EMAix$7Fgt&Y+o1Iax_xiUVs31p#(`Up~P)%h>Yt2+dBErrZj-aIwZTUM0(2t zBykOMmL*^z`>?X{t*i-aha;5bzPReG`z=X$-O+yb3?B@4+y{>@mj3Dry zz;l?^S{Bzq)~>Vd0W@#`R388&iSnDv_NzcgK`d8>gpPrO(_MceZAdyRado#y0?>)&PN;NPN-R_9I zf+AlfJ-%w}(9}E3gnRzM(FZRf$CYDbxF7p{iWfk%^(H`+`<*{gCE=g5cHcr7lTc^^ zw#Q7_UjhZd2XDxGMc$;7G`F(S-yi)h^WDm-o;npyaC_pM*U#gh9Jt*u-y2PebXsaZ zchLgP7q}S;9rSKoG>ojDM2;)}RD3SQ-Fe&TP z#+A+h3eyF<;BCoUwC!3ybySigPVDgN+)rH6ZQmy~?-Xxu7ztFXXT{WAQgu2<_T1VS zc-vpQlzRYMI+16B1b!Bd8qkuF-gt<=|KYdaLE1OR*@mgO==VO#!8;hRee)3=R68yB z!aUwG&ibslyF27->&t0PRcBo`D5-selN;2`E>rp!;7k3)h`c%rBKQT2WxXDh#HI$? zjjZv-sf^L__P=Pfj}7Z<>JxhbV@h~Mn1MH>bg6q-Mq(omL_?0d`&`)6HvIQZEzue*)d~uvE2xo5h5&l7`$vZgXhP&6>rcTnXsQ~#lB z{!ixNH&gR3YK2ZdgzC$Y08zmOI1(<9rSpou(fz{|=*cnvhn%7OhZ5r44F?_rt&jdf z>Hi`)c0O|k^9yT2PZTlU__ z)f`tImBmxGx9UCX`vdzujrZLfiB5yCL-O8&2B zMDkS#%nJ3&@Y5#rVZS5@>dZ6ymosrW0uxl6cSDxgD4OysU_XdYT{TXn1AJyW5m*fJ zJIc``-#iWb9~q34wkNf^A)yHWD^lLaf}f#~`a^eB1e};v12ovgLD4P!cIH)&BVBf% z*c~gsQsnyV2$8Ae7gcp~{jiFQuo)5nNtT+nRu1ai#E6pdTwCYRW(PCtnG z!}rm9A+>{%s3CK<^?co6$$J!koGn`UOU8X`Z^a4jZw6q`Fl;i~ngEh!O^^XSPA+Zqc{BU5m@ksB80LW z8N3=ToO$;^;sz%JqOps+S5Jxsk2!hUT^#X^0pw{nr%aG?3ZY1|%^QUBnF7ym*TI~h z^Z*9)-?USYLMX=^JltQvRfJ%yx0&(R60+Yta%A(7zPSfm8fR>STo+iP;ok_^R$Pc# z{Xs0BQAD%S`OGeGzeIGGeNa((pa@VAPgX_`f(%w0v{(V?hGedk==2B$o=GsphU7=bmjtM$=`>UA?H7su{B3}V&MAj`bv~6`ASdKD zTNecx-H`4r1)+q3Ks)MtC)$Kd?S<;dSXJUHurl029OH^#)ozPrJP~6+%2Deb4t~po z{E{qx8snwEfV)zr0ZmTmXh$c+anNkN+c*4Q>I84BLwL5yTPxBBVY)Y|zTobT@mI#K z)F-)?3ujEp(?qSwiaMM|phr9t@xfBrk1kb4BsFAdEVZ_*T$sKhIcY(u*NZygeOYHC zCd(!->Lf;ZoC+#MT+d`doa&FRP3DK?J(v>;zii@tSnVou{CUKst1)Si$fuf9y{YEH zTOHG>+XK1BF|p6tv5|c~`0Q!P-Li-WmSuv{mV{Mm+{Ym4qWi@5vmQ`2x$m-ZsKI3_ z+ND;d`f2QLyf&CnN)n93v38O&wFSJWW8Ajt=|`?C&XJ=8FnV%?4}yCqCcg#G&H%l8 zir6Cy*vhZ6)L`0JdzWx4q+)O^n}P>7GMLOps&D&~ez}5f5#H_~{^=5;(*Ch5SL*Cm zEH6zm$qV;=Q$KKKC4MQ@c8+CRVmpntn6VK#{1XY3P-j9kCx)`58`k0~Ur@5Qt?PWm zSViN5@|So*FOX8@Ie8zQn~qk_mTZz;l||Qx|af$GrKh z8D$_oU?D4+abQ>a#)DrVoD?h%GEwTgdL_R4ML(EhKci88plK#@5Z3{b!t^zbzo?J( z6p&wz%*`I~@5wnX63h#Ku{e(nw!{bB2P)uA2M>h?I@?AGgz+-r@}c^7vMCy&uh?0U zXEW_N&)8uVu>cXt=BM+f|i?j;*gN>2q;ss%*(}+l_DK5J%5;Sw@@H26GRo z4XSf@FJGO|JJnh`{NeyMLT;r%z^NM6o~=Y9VoQ|tvqcT^1ujT$?bq%Mcc@!+GV7S- zjMl10QQX%Szrm77B^4Cx>Kl)#&4|UGp9ug!()Az0j70SN^eO;UyM^XfF(HT6c{3qu3Uaa`cSN$hD}SMvm$GQt%Jsbs8fF@&??_`CxVpXS z)P-Td9ND9Bx38DRKp+70ev4fOKe}S`E!miedOS{c#IxBR8BZVMBLl{Am@a^gXfpaD zCq!pvrE9x1lNJt~FXO;f59$mml=6&nlW7rqs9KQFX*`Az;LlVkQ;9Xtv)nJ2;3S5c zp6Hi-bM^PcyDh3U)Q3m=D&E^JvAC>cGbl(l^))`b6i-{&zNFZp%|Pn0HmAlQ^EjAr zw*GmetpT&ZMR1*(yYh}v0tAJ2hEq}BF|k)Yk!W@7FAg_gn*p2*xXCR%u;wnR%$q0N z*ku&u{yEIz_)u*%#_p7PURe^yU4kbCS-c}Xtbbp16QiV|0G+47;D3Hl*o z&xVwwe@{_<8M61}DE#!Nd*`!6f=-?PXi`o+p_fqkZ0AG08!~z2ydUR{&tMGfSt%16 z$HYp^w1Kx>oDegf(kC4AcpX$gG*w@O7&+%#rSJ-!#7e5mR;Ad>T_K{cX-Q&eBVb*) zIqeEjWFWZ`j8xm^B5p3yi5e`urL)4&Z__NawQJkoU*8qT$?>e4_I>SEiiw#EQ0`1L zwiChXq6IU&oj;=-M2j|u682iN;>lspnep?6MU1BOZY}X8z@kq;j%%K;emEh zO+*eJy~~xO!4_(1fmXI_??EY#H3a|I-2DD>EecqmgUbg7gy>BwG1nH9cDQY4qYExJ z@Z$6J$}@FSB`2UP-_y68OR7{WGHWgqk)!b!$29OU=DeV|J;uUG3hul_^%mWfH&K4m zBo1?ZjSVj3!0p9EV~U-P%c*X;95}tVydT93Iq5wNxN{O<+K@7PB;3X;&d|s`>A>Np z*p#8-YH$?(WXdUKu3MhyMML#Si-Nm(Aff03IJPvaUL_VEh*U=GCAr8yQd`FM);!iu zX0JEe;&tSz+lx^fbDcE{x1Cr0zE--V@-d>QY}(3%$#VJc7iF+V!!Iu)wDck_)=enCIvsw=K4ZGF?$P^EUW&T(I1BVKF*r$~^Z(ZNtMw>#{_o7!gxjA`=S zJ7tO=-*2wj%Y}-*W+jybEt;%;-qhVTMwkjQcZC{{Gn`LiMX={WKWfX&-`3jE-8?-T1o>vQI4CP3#uAZuY$FEnv2~Ol`=PP^(ypussr)bggQ7{&{1k zS4WfzP#2evdV@omJtHwak%{C<&EMLoYQthIm!xOo(+OGgojzicN9$x5OF7?^1k$nnF&r@urK-8lkpN3)(F~vt{-D6 zD4jHZe@hhyEm0FG9NTb^nNh>?sGV<<#xEP~VpDABxqG83QF5llHlZ5i+d|JKNpXP! z_L0zx4cwva*_Py-xMj|_pug?j`gxazR+)JoGyHQ_j(rv%tMkfM|49p&`W6cnz1-dI z$1%zVHd;fs+Uv|`tV)t!Dwi%PaTm6l?CtZY*6BFh=u^7&otx@gGffBiRG>b)U}VS;Zajj`498M&!smIaGEX(pFpSPNgp{3c^zUUgpo9ARZ7L^UU>GoIv zJ@pAUO6TbKuSAC6hHA_kku8WV0jIMyd$PQl-RWn0r()C~V%ey%J7F)@BYi`y7gxZe z2PGjLO?=$DLNYhX9xj~ZgR3PwBDzZwCVq;dG~Kc~4WFPah1-M%3FZPNIe~@9qy^n_ z=yK~aHDM-}Cfsk0;tALMyJjD^;7^%@fcJAr z283zc*_n|zipj_Q)6WM7IGcW+^aeIAtv4N1^E(BwGwy(`CoR1>j~NiPdMt1&C!@CeS7Oksgm&ZW7i=sn zDsR5vV&LYW6C(R}b{}TEjap^UkwnKqJ)Wf|=;en0ORhNzv4Tlg5n zT_Wy-ckW;Lq@8?*Nj!aHKYbP{W7I7tjIwr>h5=+`sGdHa6EYn;{&9RqYVZd*&ZCw2 zTN3n#-S&;-lVwFyy0B?jHK}*r*;-U`noPp@2WqF z8qmuQAL(%Je|s-(r5?~sl60-i|IJqHfb9Q;>Hu^LIH&Hk%Fd5Xc0@IQsE!?R3=f?w zqU&6C1B%AsrO;my5aZ)7Abb+3%)C$g|ET>JpmO=sjDIPr9cJTCC2k(T5`KeprLzZek5kV#n67DdR%xizPq@GBo4TV~JEaZfC#BW(XE zz2SL&6Pwe-$JEyKt{X=INCckULK{K1$$dl2a9(7rq)Vl`C<@px z280>rOevh|Q}e^fgx#`IA1Y`)L(OuwoA3hAC}ebQwg@%Yv!57s2}(%5m5%buP)xL@ z^wfzeW4=-8)6uCO+@CpAm@$g6IRvrx4(`nvga8|Z8g!`bsL7i~NY%3<{&H2tyYl z8QH#lgR^eixKs98*dt~9l86F3lQ#%3z3OUWAjy<+^h(%5gK$GQeuRRI zpoqqDCOSiQ4kHAKJWRAgoU<_8jd#+Jx@ji@yHGN70~)T&fpxr3@FWIg8MYr0>tA0d zgf$hRaKgubUM?8{X_kSjA=#QE>uAN@J0!GWz5;4|B&%GjM z{IUl`4;%g)=dm!5teXwvkGOKeI$ph{;%%{A;O3`^cR`+|r!ajTd=A-S`dd>oy4xW- ztcnJ8`|g0echS_t;65cTC8>TF5aaWCCfkQ;I!qjrQOQ(T1{Zi3#RHDDe*h|Ly% z@b$wpT1qzCT7FRdSC!^ye$}DX@OZ9c0{+Z@imAVK=1S#ahyr(a34!B0Jdvs2We#an z=qu0Drnc;EHb2GA?=>E3?S1&HJUpZ*q|Wb@&Yj(HDhcKJ{xdVR1g; zHh)b1kyD;;2Kzv5(71MTi;jMwvSsHd-#4S9;usb!RuvYrJzQv@!}ta4brkkt|6f-e z_)j>?@+0L?vk!|YNdw3_oRA{H^t3sr2Sz9=1LR#^WsJYchP|#CE)I(`kaL(b(ka`E z)j&*1<{7WVI1fTd1?>rObIf=ESFXttcq!caTcStk4F;45bUrrW#Cg+6q*d-Ml0abq z+$53pZqD>q_nNA*rTK~wP5vqDkwdT{^h8SU`EW8D;#yAL;d`E{_p-(_jaNm;wsgwB zoq=!)Z_Z5Y38AR?$PyQcN#HTR*Rg;x zwD5-!7$F$wkn_0rQTIY*RMwS3adjaqve)v3)osBV$rZ_b@V-(Q`K4z;5RHY7P@l0J`0|E`cPU5NIrjOU(dbVfk=Uzee&*Vclh@ zAUQ~`d>61$oHjb7xBjEc>mE1s9G$Qe=|>U40g6}R@Js{_s=&F(0l%kEXgnZ@6Gm{t zOIpUuC8TeCTzVb#w6gZTVQMnU&k3BPwL#F~wkq-c{{yswzWk(_c0u@eH|q9oq#C0K ze|h?F&z=Xk%bGT9n953OSht#*mf(9LA;@jH1PD=s7iTC%^pVL<6s5!}^vQTl{08HBN z@+S2dZ)jafe{6l`9&C9*-xAu=v-;;%%*3=2Gj8F+#759W9H1(tSN%A-INI%(fU%Lo zHf(me>KI1IW=27*7}E#5j8f)mI>C)u&2n~#*iKgbcfExGy0t<`d=#RK8MZek=)nzo zFb8mOL6eEXN5{5w2G;;{0vR5Dg-APBvVqRjyYC{xh-^Iu#BXb>rEV%pFHi}qd1=zS zZd*Yxec*f$pk`o~S2Ft960Zo@#fRgonrzF4!1P+$wtXwC-|Gl0YmJ-@@ppScQwwt8 zpPcjBj8R&&aZkvu6xmm04Xz9#uIO&B;uL|VdW1XfKJv*Thfj~#zMZ|s6G|P4v=35r zczQa0gHr)(QYHl=9u)67p0)y$enGwUP1B<8Fl%?E~T%Q82dxOj+PDhIN$gr@}$2 zC8zg#UWFDwKR5i$( z)AY6W(aJ%11WU4=bgz(7jK0!mVNu{3DVq8}otj9Zv_C#gh(A6}$=}{>yvybW5(b76 zzPC3LHl_nMqg@M1#(-ZuTdVI+Y;sf^WDS~sh4$6yb1ES9tZY7{Zk4NQyPet`&_CNXS=1lIGwqL zA84Jt|7qfchh~`LYgbxCQgaogE=pqd$-bc#XqZh9nt`0pFcT#%`~440%Zaah>5yhG;w55K1nW_+p2S78O#9mA*h3hoisH3BSnwSJey zNJ})CHKtW0z4T}eHT!nd+zny8SB`g&d@?i{7?q=TUA?3MN9Q9sz3=su-QI}ZgT2ib z4Xls1ra6L_?8!^)%TCnr0_<_OoXtJp=wsM~vOG)Gm9zVx6}<8Zl}0~B0Cv1?ZMoSl z(TYagQeU_g5Ofk4MLJTiED<=?jM_lHbcgM~jG%yT?>+?Exa%yqjr4ySK|x*=Y=2X6 z|GrMor{)$1y)rxpa5DsGUgKO-c)B}weFiqwqN&_&Kju6XGKEiDZk61Gvawzb2h{tH zKU7Rf_ChxBhsunn`$K`dV-q~xmB2mVQgUPXEqN0KSJ`_*Y;DiS3tCjSY|DOakBY3A znmszhL{vnUV5U|j?5M<6-(6s-oepA!e>?sknhgY4o-8IGq5QPxbAVkIrW(nn2EF%N z7sUi^`2R{3w7WW6^D-2G(DvxJ>Q&^c#=k)%2LtK)MyI0v@@k@D_$wXuC`#yjP2uQn z9H6^7@pJ&5mca^7*m!WlWuPIlXT$Whp{tMxHwSiX<6Z0TA7zlkc;iDBSus)!DB7ydjza!W~%^PH?LT-|m zB7R;q)~X9%X9c~rgUKYC{kQX6xByaBnEwHX12NKGmxf<`grDh5$$AX3 z{f!Y_iV!jxfWx+S^gi z{d%1Q(Kb34N=NYYjEmI7%=-m_t0iEkI%>C# z^qZOutw(UyEqD?_-}JKSdOdtMC@0}Eaum9+=+;5H8I@&-)kbGjKl$H$14zPD3{@QN zuQbZ)dB<3CeqR)l?ntF=Aa4NJ62Cue$&HqDK&?O@{9?m;Hh*QaeX0g)l<;5F%H!Sr z)jaJqCRP~K5~SL|c<2O9v^F$+-%6ToCRNl$xicc@5F!r9po0$0aYCU|5CA*6{~+zM z-vWq6x)0-iE7Alvw&a~<49af|5L$GIzW?$v(`2+0bfM{&Gvp1x((e zy%F)olkyOOuWYq`*Xo~lW~l>>lFxtpS~XuX*KYojZ%YWvI{`el{xv9~P1ulQdwoiL z>TCGQTuGOXP&q2goV%V6Ekz1Az4gfDYX$N^wSUt+EA+Dc>kWX<+Q|<(LA`1m=@Z2T zI|i`$4r-?V;}7wIIrv|)2ReK9Xzxw}PWQjSCkN2)?99&pT{j{!Axc|*H+P}?e*MUC zKAz9}!CU^)26_yH#S8oWyt>DTADDh?P4;n}(1i;6NAwpoF=pE=gdrx<>iQl$eE`-F zdiP#>f5i-2GmKTi#iOYoOL@z!Y8$@WWUNt?_4A4UlG=aWAEh}y#I)SzAAc6)Gg?e{`RG$f?}r;(>p z3u%G)?^3ip4D-c{(R*b_QAC>7SWpf#(WxOFEkc$Y%>%(BdyqYSsN#LLE$M8)_S+zP zHewM9xhWCtyeecn6|IQU`xloqgQDCD*G~59)U5Zu2u$Q$z&%wiCow6TG?NwMn}lhS z6)^pYDEqXCTd7$K9NaM41(yH6T#*F?qulvveby>ix$ZO{Oy8t>4>o4SWP5e~l;k|2 zJ=NxQ?ESG+-RgNe8wzbQy5W|jf9B=$&W3RLm`UX>>Bba|C{;hPeE}6&VJ4$e zLEDgXfrf_YS5~RD-8o6q<3D?E+YCM^$Y(CMrytq=kaXZ+TzLeB;J{PZ7!ZB;DYS2z zS(oK}vh6j0=qkL4URvsC&`h@-+ux+w$VoJ+W-NsqS%p`O>Mi(Y>%6mj&1Bba#*E*; z{g}eX0{0XgpDesEWxRgJra)xZi(*ekac{^sV8sFws%EzC9ERyL6TyuaJ;DA;fs(Qr zM7A%90qf{!^b)ESe=(ZX zAu1s4yj?uCS&&q(d!I4zI#D5dvQpy#?nmcjT$LhL zGJ}1%PG^6?#DzV(!{E6!%)yB?e^6-v4a|+(#IXf+)yWT~=6pBQP(Lsb-H^Z=#pdFIDY_JnKL)Kg{9UHsblvITtY$>(vN^m;v5*O$9L$8otJWM zmsp&O=De)7d@_#IAPJ9IBeb;v^i*J+xIwy?8L>1MEer|t7k{dsw-0NGerOQwfl%Aa zRD4KZn|b)nDymY31383H)*4eqtpk%^(Mvlt1Rw|cvo+WbV?&>~-e4dY-Z|e*l0O9N zWxYCk$C;5>>&r!)Y(@F;+?Pt;k9D0AnSua8Nm}u~(TlgwFM}jK6_9qh@1-wS@Cz8> z;@iVkuTC?N;z2nJIzfo`8^NA|G`#L)wlJZib_fQ5WBAa*s3vrwGhn-eWTf~Eb6atF z4mt3cAKyY0$z~k-tE`&|k4c>e(x6BDIZ&G{a7!zC5Jr|>E)ih)62GnK$x_0}KuC$7 z)y)vWH30g;+wVN;-Uc-a<@ar~nb>H}M${C>XB~*rJoIweXLu!tuY5ce~Xd|}Lfm9iL9G$*(+ z`?^x-b_cn3iyYuKx?_#_o0(R?lK1Ygqc$SuTzg4cxr`;m`{^~Y^2?jk?&ko!?@=w9 z{TyeXIj(>u#%EG*3d7@dIv&Al_S@`L?r#WQ*c3OfbbNu&*oO&ooZ4&T0T8h&)~OcK z?K-Y6Sni?73Mbitq_L;;MF5o`{F_gGYA_&eLXXUs01Z^PEiY{nf20u!GWB{5ql`6R zYkYyqBmVPqz~i-+M4fgaL9qbySh;WY`lbgiCXVKMFc3Hs?aGGDfTt`kEw=`eE3KqZ z=$Freu(?ydqyL%HOv2UYYdCAL*gVHmyXp@2PDvPB(B3DM`eVaM_cmXWZQG!Vqw zy|E=yi1NGHmuT)UFrOX^SW^Y`@{k8ZNiAeQ`z_og**P;MzI(gC6RP$T?PPXZ?R>KS zG!xQCt@1`0H++E68`vTTS~Zu?hu_p<&D4+aIuT;zBhBBSQN7Qn84S6 z_g3oPKwv7R*H1noIA*^>H21yfCr5L!&FCGP#>K6BV4f!W~ z?sp%F-EeJA&h_}vkUadp4e#PgKFL^m9LNTDl_Jk#N^!sXi+W@j6t*J3w(fna7llk5 zGcR+=ne4m&p0Z02#+t8UoxTc;SdC;cr4g4E1sPsF_$^iwLoFmEI*Q-wo0&l=-)sN# zX%>9whC{M)O>9!&#nbR)h}nQU8!>#E6=ZV*yThmu%Iz~4mNAfE@{JwHR;}`Lw3Q;tB`0`eCd}R0WTzHe8azeRqL0;^UpRbwBt$V7Ve9ct zDCH5WRr7e$m%D?G#et`IN#(hixAy&Np~-coIiV*WCc_O0-8a9TH+)@sjdM4io^W1N z{w5?iE%^FgEO7IbDPSAc2yg?LlkKjhv-=K)#k5+NOuw`^3X(d$>U$%E<1^AN;ZYNn z{>v->&01hz{m9nulCrAIq;{|^iV56#c;fxqrzk(= z5Yrqck07ggynrWr{SgU))lUw?>|i3$Q4tuM^Y1eYLnxlm7?ATRI4Fa`g?4-82J&X_aNf(l|M9bs%?L&q>vbWv*~xRRSbChrV?3sikk_YU2cZ)<%Rw*GL}S76l4 z*-s!8oX8fsq~)bo4kx84@QLeNoS3pi7At?fTjQvMV;}bVwN*O%Oq_G|(t^XHSRYCp z&-Q23@8Ihxwf~Q<_W-B*|Nq8As1!+3W}lWK6v=juO0o~mF+*kLSQ)2ujH47Gd}M`i zj5v;QjO}z{uh)1!o{u4&&qC35%90k& zWG6nn^ctVelbK=#;eV2PykOXit3CILLLVGGAog|5@)6<4!o`+C_JxzjA;6X;J3WtR zP-~k#;sKX0KKphgx)CSPb*0+7-(AfGCWw|n_v})R8Q6!lf`wN zL4XX(_e^q)5mPtX(EmN^TuVEr$_SjAWi!8Xs!oag; zKygj1v{Mn>flI)h9kRVC6`o2TX%Q0YcDtei)WQK-_t}MD8C_dVOMS=N?f3-+4IZ(@ z22Z>}$jWSAn1&ucrm%ljQ(R0z163L^b8^qoR=_ZlVUUj`PQWZ8^8H0cR1^Wz{<)bD z471kyIyL`$>05!Wc+ll}y<&$~b75yU$sI#l4=Jq%-E}&`S?)3D5mT@55h&CGq5MQF z8`DMrKRo6dEKmyy>_eT?{ecs~=x-|VT=wv0hdw4Q1?H2i2YY{u>r*5%*{Z$f>(H(C zl2QCpK-D`gWZ{4;d-f0)-i0?LTRK4R;~wXZMG($WMb=TPb(Gm0zkoe?ZkNihUOlqg z1lW_yOcwcUzJ{=%UlXTmUU7`h$vDVNPHBD;mn7H-Nfv0dMmX^S#TY3A*uOgx%6*Ct z=k{{xS;?NAuln#$*LnYFQ^2R9?yX*2JWr$3TCHh;=znOpF;-j8H`$vW`ERz;B{8(s zseA1Z+FX$I@iVa^;GBw0kHJnfavgO*KzAw+N^8fxtC}sNY-~p5=IR7aZU)Vu6j7iv zgWyF%{Dg^`WMy1+moknVbfu^JIM}u;0 zTayN7BU<}sA7dk?Ci)A~E2=u#|BSo1y>Q@_eAGc*c+>GWN%-Z&;+OX`s}br8-Q1p| zoO;wU;eM~Lp66VZ*3RS@vB$#|U;fz!1YAaMC!$+H5a4 zAO=(AlnQpB2&Av%gTwls#>+%;@T}Ft#Px8X_zx0#d1H#jj5NN+B^*QQJLt4-+piBS z1&B3d2?%m~+Pbo+jrn)aT`fAIU0w7_M~?Vwd*{2pmA`=})_?Res z_-1Q%qV@K2DpB&@P>UFx8Oabvm}RmOHMV@fFI)s+em^<$Uz~=d=3d@$L{iL4FZeqQ zw(DIoqTyV`BvksL7(*GR(lVdoDb8~{=F-3r9I$_dJ0O0<QHxsKFO%(0YLy(p}xOY01&cQ(s}X%_L6|y(l8ehD^?s%ub)kG11C$HiLd`+8fd<%pfD z<6F1R5RkNqk4CO$Po|y_!-l@2>L=V>It>Aoofvg3sEP+N70lSoE`Zllt9A&#reD9U z(LDrbCoX|Kg- z(=5I@OIh^NfsRvG6;CU<+gca!HyIxkY7(c8c~AS(T7SA5Dq>sw1@I`k2*7In^;pT4 zjEi41w>vY)i?6)Uk*eSB*RNkmP9>WFlv0QXCc!OdcPZ8=YE4<%#?plfU=gen(++tgb zjKJ|6yJI?CDKeZ7SMq*4%0@k+*Zsp{lbPpZoqTO(gNLcV13I@luiw^a0?Qq}YH_b`=i1LR)x zI&@y4_MH@Ile9(|)TS@bQ;hF9wekrQYPSUFc4Vr}@NlQMz0ahz+n5m3Kj2YStz?5s zIg{cEO&mUlVT($Dr38SOh;F==h{%^vlJ{x)uaCWYK5~9S4kl3(R)Tr8^x^<&PyH9) z1#wIqU4Z!>W5+*!0#;q*JD_ii0Dz9d|4p;$d1+sVDSh;d3TUOv`Z%6pNJ}vtSSqgg z$*oih4X2LNf&yL>1CS}1pjr)$vVqPwDq?S5-*D1q)1*w6kpO5)crB{w_i!aTv+B1x zBxl1IRn?W4T}P;Xq31_8Td@A=P?wrB_5M`=bzt4CD|PjrChFq9Jd88k3?CIy)LXsS z?XT=q0CJcWUjUHu7Az3$38lPw?u-+I&6bU+=SqGQPIuex{$Bq+)z;Y_u&<@I`O4)t zXJ5`7|8iah`|T6g(F^7L|C27D0kyWz^-Bg&n#QnxSsmj#KtYt zTCn!bW{F&t6(A%1(OKB4tcQQ+kNK&bx=EWUEf@xrC-S0I(2#mkmS?WiO5x4|aa}4k zIGKZ}X|;E3YkqP}n?7+z6L^G76x6()5m*v=JX#(<5+c=h!8+-tUlBca!~N%0emc)V z;ax>o^lrvM8MTldPomheI&R>M6cRDh%|gK+GW42|v7sejXy34g4jsi0X)GnJ~< zLuL(r_mRP@ZBLu;ZJ4UMSmggLfo=Fl#7bXk$nYHt))tfbp3*N>LyghI;gIdEDwM=W z4-f~Yc;H1?f@M{5NN8S3lg6SI`I*mXZsJ|;Tc`n54i=}iBKmvk?$Znp9EcP?Vg+Iy ze5%e4lS3{JkZN^(?1OUZOlA3-Ot@j%k)YiL$g%{R`_4CWf^wihOWu-`x8lp+Aw9s6 zCE+*HiC5%lF?2ENMyDy^A9^KE=HzIqbZ5*FbYS6c)Vqo$Fl(sdLwfgSqvb~r*rw-& zE`MVt3-DY~zYt%Q{lNm$6KUDe1~FM&{IEB>doP(N8;@gP zx?IG~Dujvbh;_^6P&W~gY^_JvnVE9^2=(AJU4qd^+M8CQN z#YMTH2};eVuQu3NVep*GksZ3>6)h{X<-hc-N#e0asT+~{JJ)45$<;p-!}KnBe`Q>j zUC5gQH#X(}8Wt#w;|Qm(m1v(Za1h(Je4as-nuvaVL!n2Sw5N;EI+l zl=uzL{s*i}{H9d;NWy|@$3+4r8_a~r4P_Ul6P7jy4!m-H^fyIyZaA@SA+apyiN0-=iY$Qcm5)XYNrG?c?BfkNiY2v4yE3R19a4zlcKOwti-s-)Auz@?xqAu%pvb( zzt!C?F4{4ty#w|Rb|YsZ7HH-o)>-UChpcl~@;XpD?#aKRj0xN2WSGxn{Xs)dc@^G5 zA?^PWGym>}0A0j>r8}X|_z}Chhym?q5V|vAEL6o_sPbCo`|d~V3l=IDXt#OlzhG!% znnn+=#eOJMistgF;uOAIrHpMkL(TI0HTO|xLQ3C5DCI^+1Yq=_fW!ED+e&u2n&D{8 zlty&!bVBYardeZ*0$R-5r{vDNqlLVTLg5d?*kMN%hiKw(xF`gP6oa{RhAci3KA4@F zkdjkiRu_}@ppJ{|UbucE|IjgOoXlH_+M%8rX`i|xKK)ZlZ^e2$jes2(aquQnDYuRp zFYrDlpudu{vl1K3yJ~3fuYw`{U+ovb#krEdBkxx_t_%UJ142OqwWlX30ixt*97O;n z;d+6jAAPilM_4&JHVrw9kj0^V9{i(K`#LlE#+tgH@`|-^Pd5v4Ctp&YztQ^9535k8 z1tE5(QvbUhM5BiC7!&20qk{~`@<04%uv;lr$#&?(yd3~y1F|1j#gg0D5l_=-@8kX+ z8Q72fFN9E)CWG!FQg7R zq*;k*Kjk^Jfjp(Efh}LwotNdmFkIX~EZsA@ahB;o)MYGlU!pB^s2rHoXr2CQ(yBBXyKy)9&GaWwz%Rd- z+W(u$4FUl?49zHKa_YWhRrt;*U+*pxTZ1KnT; z6mnY;PIzb@{#kwijAV~Ssr}Lb*V$DyC8eHWy;J>)iKMD9BgKUi4G*%hTDZ!ed>>dp ze&MY@!$7f9v23xR*mZF|TJKq0UlUT#9hSNk{<$9Cd;@(^Ci`AC8@w{u1A7W2#!({9 zHxEisZ|;pb_8Zp(LLdeg{#|82)!-ro*dT3%-;}yt7<64hQcM^&>%>OH_oqHPa8V5q z>;WFf@FGwTdJJ1foOOKlGBr*jBiE(32UKyN{p))jub8V3hXfQeTn7G>o?wUDgw)D8 zBLuPWgIPShgK>l+jNmAN@aS|mbtr(WqL}^^$N*fOZ`8OJ^30K}JgfL*foRB#yoQF# z+C9WwV~t~2HY_{tUN?Y}jHlfUw4oH+Dp4b%~494#@?8Jd4E{ABO3+%ztC&HGK*&{o{?P zyD(!;q@r%BJ#xVyTg>W*AT00$)%f&2oP9hC;16gs==tERcMCyj>_YAVB4Ae8>KN@f zmhy)e_@=RtBh0ioa9ihUQl2p(xW+_)b44MDH)P<@87$<}!l(mBB~QTg^~A)P@pIk- z_mfwvlW$&1%9*(A(sHb_{4qI2s($AiA!KUD_gL z?}{#~&8j+oY(B`IExqb2%gEey)aE>-8x;>Iuu{mYsC^CR#97kA04KA^(t zkgrqr5X{C~8w)lHgv-Q-E+6mM+28bsCMNVyMRXh$Y-@T_>d%7m#sWm%*6i6tDSRV{ zkE}Zorq`ca3<7u2NBOMN zCVPqLgaxMNOYQK^nYVs99fUH%uoX4ljnEsOFGB~sD#~1=B74n}^^bS0v6uvHQ~9-> z9cPU<8#GPH?9VO(Not9*4Z z0oowCcJW|_Y(~PO^}LWW?`2fh5qw9g_-lSRMq3sqmx2jNS*$jTtX$i<+hY^JaLGvp z{&vex-Sx`G&OX#)gUdqk$j?i`85_-V>j;xI;d9s@&nYa(Ub8obF`3AO(tX4S>-4RErC_~5>$XTf_%CC(`PZloc(5xbw&Y}u>vdf*%19sPbA3DN z4ca_IrVwnXb>rq@cTOHOKg_!0K8V-pt3i*Ywur3DVy!l$ibYnJCtF$KcJ;r$|HkOy zI5w|zX`t@zGBHp+rckvG1{j`eIM zkjN|5SF}yZR7Mx@)q(=HJHDiU@F7|yCto~ZctfTneP&B{Q!<4(Tzi<$`|#XUO6aVg zQ;<>Z+77$oTc!m@yF8UQV?+HKueHR==9SA__=&S*oj^51r3n0s20s}hVpvB8#dmc- zt_oDs6VhgwO0d7yqhvWsW2+`#RAwzV*}EKu4Zj{bhs?R$t&LyqiFr5Gnj6MSv{|7( z4=LgcMsM07GvAO44)6fgFR$B9LO#$XsOIIH)Q4*Pq^eaY{cA~$&J9|q$k`*4sA(eWiL$YBHu6AXoyElJU8sIvZ%lk+=cZcL3`!I}fhHBfmzxJipbbMJ+V`<5MDh>gnr;!v9{w+z^|yY9S6*$Lk+ zUs>zJ0mTP^K^vzVyYI7gVY#MnLs7aho*;2i^eHa?QKNtp#RlO*hwIt zN865$aLBesi_Mq{8YC#yLta5TSEqfsGaZndl?vV+0y51+PlP*O%r+8rq1U5mev8HL zAF+B)KO-jJyHu$?<^Kxr+WC6rhES{h&I$@5a>Ee<^(prFftBNDQ3Q#_z!rvrw=)ob zZ+dCvd>R`suDJ>^h?t*`cbNPMh!6Ri>C%_L?p0{~TS@{8)W_kYUF};Ia_&j~YJBHZ z>0<7Q!q4RT;xlfO)@wXdi|i#g`?JM}RtcGAgtm`&E#3JhkB+`Pv|}$tseaAcm_oAi zttDRe?rbZgp4%GFfd(*$rNB0pEu2a!B;-WkL3_ikd|EDO@H80L&br&X#csm^;^t?} zR{z_VI2p^!m1nS4_yEzDqf8XBwnuIszs*Xsv*n_A=x;GNzF@Jh!dtY#`&r@!uRtf} zxdv_pRKf8Dh|e_#9HP)Ei5H{-t)^eQK0va1YTKQWCTP)k5p`{smSxW(EZq?8kP34*AO`$?o z_`uWB_?Ca*Qk{zEcyY`*=4FEic?uZ#4>+mZ>UqNo!4Gm)Y~#ZmTpun#|3n;-tO-+F zE-WcDJt@OjL(XXK$_LHZHJ>v)0(6rCwA8CUDdU>&PfauFc5wn&E;_CXw94|0Gd7aF zp$HYG0z`6t%GQileqG*~$nbM8p-pKsjgq$u37pKDYFq(T*$=rA7vk$U#>|idtUU}7 z@t?v*bmctlWLh0Xz|&&#wZ>(Md^K5Acu{U{v69G~waazr7TG`L>Tz1tf zp*uUjUI1cC5L(y~9+;SNq2Zjr`v`#?dq+6Oj`F^`nkot`3ig1p!Pt4d*k!v6yr6K0 zvl2Tfs7v|NQNq6fBjpY;>Wg<37k;L8rjdm_24r1*4>o{+ruV||LPrkS0ezNbI_%OO zDIk|fmQr4@1G4W*`k6kTG|~BJyI`h-zZFMCV}>4rA(=lXj2c)AT+&t(vI7$m`54CR zM2^OiHF>lHG$UpUL01hUl&5+C_zLJKIDfe|*HdK9j~jw(j+s4pp+LU`{+s*dXs;+P zD{?dve{HJ{^GobSt0$8z_>Yax2)8Ne(o1*#2N3B=wa5RL1m^y}+FPFS=&O5&s`+UPLo|tj`(vM zyqJJ;ss)Y&6J!%IcNWLJLv*QaQJIx5_|1SShac(~`{le;^Ky>Nfn*E&zZgaj7w)>K zp!S&~Ncav2*=|bpouw>$CsqKr=sijH6ZBy4-Jxs-naBIyC*VLgBMpe^=Vy+E2y{A zi)ZVJuf)`0zkCz@ALRSl!U|`n(yLyw!%tj|&2DyEXhP3ZJY|m4WBccyC!6v&X`$1u zyL!{cKx2&%V&(7HYqXl7%Emj))#M5&^}mR{$eD|%n|to4XbYo2$#-DaVr^Fr^p{*? z?qQYOvE6FYt7e##i0(jx$beeMUVtK*u)BR)c2_w8o9*TU=%q&;X~-1_8Kt;pfn6Xw z-@S5VCnPLDZ4@C)Nlg6709j)Q%R1=v3^hW~-1AHgoXza6NaE&?9b z&w{^0$`m6^3<)JW+?|)(g7zNs{jvCAb zVXMD21PmPkPC3RsS;pal8*J)>x1 z$ld?EjKcpE$!A#bj)R@Q2A$35T7s3)WsVwb*K;@A%$=I79)>^_ch+Ql_2g z0JP#qUAhCrOi$6@=EpC+n@(y!1U|{g%-{0?u+my#c#@foABNc()zwb6e2kez27pEi z^dE+J{oZBlkI~dI<5FaPNdfe1wcwqzwBp|tR+M2NI&3W+U0U}Ue=T+%9l*T*VHa6^OI#`W(3hq9=AQs=6eIPGg~N!8N(Ulmr&2JdzGzVli~q+?1J9yu{+f!1Bnp} z7!a8rDk}fEL_p1&9sv4|Hpk|?} zAlqtJ0sF|SYq>nr6Nf<%gRYAjJnb*w*SQ(pxAySYmSWU8bN7LCMcPZm=7?+f(9NV@ zpmi&k}@u4mw36cb8rx*EwD zX&o!y`~D(+=@Up}@=0PP8feuU`TXQe%;-pKE--rZQ}4}>TQugXdE?H0&!LRI1@zW~ z%mV^fZb~c(R7lA^W1_&=h)5C&m>as9>U1CNKGq}>j^0U!d|7A7*jR~v8-n>?C2`Pm zfMYpdjoixkJd&pMV$`YK1)UrsZe;yd(VZ(7;j9OdcG13m7gQpNuG;WQU* zk<$f8uRMa5KcXtS}9{cmb z`JeH6+i?`96((77pjc>sI4{q**>_d|d`a(x4{GN~mik1j-s~;8PN2-7}R2 zxnb^$F2`V8w;$E*8T2}uZ{|W#y6FG*;nPFBwRaEGlat7p`JBQ*u7@%8_RZ&L<}O$k zq^XWBU$-7z;uCb9Oak9UR3E%is~ek1k$%K{%8E{VV%MI@Hh_3IAgZ}birXc)hll(~f$t2^C*>;seh z#@q9CSDlt}k;1JW7mZdj>iHwTX-M$E+%e-}jL%>)v(LCYW^@O~zVp9yu8GvbH=<%a)>4pNkP#^+3-m&2#9 zqd%${ids6gKAb))ej&+-O;bIx4~N4nJg+|+ZCdP=vC}Z$rJUV_-13FW>Ta(z9)`I? z@T>HD>|_ zLNFMx7%O$jWg{C?Bby^==7J!}2Aao-qiL69y8xS{BgEWH#OV%45=NNtr!QX7?kUMFZSbv zh9P~+79X#JXDc!?>!Es%vG`JunnZknSl#p|xk=lTf_WB!=Dmcc{KQX)X5Rtjh4~&% zFpD1F72DD`MQFdcz`cw=L)^Hrqayq8YY0Uxmi}GofWP*eRms5-w=M%|kpbWxzItHEZq(ImXqXOq2$RYEI>PV?^<98aSvsh6K69S$!e!%Zn=zYx#YExO zxSCqRigV?ie_ouCPTu-qx;a^1AmPa9FV0V<5Kbta75#ZK7LG_e0U@~Ib`MxHp1#PiKo^a!%ryoD*tHV zbQ#;jX~E)GVl*tX)N4Lrdx|}&IXe{7ajte_Js!QJVL}o-GyI&_vgq+GXroKHB$gI= zrgz_|G+N|@i=Hngoy+S!+g%HOc3G;)H`&PQeLJgxn%zvsk%8#+rlimy?c~=1{5!K@ zoHE~n|G{Q$KUBohG+)U_TW9Lz)6HsVQ=uQa>Eym%X5Ct&>`&=n?N#p7+xiO{C`3kh z;D~xs*;Cz9qO~Ew`u+(dNzZylCR{)h%Bxgc6xetQqCJMyGY?i;cI6ZFvQz3J_9hC3 zyeq9zEu0QW@as0 zTsFy#`RU%(Y|L7AS20M>pm)4rMv|otN8JZ<|3DtFpvhvhZpMTjfK4$YDJd6WzDFZP z6uVWzQ*={vV12~vVJ!iP@6=op{#8&md8{XR_nbrZiaKt+?T#*2#_?Q-#o7EDmy83$ zgHAz0`cwMKml#JUO{rDYW@XNAgZ?+V12RP zk37SK5IpU?$HepiF5Ea5CHPWNlpPPv#0&p>98!`aTK9}i{FN0GQ>m;plB#JqPmIpQ zk6QeCV4LE0)`w4Im3fn-4Q))i3ohB%bK`7GO))Q0V!>F1H|%C4yQ)Ex-i~;P@x}~$ z&D$Q4b`f00l(NkD?Zf$rkb}T#of<$D#jJt94)w`!zT}my1IGHURSuz&0#n^Y`JBM>1&UmfK{Ne628P58 z^O@TUGH(fEAHMIu5vuHZgNYJ-{?3+ot6XZr2xW?^w6I&7l-VO_?)uX!-Kl=~7MP=m z6?;r=E@1s{hohHf&)Kc$*Akkue-LmJ!&k6++tFvUBt5Kc0Wpd&*GtbvJ6?rChhwRK~SHj_fFvxayfD9S?uVM2NOIQRyx-B-p%W^uZJ(Fy1*OVUfiIXo!*Zt zxbTRz$BYw)av+?&XCo>`ezRqsTP;e%Z+SPLQ$ab}G14$4W&4kZBKB&5@X=$Yz}wlT8~D-9 zO75#T%0$sEISQjMJ{R*xTu~hkqer$W`q#^SIVeL}%4*PQ+%J0pYAL%1k>P$vQAXGH zLA9vIj5fESh5g)CWdK>?<&i2ytiPgP?2q#aLOH@Kph~!G)L4PU;fecK!Q*tI#;fNx zw}`H`slo%|cQ9Kg0-JO6Mu)31<7Lmwz1S~xvL<( z{LliUS}a>Q!`gbA7eED!cN1!{@rY}<<1Yl#x!Oh`rSn*EYIH8{&|YGo!cqPtJ{T+xRoN7ZD?uA#W$%^0B7`Pelj>90p zZ-sk173kuuu4IW7ehxhX2PJZ`OK`$d7eKnE(Er(5?hdQJ4T7>ZKub9->j119Br2U# ze{Mie>tFrhg!r(7RJ=U)uT@VN{io4zJQycRhin2DQ0|k*OLd<$(M)1 ztR*ASOG#ZT!R?5^7*$z~HxS}Sy)eYKR2$BSw0RY0+5L{m6x8p14uemqcaw&#x-x+? zFKy~=Sq_KN?I1umXb0qyFnf>)lp+R2)n-O--BGN~4)#gZX+QS??kq9rrkdb?EY=ll z(-E;N_$zQy7FP$NghXPJRrxieiCio7rxC9~{jRE*m~7qV^~uuvmme7Ryp!hTUiVe^ z^}RQ`l3RIQV(y*kQ)-Ut6JHO6BKC(q-z;No6@C^ zmppIjLRC`qAT!PGnv1zjQq5okJzXh{T~n{}{a0C2s}l%ua`O3@@ls{zZwV@L7dHL3 zeML#+_isiD;iM1@GGUP22eo2CJ>K?v zls%`^{w8@sd#`ip8bOJ2ANYf0(AS1((~<4o>R~}C`*G4{p>#Be3j3K;X+WOlEm1=s zCO%nM`qz1~1$g)w$AP&8AeWF3<1A)8XVyElT7x8ZcrV9T;;*Id%sr(2wW|*!GgKF( z(&KWvuBY%%8f1l^$6mGVJRNHZxxCdj{!N=1FZeH9#QT!m*7q*a1h+tbp{2?N+R=8h zsZjDrbmjGn$8Y#;NhuIl5Qyy#2lPbX6eu#1vlMSL#W#y$6g<2er15wo04--$F?A6^U_LM*@Za8S4V zc9O`unKSzB{n2PecYezvGgvMUEdM679eAt`91pgVe_-TJbZ5WUB|jMdYo42L!qxFg za#@SNRK+0xn()+O!e~Al$-qm=L~!oLhH~=rz#3)_(5&BXOr(b<4cN$ zKRLU?MC!sPR;Ou;cP8sb!sN;8?z!Xer{+sFbqi;BV`K0MT1fGg_(8)xLY?tB04{q7 z06eCn_Ok)xD?PsP#{5@piKX%TOwVTrm_KTJZr#TDzK&0ExAFps#_@GRw5bJJExIxYyRSR zcZFz-Os`#iS;Yw^k}L2ju?b3`dPm>Q+H=&=4ypor7IrP-Sah1=s}s8TBjn2)JmI`7 z6hhz20Hhp4L?F#67I2E2@!2Fc*;Q&O`Ypi*cQ9q?iTDnzOi?IjU#!J7W(od;qVzv1_9 z`G5WUZys^qJxX+@-eX`R0{lJyM8(_zES@`{`?ve8OdGN3AaGQ5K|$XFVhsaS1`Hjp zF)VW#_RxP`hTr&oqyKuP?!(Ehq~?n9C+Rr*SA<6-t&Ef@sGVr4{X8f|v8g@VBteeX zgR;6F`tvDTf1Z;+Z~yz)FwV;sKU!~_{;)mdO{9;uWOO+uH^QRL&gy?o?+E<&!viqw zqh0d2q`kzXh0n!ZBQ1-ymqn)tICs}>+nYJnaMGb3`T5CG`A zCxqAzkdOo}9-!F)yb~Bd(vJ&3I?BZjl2j)1A+8-JCOz)uzFWBlPygrch}!JOEA6{^ z;X}YRVN4;XzLwywqKYxSO1GIVO{xnGdfH*Adzx<$mq*&kiVZvLHb$d>S`N7W33iaBZ#)f>*$NBxSIV&ZPp(qpHSXBjA z`JGnr&88AF?70+G6=z!t-G{myDq*GS_bO%HclpW}-(&Cj86%i+6Tm8?hN&S!GS^oP zGOjMPz9MDH-IAC&_6RbAC*f#XXeiNuupuSx6jBG zuTj!>bfcG1Mh3`eAX3f;mqCl0mDnxLLM{Y`CD;5`vAy@vaW34YoS7PH2M)hV%_?k6&R zC$UzOBZ0jpZqi9f!+yVhR($eia%K!uM#tseO2ELP6lKSIc^Q}0bEYv%HhbsSL~E12 za#7}rALYdqKBIkMswp|Py4+WWS`NR97ftY}clR*qjpszJmDlB1(5|8g$0R99dR_^Z!(S+n4AEb%nvrir?0FU zkq+8UPlKgjsrqkj;JW&ZcmS2#wWZXuWW1Dr2;*(-Mty*&-R^3QPg4jRm|A+C=rr55 zRdL#3q5e(smxktz+#3JKb~)mA@=5xy6elzdp&@_#TCQ^8)G$RBUv#oym(y}LDEQt; z5s0J6CYK1rOMpb{8f!}Sx6bTp0y@4=bJy#8(W@KoCaxE)9FlAM$C^aNssGMF{Bh?5 z-OtmfVa~H5j~AE5w%D84*F_+C_~;>w@~qG!k=BytWG-RI!_Y-xBeFZ8D{LyxYqILB zh?8)xJcNIUv{v?VS%eKh3)_z5yRnQv`^y^d&90oBP4^Gw%=dO#C@#~Gz8iI>6Fd3V_M$Qbp zevvPk-7Ti({N8iZ~<+hXUaEU3(F?01uXsS;fb@u>l4>b9En`CeofUnp~P49{N+MeDPRyS`XFhv9ldXm%z;`A z2765PfYDZ)eob9H;oQfqb%#K2#1?JG_{snxe`%Vgqpuvrb1w|95IHnt<7z_4m=!L_ z8?#qdGf15ziJCH&Wd~q$H&a^eUp^#$W`?cqLB-OfC{2^wB*U6#n~TcW;G(Mwo1(CuD53ECz?^u`J$PWp?ZzaD7z>5mky2gK>KQ~pUZTD!Z z@<}=Us(&6(;`a0bHx%_}Z@kr}pf!`JFqL5uyZ)3T#B_#x{^uibGYB0g&V#2>j=H;bbq(w+H_Z*-%WLT<$Ejub&0ReP#A+OOY_+{}Y$9QIzm zv6uKGS`?3h&W|^P0|~S#?c8XNwujFqcu6Y2xZ#4$-BF&4%{)LvyG>P)3s zEJ$6~@z-M)1zj^jK9l!XD_!pINt!9E-6@W!2KJhTopDU0^(1>O-)eG5SIFXG>a+)< zbA5KKp37OK2-am%v%1M<|Vg)X)4FNreY7JilP6FBj!&W-u|zKL=> zU3Y&IXamkG3b&U+4p2OPb7(4;T?W$=ZUPU?lz*p`)ckB(u#L#J;G89*g|`YWN#J)z zUP)?>*v#+OvFi+ZoGcoLH^E%ZO*F|KX|u=Uh&5JiIx>4OS*@NH_M$(p0@~+VKVXbY z#k#NQ3^OP0*&u;P|(w&i6{) z90{-I^lSzQpLND5#9>BR4a#xJOBEA~)hy-d{265tfmSijQ~HLGBYa)^(UzE0 zt8G>WFS=bDmfLXm`@VPRSF3kCg510Cl8Uic(H&?`-<;^PYn6-Dz}* z5((#}`DbTMMr4T5@%;aY5xa~?&8;$Sw2$ea6bUl62CLj$U0FmvazW$EMSJkF8_ zuQG6r0bgoku*br{+<$ekQ7TNUN!wE6&C-LPZ4#QuOXahVq^l~9+bP`6@0qspl6iA7 zwe=#sqI_K1MG5RJ<9DP@u%RM_ooieHc&{qWm9GsIhu_Vv!=d3CX|IId=#3g@qDWte zZ)&rTOxfnB09VfHE~^N|A9t0#0u(jHBi_Q7_`(^>&R*h8)Fr_R5-~7Uu!%PeAAWCc zD}g-g6#is!5x8Z&KLofpYXD)Pk>%<(^WFSb1nR3?1+qnDkk&dv8zFHm*eYAuuYSq4 z1l&$fk*cHgio#>*6;ZK-rd{=QE2mn!AcPt^wT1b)OH-!#|8H1>WC6Dik-I|5H5N3dV8DaVZ(edq_u=Xcj1 z)f;^$?Rn1F`kINr=u?jjX;TBbO8Ad;v*kp{Qqe|g27Ut=In`PXs>I|QJ4yTBeDaQ< z1>=&+9VKkgiYierhZ50>0g6v@^{?$0bIo|k#0yQC)H2$S3-IgNwIN>0#VqmFQ^QI7 zWv40WEH8CdzE-}dIE8KTTqEwwYIT80T~H(G>hf@iIyP?2gC@Nb@HKz9u5@9dLfz8R zOeA+GxMXeK0kZJjcz0f`3ZOcjit+Cz&dJqXu#ZD-Mn>!D^9=WL@S{~@G(Z+~L_d1i zEmv5-iOQdDwGHr>3W1nz!o}1~zURZ-4LbQtmfi3%snxt~KXN0zwax!}4g3?^EaykS z{*3|knCS@^sRT5Hp=Ld-M7J>C#5O-q^@old{w5HA__OO#3U*H4$`oz8*8|@M@NZO| zhu18Srar61hH*(9!lm@G>vQM}hf6SKCU%p< zrjHIYit9_DxV{GVR4;*m$r1Ek5Zrh9o1Monep5=?ewz~w$%8u}`>r4x?Icu)8#dzY%9*!kXlx1a*sshXv;sR5JKV@vM8-c~lE^RI(p1)W`+v|zr zQMLN5WH~_OUN!jrbOZ9DmNC+_WfOP7B>3wilBthHZ z2pjRIG;2MP>p0}7{r z%&Bi1`|o-jB!UB~9*%iTsUF@8a*YiYa94-EO*;DE`gamjtUIX2?2g-$l}uz|&)VTL zxC>WyS~oUJ>+#>n<+hi0)*jJ38cUADVse?qVz=Li!cM`=&YBQyZ>1*af|Qg0 zHF(0Tphp5&!9(*w$1QANiW3#x ztflxrbKDeWD!rA(5|j6tZVWf1CV9Nesc_z|Hj!hP4yOXxBS1g{Y#9sv;c0HZqIT-u z+-V+IZP;})sQG1?$**a05m!0H#oEq^H^%AJE&yK-;x#ECAS(llv{}T7%-BM*QwiA;Geh7{>`I<3xoxwSrOX_(_;O#o|Nf2<7ns@2;1M zlfV>m6}Zm#RW@u!kapL%&D{~ri{V?yx1VNPRoAj!awG+6orGpz;y5%^BYNxfJ3|T{ zM@2$=hDdkZABMU=CF1xnmJN$3sJCLO%> zC$h(Nm>QvR<0#AOS%xy4(rsu3#bFrWKAfOl0YrVUEMpqv1r5VwrkGbYDVTOY4%WoT zI{);;G2&FT@NG)pr1}jiP8|Sx{oC)y(NeKBLrnzogu~cMF85yt0@<`Qu+-Sbc}pdA z;BO;#(D;p|1h8RH0zbD8=P)TDZ;o;<-LEHl=sCTeY1h(ia|56)WmG}+mml}?MFnn8 z)8$J91u0wpCJp7g;d)Xc{QPeTWUn-l{hy?-TJA)^t#4nK8zv7F5wJ4>q~%7y;RhqLVZzw=UFp8 zx-wJ(CU~T)E#Xc0Rk1T65va)dS!G)IEEuFHCTzY8`P54W0B>=>X2yDiQNTO!_mpFR zn%4v9%N~q6Wgz|k(>wa$@((^9^jG>Uc;r9XGkE`ju2?(^V1JbM+zh{m;{WH*e-K8> z=z%{G`J(k}ItzQn5X)`m>gG!3?71}ayUI$aK6Cx|614%8{RlcQ@9`ym3#ARHs&|s$ z0sTfYD3SOj%xLPUG?`nd)yR8@Cozrr2)4?E@(_Q2rx;|p39XgDQ}Dz2RV)Rj`wQ+o z2hd09-?)o0DQth#7|LCjl+OI^sYII3_Zk5Lu6BE7bA=Rpbuep{>@9^xot)p1^w=@q z`tkmi`G_X>TIu$;ftBU}{a4>^aXs){Z2R>we+jN+(tpe}Y4+0*l$7joCj&a7MfuKizfhw)Dg{*>+-q-w5Q>3;ngvy1kehohTdj8M`h*3tTMXcfxDlGWAZ5tXKEb0q-3AFbXyfrm*x(epUuLCzK$YI z8whKDl!aerwuGjUuhA>D*mv|V03&cC&{^o$?AM!`BA+CFR=Rv#r$khA6SNP~UbW7^ zGq{^g>5`OIqYK*Ay6)@K!s{o~LlSDGV4>%t5K;WBY9=K9FCj>+vt^}Qk2BS^j0kMP zi1>X`)FENlE2t5A0?s>x16wnNJhv+clE;IEn=<0kAYiA5uWzmlR3o2BB~T&g)z~## zMR-)2i@FgGb!?L@tW;v5#lo(+4dUJy!{g8V9+$8P&pkWyB7AwwAVfchK=b(7S66aD zMqW8WTf#L>>p@S8b#wTCZiyjooDA9Xa(=!ysx$nA7@L;3vZ4lOf&|=G%^T3}bcjOy zQ_t2dOi!G$CU369dmhX1UY?U7t4(&e$Vjr!YNoe;)MJ)jD%PHiayWZbUt8lbndmyk zTkwWV&LC5FLH_}z$quZ)o*FFFHauX<=G4CKrX$8MwFSLw<)2$Chf%TR!3uK9^yj`> z*D%TF-z=cMDR;o>*Z7{eU79@g#NYRL15;;VPxUe){a21A8h zp|Y|gG8plRuNlBKfN<0c2{CyB*}hVA6kE~>isup_PiD%4=~XAxWKNS7G$LWF_N2#` zS!U5Ldp<^agNX!sb9wd+T^j~*m~l6-l(~U~DE9*wj9xe|<3(yr zZ){cKo)w7gnXzh|#9(9v&y9*So=ajI8po`VTqB|uE86)Ztlw+tb#%!?s8ciG3 z)$+mqfE4^vmL+t4=FW{2t&C^4WO`CnRQFxCmg5<7EKc0kWZipA3Ff55qAOvEuA*7B-RreDpaYl$ z&dBuI`QZVfLl^$L|A6eH8Ze|^@nxE%#Jy6;&?%}4m*NbR;}&06XWn|(M<*ycjYADl z@)%c1EV^rC-H0}6=H>ow9AOUH?Rc@i??2B=bJnHMh8^?dAz3PBc@QjvNt72x{J-0U z2qRG*aUp-JO(VeNZVV(inL%DUBV9g%zSpt-WO<3!iUtIO&&;|q-qbE8>NMxoaQD&5 zK-kRYgZr(nF=TJZ6QE^~@`V$kVqmGTY08TiNZ1!yPcih8 zS|ixZia^=V9Q0S+4P~vLJ%8nGCxq36>Jo@m!0hCs0rxy0j#(>Np9ki!eU#|s_y48r z#J@gL=ThK<-@CeCASn52%?BaZ=|25{mOO247NK@wbt~S!BtvTQ zWlH`^34>O~>E~Yl2SfE;47CTjEQRNz1XK4r{8B6%SsJ~XU(>?R0#eA1LR5jTukK<^ zY`Ti@&O|6TNy4dOVq4m|U}Q^40x@Dx5LIK2FX}M0@}5iE*i(4<$gZ+Nn2BY*Atg0! zgMKyo&L>$B+kzqoqy_p(f}8>ujMVEeyWCRtrt8nh0!(>f_B}gq7i}L@sVC)yBuoKy z4f9&F6%v0%*?ahQR}0mJx>Zl}gkpBxf2xEi zw+H?)8PUN{NY_pf>u}SHNoNG9ZS#w1@Drhd$t~XzaAVzbQ z*08o`Ay7)KQPxIVb>ix~fHG-JX+@cALK%~*-ZN0{d z-#qj0G5dwPIjOLQ=Ce3S35~UcY7Sz88hi!KxYJ~tPk1`7c{CiZuywW+4K4M2 zon4udltsid6`s?(n<%_0qY}nfXy&m?S05{G_03PkNulwBdUNq=)m`?tvac%%@7DNcS6{u4^ zWqbs0n`LK-f|3yHxPgZzq;=H}s)}O1G9Ekx_2jk9>cpKHtGD`n8!I828^Kv%yHVLWl-*kwUrq4LFz1eAHMcWqAT z2}uMh7azu=g$1_@I6XLNO*6nE8wk)TvuLqxvrh!G=cwJ=6+5kJE+4T&y4{OzaHz~u zcBiL}@Lp_oJMfGM301f#(52uhPHyG}vP8*>lZ;ujPI>}&rkfo(j!EuHG_d$idikz4 z@%rq0Eu&Q?$Hq$bgFJV?u1}7!k8QtYUQLic)o3pEZ{J=EiJp>ruL3Q2U|a^Z#G51a z|AGvoyQXlhUa+n(iNLWGkgn1*oyDh~juppv`AD-vf#X1L_F$iAQ=IrtP9PRYL^?oHE=jYU(fs><&~ z|Bz)@@ZC_X9xovY>)8w4Zt=|l?c^lv(a>Xc3Ik(9Z-(xFIRs{-oNd-%Nf`8Jpyk?8 z`8y}>e^XSK21r`Q;B{h{rQ?N7a!dEWVpOQJ--FAP^q#f{tieB-^`9wcUtFzBkoRF=E8GcNkWpZ9;Y5cj;ZXcscPLK(Po(t=$ zbrLSD9%%M6Tw;2ELOaU=wnckcl*iXKsJ+zK^T&7QCtJI`J3r)=3G#Q?V9hI$$>%Yn zuU4r@KeHTxSG3@E>ahM>J8{T1$0M)DJOF6ereJ@GsJB998~#_40_OK(=NMOI({!ww z2)rTtJL>q*MjphH?>@cn1&IVB(9@~K4)NTq#X#d@Z+sdCup4Ly zz-@$crodL&QsQfd`kZ)K#OI;sQh^`j37HuljlUwk`y^#AbF0GP46MWFf#dT75{yhA zG$Vny?OW;gV7nTv?e1X94pQ173@z)+o<79R%ifZm)%Dd66)uRKB^owY=&r4pbJFLR zH=*^9`2kvb9M+GnJPG5Zg&$}f9u8^gzU*Uz_e|g9g#R=M;;|-gUvGv$F0BUg?oO9X&aPWsn=u#mDSgyR3*S5& z#EUQ00Z~o;<9E3SRMRJvdlNW^t z5wa4J(Jsg|4|-%QY_6bhe%Ad9kLd?O)snz*lM#%N_QXk8DY*E1+4FSdL~6&n%^4B< zaNDgOW}lS|va}hz@ny5=PNVrCX7ARn3aR`p+ydZPnnQJL`{vLh^4Z5<-^+rHvPeZ1 zM?9yi95?qtBWdolNWpIP)4}VvqfS$kKdiKoQv+*~^tn(uQz#$1xs;0AR3xrKG2aC= zYT>Q&GM$oS(goF1Z{hWeilw>@Qd@@+t@qm6sbSof}Fs@~)kydPe? zI3+^Hv3Uy)MQG=7e1JmsBx2YG*KaCZ=}F|CMm3emE6wg~t^2M7@a~b;zvZ8r{J5w} zwQBI~6(d$Hzmj+NO9^OP1JFsAT|azTs!OKmeTgf>MCJUT?nWA=MNI&1#2;%871Qnol3Sp`;^a z|LUc7df0PY=+QN;IH(vH~9Vt`|)A zSv1gh>{-XRbt0$`_H)*P3iDny&Su_b2IGDl@o$5ql9`H;27bdJ=^gS7`r{7r|!F(_FxOx?p1h*SB2#pgion zRw(0;=9LeKujOLWHxhy$S(m48*~o=M=XuRc#7uks1@8mU8GfE_Z|&W0VQXC9UG!ZH zOLW6rD^gE8yK+;40Xxx<6l;6P0NUxv7ze^7 zwL@@pr2Y$OSysOZKPerSHgexhP}QKJJXz~9wa`{2D7i*{)sftU|()~e%B%y=Tu+H_ zohyw#T-tLw(%VGm2}fS>RR*jDDkZacIx$tZ)mM?iaEF=hAc4$QoIoXZ=LD?4gb?C; z1w$^yS(md^*a>0WCR~`|HdfwnZdfcSEzpmn!dF(OgRQ&1pb4q%NB}*o(WF`R^CL%- z6$?ai@H$1`Ba60R%?AD923I>zy{4p2uk1m`3oaquyt%u=0XW0cQjl1ttQfsgm7uoF zL66MPF}t2b%D4v2l`krFxI$6yZSY8kGneyHiN`3exwBCLH4w}~JJSX(rY{#XAIeu3mm@Fmp?*ljX7p<8y^b)vN3y;1U9x9ixI&0>7I zjNR?0#%5y2Z>hsLZ(qPVc%x|H#-Ksx%bwR%s2qGheO|g8e1Py1>8uj(l0ugyr5kCP zp9dE1iHAoYAcXh0#a_Y9DqX~yG#=kqbj%#e=U2uFa)2m*+Rrm(RVN=vGLgjVq&tjQ z8kT>o@SK-?6}rE-Lnd4*6et$Gxiq6G2*8M{8t zW>7i?O5mJ)y&vG%_62KNCMI3zOLf9k%!wz`NGwT>J7Y;wk09Wgxns=AyH!%&gwAboET zqrZg@7r%wypttf3hw9&`P>9&-&PWOuPw!Gyvv{cTl%oq3XMxQW^kRn?!G}$!vbe*HVms#uNU; zmG-)J%%FDq`P{@*KS(Rn!=U;FyVon{abcih@O$@VUU|~8Cw)1V*q7nag=UE-J;ys$ z3XeoUK%CY*EJ+6%Rg7d$#Ij$&ex{?$KcHb^6cGdYji~$;n`tqPWZZy<4HxlK0h7jO z#)(4QP)_8CN2(Kmf$TVri6WL{@|lK3`p?j#`tC4c5u`&6(Vq9}k&D%UO~1}W>hWgv zeHH7#dv+Vr^#V={&rL6{k|&r$<7s9Ke!7ETB7}zvDi?v;9QMNX(7~@^Of=P0!z5m9zs_7URA7x1l1aJ^vi9l z6FuGnLaI#5uAl;q6QK>a(@=(%rrvSwarz#`u|BvyNQvXhWjf)ieLvUrGrWihi;NT( zE?nsy>7I|5e>QNG=h!PEPPcqCtm*dIBGYO5x;g0QdAAs0QRkwSF4AcC{|K}AaD4DE!5-#LCzZm4@J2l*&U8`M98DT> z(I?|e3YWK^_;s0V9gsL#^VCTB_8@D_o`MLhan2U#JCr{V+pdq^DP-X0en9{Is*^;% z3O35}H2fwBgV_0u%lBGh&fd+z%rP&`Q$z%_H2965MRShxno2Zr>F+I4)WHr}`D`K( zfM;t1S1>_N8T_hp!^EOoUX;{z=CdeGS#+J#Iat~YNYkg4DfgHLoz=9jx8^wE*|VG< zT%|Zt@`^pr5Pjg55QE4TE8if;69ByfjJaxh?Jk?+SAJ_h!u{*LI2BM0J?@zetSu>u z^KN~riiF(Nsgwxz4=b-JE$UaP#D35X`7V9WcH6|W49G*`(srhJSfH%(4zd1h;I)4V z{s#4Yl07@oaTvoXH!26RwNFPK6}f#S$PHrssv#%g!+cuP$0%}^*XS7HT1#f)g(+%B zItqJiAg|Uw@AY=gtiIW|a@~^6yHR$G2k?DH=YMR0qV<>Bn^YCRac#MTy6V2p5%LAl z(E01>;UE$C7kw9MVJ;RJ)zK5jfU4Th(^CzK3eIF>G8Tcr&a`rW*sG)bJ*8I~C17B^ zfGw83nyvqjpqB3!3Xy-%!jZgV%nmbgKNJ40tg*pg!a$PDoVYhY!IOTIaD|J_%or}P za4-Q^8wwaiSwnt(!~j#-H!5xaty%bA3}p5%1S151jtAJ`U$8D91P_?q-(;6KKtta| z`~|XtwzVq&to%$NaQ`nd6d+&VXn!B#?;`>-hw||^#QNKb^*@j-Fj{@7Lq2E`qC`>2 zQ*Hyyu>yA8^X#O|Kdm!<@rX8~d8aYsj|H5_;;Ng&8@Gdz2A0`j5 zqCmFr_jfWumiRaA3ohvIj}$unuV+A^;{V6T3;%<2_&;tw#!sD99>)Kh*#&R@xxwG} zSOs<{?tH4c%P)`vZD5e^SatZ3w`*thD|aF{ZYM#4J}GYWA#gYpnMrPAH7s8 zr9zy;G+d(uQ)o^&18nJZS=Qlr3eU+&OURN-%s!FCE3^gQuZ1l!(#i2WU6@ z`=32Z+B(b!EG0`D7W)CsZ<+8 z-_YnQaz~Oel(L}E!teQ8QN^ExV>G})-OyH+-`%rWJE%wmqjcc4OfzQ24YL}LgVTW` z+WZHaZAHl})d*hxPr(Y5%LQczm%wazX3q_)^%>&iUf6CZn2O3~y-kHs`n20Ny`eN$ z#)sJ&$$paxitDx{1$+b!4>kCLnq8SCqx+vu4fTK9x%GVn8f01OC>zckU`}}pxBS!v zgz5sP&mTJb8U;n{uFMvf1WeYPgr6`+$nIt~LNIQt@s|=Yr;|gBfxEAtKQ^RPPEs z@v31Z9|lhgVR?jMs^aA~(nz_7=<$FZ{)}D2WW~R*5;BHG@YfcYpMhDYSswDK$gbK zK0F&C`de51=8GAz|EL3p@Hqg1q8E2XCwABMi?r#;{XI^gDm8>bz6+ES(Ggkc!=qkj zFkl4$HO~+7Zr%(q?+!S=LT0SljoX{;p~reJV7*o^s>3wBbo8F2B*J_|9X`HS~bR!xiZ4MvO>QYnmlRfGd9+*`kAmKO1@4qW5SXIZTs|@M6F^bbxv6@ ziwh~puI&c-aNf6CxLTz&Il|oiaiO6gPk!T;R^z$6g%@cfI$?4$+q)IDz~_`1@Hr8> zQBst3WAZLI5!>eVWR@eVySbX)vA0^Sxrj8}CoYbNO<-OF1osVNAtWKmf6F~v)GA2< z_s>5z4r)pY@S?+5zLnP`>4$h~V|7@`^4s8=>XwmE_1qfNHL8k(RS$4`pa}hB5vaL+ z%P9dFM%|N1znQCnioUf%SxiKz7f2`4DPy4akZf?qz?e zDpH*mz?drU#4l;(E(603d2Ih;``8pcZ`%_mYUB}0G2JtW&l@IS2O!PYS(uvW?Q0q2 zNuE7q{c!{g5!pYltaf)H#Sg-33ek)d$Bi3=+!;vR1e;lW?F0A8j#^%Hg=x>^M0}Lo z76(-YQUaCw|@wiy{fWd#i@wqJaltVVNuGLqwL|->ME?6|E^}BZt zM3!?eCjsk@a3gN$u5K-6papqZ@&+@~(}1_%Wts`Fn^3sMDs#*pymD8#gagZ4*^xZg z-1w7z*+6LT%f=6_-TL*(li(Qu@EKHJ`ysG^(R)P7)@tOEC9iYSd z4o&2+=v#ez>?w$FKjoPXW#(k%RM}ot6rStmcDB*EydqOM*Goa5^R7107o&iY_fw#q{kly~%<^0s+eOMMLMMTw4(w6~H+P2)-u-0^mXL zj~c1-*FiF!Np$X`kJlGQ+{db2+7@W;))yOww`&N6DWQJ$Y-xeod>C*H1M8AEim`NWJ*oa_XVe15Xv>oOlT z(&^8Yf|68eJ2qG_#91o+QcEGWL5)? zq`KZAY`kDa3roRi(h+Pki0vu!4}@(#8lcnryQA86hfvE<_) zZv}nCGU`>(aVNp6S(FJ1r}Rv!t+6bYf`H+$?12O8^p+6h`$Jejq&UxHfsvA=SnL0n zW45QgPh7apfQ6*4Wk=seIl$w$YqEUL_J_Ns%ysw;_8%(2`H^#KHuD)^xbfE1KLWt} zwSJJHq{t_4?+Ib(_%SK%=o9R=DA|*74A}eFzBO=HO`S>S2Y=`m>2TuO_Yi40G9dMJ z6Zt#9`wVgrso=;&YVzltF2mu|s5^ucW%#3aH0|uq@BRp|Ow754todg(^nRYo%=}^O zOB47Lu&vrrc>R|q(A6ybRx{8}ded1Sjn)d5>eP3&Z#0UkK_vGQ1u^>XT!}9(LQN!K z`@>&=0QhtFK?&8GP^OkYAj%KLQmgP459o47g^OVz!q3HWecg>WQ3OfBtd z;iW6fH`QTBn?ZO6GzNA`6pb)fi^9k)d?NZwAn9`C(~}fI>7TXb81TeMVzf78e{9-_ zzb&6uHYN{?8RCGo8Wbk9H?#~{70OkVl~A7mHP6z+x1dFBLI2=3OYu$2io&}a0$++^ zE@K8+?iJTO|E6Bz)ur5su$60{_H5}h={2I%n0!^C9)I(5OM2!|yn^rfbI7p^nDNUf z6jU=mVbi&-o;TRaH_R;0_w!wJ*QU|lsbX})NS{s*Z>RdiNPT^eiQt?M9}bR%M?_o0Ie_n$XO|p*L+TT zVkhcT|6|_${LVdu8;O z$jOok8(>{9(re|HC+@NSa>PMQ?`;WTfiY!%?Q6QJI1{rKjem6dK&;WaD^}Ob?VJAm zYcda!XoLLZu-<(8b96qrK(jei)}&X!U5YE0h8&uPg4h=^U~9)QU05^1LJoI5r>5QI z5?ML)N`s2?_d5bz)6cfV)Zg|v)W+&Ne~8|A<>UPV88Arfx-h&ke+b81lPx#RTj{PE zY(HSu9_uLfiMOIZQB=o+@qO#V(dIjL!g8@q7HCUt$KzeN(@f*BwjB~8;t%ZeeT=LX zcatwrp&p(0l}Nq*6?*NjEKwZ1+gC6=c*&m^?hfXacbX30L~uqm>(|VazusBQX?rTw zS37(2y;j)+y+vU+&WP4LqGSIY+lPB3hxHW)Srjt0Hms36)nwp!LC^-*grZ0f-P=`UhwmCS?D)yYLOB^kH)uzJG!FdOM z6VMN|THGA&tD{BGwD-pDs`ZH&?#83lT0ASC2x0H0(MdO)thOYGx{H###l4)bX`B?6 zhN5}XM1-XC>g)fyC(s$ptza>d$?DCuKzqd#r-LoDwN&kIOGBiNDRstdhtLvdYQF+v zz*ba-m1-m`3tFIFy*Xw$4?lvxbe=0J zn13CghOoheds>`m`%aDAek94G`R!2n$T`-fcWyg-uBJnr({=-#HBE{Ym4}}OfAtUy zlMe=j_^Ne_1?rYNF{Zw)#ko?a&S=MGq3slJW4(TvIz2JLJ1ju=2n7^fKBE5 z=rv;SB$FXuROI%!3lpM}-^oejy;(0EA^}gwz1mjeve@p;UlwZCki+n_dF({dBN;gd zy)iQmux$SpRcCYpnFIbsMH6@Lt+M%5@?wQaVL^n@YBn;7U%G3R$s)F z>uP`2Z;(i#Vk}F1>w0aMgj!ZC*^A|(krHRMTysXF)yE*0Tdm4Gz5j`SB5SfD-M6S% z#8G1|aW_($XZAU_Z;IEJa=mP{n6%TyUM#H`M3bjV5d|JJDs*g|E)6T&t3j=J8@ zQ4ia)Nf!Ay)8$(@i;4GF4^XXHFuHt1BS}{&vW_OV@I;DM6 ze~Ws(<)?HU*XE8G(=$#ZvSoo5wxmpGjGQU++*FzuW`cMk zC0^UsdnfjU(Q*+lzV{X)web{8&rz5ghKjE@ekovd3mc;^(LQNG=$M5CS??y}gBWG& zw?TU%`ysulYm4fMI;q{3!;#d;At36|HkRTK!svg!g9>~gdapSfrH-E;ht_h zxzq(4rl{xD{{GCfE)Z_&oeNxOsn*SvW`RxQx-@Adr@pr0NoF360YDh*7}FSTQBPV82)$4%}7uo&C837;zHM1KYA)VqKM1LxyKG;9;zog zS)G`#@skqgg~P37){Do#764{b0;egP;UBqkfr5%r6p_s&Dnw8gFk4eO0cTrn3$?!y zsk40M@yK!LvCRP|l1pghw$7{uA|1I^tm=jLCLx3Q%Vd4d>d?cN={meOMzPnp0Wb*BhFsYvy8*Y4dV-PBK7L{;O8c_P*I1-TqU-PiE`6%X6pGIzmksfs~= z)8p}%$fg^4^D~l$gD*IJO&2d!ib#XjfBI4wo@RRPV0={1uouWGcRF#UK3lQve)wxP z#IcJEZI^dt5W8zl)x+LrGVDv)_Q!`@V*SXkPv3@-MABp@#IZ;ROptIDKGe+p=+CUE z9`2H3SbU+I@Kyw{R#X%QDfw?WHx;p@>QrX&B#h}Qq=Rer?L^dsv-(KbSa|h~xa3>M z%v}qqk*Wp@1$%4e-KE7-iDB>7GE*MD*1Tp<3~RIk7KwEaV^ZuoQW&(Yr%Fc$B7%}b zRs7WH;iL;{>)uhG^v9T_o2CaRP8WzMc4(${!PdtUA1}b_&p9nypabpo&haKP<`AL9ECdGQx($B1a}8r_8`aObza z*UfmghY-(JWu5ki%bN5JhJIW~CPXmlF<(229~Jzw0RJx1lJF(sd`K3&+yyVcMbOvw zV0ZX2c`XXEaf+xMDTLQF(PB{R#GP#_g{$Mx=nsrnOR24XR+*KYPx2oeX-#RW>^Avxn%bWFu=nONxUS=gzp!Z{b=}UL&%C!`QDw<T@E}>L<+d1noY2Nvb}@#j7jM@Ts;Mcapet#}xW7(zl8&9N4a& zl5D+QzhQ+dL~30V=d~!v9#aOPDucuN_&R>7&ebw%EIzSrLJ+i+rXnF4Q(te5JTqYy zQmA+O#flt|B&l&2{+6W)4ZV(pcVRa4HMvV`MLeR5z9gmzSDon#(x+o4eY|%cL`F&& zYeb>J%I|&oj?v;p7M%9kjJ@zY@ zs+MDUf!8~wc=7inC|48Sd!>1^(-|5+LNef)cx{#C>X=`>?yi>XthK03Pt=U8@X20f za~twucNG8gifE-}6jGsLo$Csy$tt-xH+6Mb>rdgtc)jmzFo{cFhkrb+oOAI#`ip`9 zE>KKZ>_}dY%O1KDvsBMYEL(>_GYUDV;gdA)&f?&V-4B9XrtmX%7%Sn)o)3*be zxcY%yob&(UlMY610AOE_ME?pQDfK4*gFX3!ul-iuXM*~-{}i$PLNNaG5!AGS)bJqd z{Zr!x?)vwyVGNYt<^EFH|L59(i5U3m0rQg$KVWVq!3lFI#84KS{I96)f1l;QNBxgg zS2dF;>GARp^E+#7GLKUKB5>?Pn8@D+dkDQYhSAciyleWSYLsZcBCQnLHszI@B{ z*mZr`CNWz-BU-_I0sDp`^pdt@#`@DH z=^VZJ7#sA0sa~nkG+v%~f*yA+G&eon`F&ZLe4T_&5wGX{Mz=OYK-d7+0w1Zg1~^MV zK4B(Xv*E79735{$j(3_u;Z?aoSuJlyJh+LSRv{P0g)+BCEDJOmSeLSZJzX`r#qCrC@e#`j?UPCe4AM!7E|Ev}Q<^?YRzK<%Z zjH`vli^sl!o*#j*#w7LqvhD;xT7W|3?))i*>mw;z+Qj!g0#k&UdV!jE`)^m0hxX>Y zJr{3&91)#I$j4tat%7{5DbhxqK2^KE*eK~c=rS@9ES%S!-kCNtx{~{+-Y{oZ-R+~) zRhNL^k~~mzw(a@R3-Zy`k-z+>s1sH1?#!!;X03!7n$5w)qLZd|jHGs=RaIK6aNask zXMlvV!=ivpD_}JIE;T{qghfoo4d3e2sE^vnZq8+b*iUe z6X(e0`m8X-=L?wAZ+jY_aqqGVQ*1*nQX{XX$!UIH6NZ=MM;^(nc?g=3=cOkafg478 z0qSMSdD&aCms3_FgFL#j6tFVxM7%NEkh0nty@-o_-6@rY->M%)}omi-F0hV#pZU-r`GPg^0r+zWbo(unXsAq0j1Mk_$Qx&`NHL7 z;*4;72&WqDHD)=&G2_;8471_#$&}*S{K_-Wj(Xg84bMaRp6EHqtdSN-pp5L!Tg(`eve(Eg1G_&(^d%R^Nle zXlxoAT$M_%f3ivNGdwGCrN{IQuuL?dg_|kAdAy!SFxlLK1^3hAtKdp{(Pv3pGjPP3 z0M%;X%#LtOW+7&*U#IWbN6oJfy+0c`H~AlqJXf(6Z!AMhdli&7y4SPP{0TKgz~MK2 z<#s#cC+@agW&-SFtFJh^Sorqja1(BjzdHQq%ynX-Dm1YEAxO8rmicjbI^9HlC#Yj= z7p>~JTK#)wap1^L`66}&L^jLOpZ(N`ssJ}4d(YZpJ%3|}qk5nXlWK*um3f-^3 z$!W7I2jVBZbWW}ick=TtPwPS>45L$){-M(&43MiMF+qp1vSOtVvaAGdajywVg}1k8 zyPiVPto|&Yku_jy-1;)=YQ}A9I(9Vv_UdY`uth(Y$!pWIn0JPh-4NJjsK0=56YS-( zXz+~JoHDT-NAM-OHWm&Ak`7WX^Hdsdv6Wdb9Dpx6kIv=!j-78FiSf(#rF;H4CaAyW z=ZQgn%2kCtlzZkpY7r})NR5B679KM%t4G{|8?=^d z62qp}@(Iv|G4xqZ?pKH-GoM~DJ{n1f{D=^?kZ;%ldheHPV9jjGwTERttx3|>1Idj8 z%=#>jn^nNJO+WAuk$d1vJY+BH>3B(mj>Yx_urH-1heGyRou1p_YBuP5c7TY7%*8~LXN9eEChN1eq*_`Je~%Q4>x=KCAi0z|v>ak++_6D3Xm&e`raJes%F)lF*%Su*KZhWIA!AqUTO~zBL!T_0nnoGDB|Q zbLu33^GAM5a*Y{u8mWpU8Vm2SrsZsTrif#e8LrqorSv#7U>03f6X{=-?iW&spwq2~ zVB{q9O1{P2Y9vfW4W@5j^^tkAFsH|^-eo+sTJVq&zda5Wwi3X55gagH_bSGX>)=l~ zxFb)(e_e-(!rcoi2|sOy)zk^eqD0wk_wji)Q0$j|Uw4(5Go&9fdAlfTI`BP zvX2j9zkFupYPxhye(HxcrNchr_8tC*Ow*@3j*g3C!Q$ft5m;<3LfQl{gi&_P!lJgK zryZNFR?wmp;~OH{=SM>5$p&Gw-*3PaV{dVDOJDqo_uuLwzJu<5f7U|-BU$Ji!B)?v z%Cl1sSb7`+`{cDI4YYULRXvf&D>7v8;ymFP)5}fXJVeAf=nd*{FUW^5d1>>$?$>w8 zM7mr}n%3N%S-I7auB{DUu+z$r6+cuur6atTs3YuyX0Pp;B!uybAMCJw&|RQI;D_B6 zDCE_UO7|tsV0dMBZB%6k;j%{%q_Z7UH&-WA)AFJWGJJ_P-tPgy0Jgv5zEa^ASKGgo z`Rq_++0ByJvaUQHKO(nGnig+ch*zHC&hA4lV%;D{E8|%6><{A#E)R2+w3HbwU1m*h zbdhOBvh#>R-u3lzoCCY`>DFi6#jWvD#HLsLiT7I+xP5Oz1Dh$Kn==DeYxy5tP1c;e zt&Q2y-7+rtwaZ*1D}MjXOkB`;1vdXPm*XRVz0aZN*};LMxf2*?pp5`t%L~(ix1MEi zz4#y?gT_NzCu4KcKgz19J65Bh=xgZAjPn;_Cl_3|#`nIBixR^2@6^dSY-Y^dh&EiT zPnYo&6Kl>{p2=N4q9>x2I=UV3VBB-J__IRfTn3WCHvRmny3$>0NMSq!7T7Qd>S$3R z`l<^?HRAm_as?f9@ZsaCI*1_irk7?B-`&5tWWIZISIYFHGU;qV<_+_*>*q12v!oaZ zjhI7}p|gUb0t~)auP?b|NLicq?@k#s@3-AX4+kUnqG#SaGD(XH$Qn%IGn5lCdQ*>5 z;wn`#CBS9`>yA)2Q85tSCkp(2@x%F_G6MQ;itIOPX`? zMkjGDS+dyi^a~mJj8QKc4|}Ge9)nDlnW}9NqwLA>P?YGN5`L7{txp_G3NaHXJc^U2oUxj`IRI5 zt?W98Zz+LuGZks)P@4&}ApI|iS(GdlE0piZfe}&2U;Y&o0l@!kS-lk zFI9kY(`A36fjxh8iV=utazQ6Ep<*s;{$JH^U)vAFT9Dr+oCk_SD_=(%$6$Xp@L0D0i1C=Q=ejR%+j4qHSB+AtN`E^Q z1LvS)^+m{y?XEr^O(gcV z*k;!^{B7H9BC9V3gp?FjD)=fa6v#@K>|XO~E;=SmTbI|DH7-kl6@``ZxDQTd106{m zNy`%M9k4Udq%Gr&8zZvr%_oojcu#HCtULC~V7{G|?O6OHPE!Lbh0wEtWM0XK|+5XqKDn2qWziFjY@ehiV`} zCLLpip@J{Ak~k}R%?Nviq`oj_QeC-xzl80*NbgMZQi3$T2j*gEsHOf&&(6)+D!SQjwCe>@+v;bRGFa z8N8&tq~*x;?B#DFk8j=x^*dY`S=B(P4dSzRW=B^zmR!`5xG0_S519R0zh7HUkjWZd zgU8+SAuC{mrRrWksHtrDP(+VJ6Y0d5uxxoh8-=>&k}VC>7Bw#{XOVXojj>($yrqKd zi*p5eqYb#o*S0t94AOMjnmt9F^hv9zRV$pP4Ek}K)wA-b$=Nm#ig1Ezyk8i>LD$Rj z`v7n!|2!qZfYEwEi!f!i(WyLS!hqGes2h7NO_1F!cF*8gycdmFkAzn^9@j+IbDvl( zl{emi7ry}$Kz3>E+|}l80R4m~%IX?ctQfOGfWwkL#U>j}wF6V=V+9+4G@bB>c6dJ! z>1^7e0g5dA2Skxq9^D_TNS}s0Zi?V#ezxUVE$ES+)GG`|)mWT#TNt8!>@? z6mcO(YuM*3jP1CYig0AE2{puy@?O9-Kpp%@+1Qy-id>cxh}IB0MUwrG@m!)z*w_wg zou6&K7E~!H*DAfT9%vW8S?gt)?utiGMwO`$EHI8a!_l8^)b6 z%XH)W%UY>Ivf_0qWD##m?LugpW^f}=BR;xM29typG_PcU_>SUKl8W$JN)_qe!Eb2tapQc}medJqkWb8|&WKc+Rzm8UMz?SD3&aBB1D}rTR^Ypp z-(8l^*2sVx20Icc_@g7Lm&|T&7x^cxco#uv$ff^JbKe0C=hn8Z1ZjvChY&eZFoXm{ zbV-!xgBhYnLd57nB!ZDh;vhJPh(7ulQ9?#9X?jZ-M3hO0V6-Ts%ozSXoSc*QUGI6{ z@B995{p+`uHA{J(XYc*&eee6e_H|#kP*G0W+_iI-NDC8re}>MCmTrgLv2fRCPWh z$%B{(LXNsA2)v4T-e({Aqv6Y1nTTkMNG0ugz^fG42pK!=PH(_ z5e}Z{JNGmv{j`YUET*7I70RL%8)E3I)Y|2r^;&}ODCXDadWpe~P%5q$j%Ljrrfut5?^mki#N63$+E2=?@qlq1_otDxN95rv(6YVV)8u4_J0TU087 z-goky&(Hup?kIL+{;UD)!19oz9i3dcDmqHpk0LetcrWI*c-79{?-Y1>v(2K-{OLe@ zOpc}Y`K+K42s-@Ar@7niC(qF1Y>QVv-NfgY5>}4PtOZS~B?RvKh$q|UGI)576MBJW zXrb(P2C(y{6_4dURS5PrsQVqrNX+Try%dfXlmto!}q|K-)mEQW_(Uxn^}c@JRh!-mdUVIEDielj!-fMkFtA8asS&purA`%)+6EJ! zPZtNQd>Gh^ajb-(tHH8)KTXN~@f9#y7QwE)ZQmGCZ; zs<#{@&o_EBZ}QiE!)ZOLn{)r=+BP6u|qqQ;lk<5B6 zkeW~1hQK$QOZwvtok3ebOI(dzorShSSG%=m4x zQo}MTEMeQlO%O$8N7<=Ptpt+vjO*4Hf^dfFr)eiJwDgjSO9#Zp@A>LqKd3wQ(5SUbwGN>%`J22}p-4v> zwN`YciBtiKZuKv0r6rQ<2-L#DsF!Sh#7&S7yGwkARUt!^76LZ@ z3h}C|6Tj%7d72NiPQ1g4_>S#h!Usvmaig0^TZ^jGn)x&Z&5yLG>o>4Xu~|I@i8&jl zeO(bh__n%op+rPz5BXA&m&0XWhhj<_j2Zr-P8F_*jxvRA>X;em>Wudprs(xX+5l)% z2)t!u2s(`ul!OPu1)5f;_0joji>lp9K>qhH=*x?&qVv| zLtgBBN00lG1EDl_7%E>B$u9+|xqtFLZ_z{X3O=>mu;x?OwA_ptJ!n*0$iOpBJgnk; z-@qZn;==8Om8ZJwGEmF1--0a#{=nKg&X@)p(4d0y1DI1ms|gIqx61Ei&sgEV{sM<| zV5@JBFeCg5ET>K&7JH1#VqU5jddSg~LQm85eu0wm&e0%^Z#(fU_hR>Bp`52cGd?sC zn?Jgq8vyS}7ON^8f5;kd8%jbx(I%-=g`J~J2DowRg^#31%zC<^g%7E1Z!%Lo9sVTU z?nZP#pNSXdNXM$FVGTK-Dul7~0417$Mn0yW_=9tMHRhDeb?acoo@TF=4TshEP*2kc z{#HQmw*Nuz2GhiT$P4Bu=QA=9oD_u5=T!R)5AAy0a-@oM@8hYq*lJup?-9J|X;_wA zA+G*oRx#W31A9lW7+#b6mZD&UXs?%+H_&1)fRD`fA7rt;JTi7kl;fu*Jq%MXe9-zn zH$DcSw#0WzhJy=2iGO%J#($nsX)z#Ar_3Z3Clz7Ka~Fdtm4ZLD5s+$$f01gfidUV* z{cAkI%YCa)FQ>}@L2Rlgx?@=(qkDfayLXR+(cC|*H#lp}0rgM?tdrI;) zC`>aia%-cefl6sud~>C5jeKSY%W;nyZ_^Gu*v=+BxXKw{RP6<)x!6e>P?9XUmfOr6NzLPdgX` z*$c)PaRBDXK{OG^zdG_H*62v&Jzm$Ns=)<6we@-Nn9v=R)vf(D;~sS%#wy4y-g^5n z^Gl6%1KJ`$-uI8_eTES^HE8vAJ1%`Ql~*#TAk>SYUeW^t>Y^CP!}@tUfD8zNl%EFC zx?vB3JlNe|l*W!}*xX#U!-S<5T&Gvt-n2t4No|uiCbz%0qhyY34ym=;nyYO)zg%wE zEc>`5gmSlh;m!d|H|lQw;Nz|6)@tr_YXKmZhQ^!?~We-X8K}vR3aP~ zTKsNy86X+%G&TDl^YPX=`Z{<89M16TA5i!RFqk<}Y1`xl#q_NXjSuNbsVFht&GuFL zzEs2XQ{F6;QjzVchtsmqvw-l}t@{3mU?NV68F}mab&%ffb@8VWDB^{oeq|W%&J2cJ z`zi&*Q?@C~-Ke7S?ZJkboA4I|wXHEbQcwD3&9n7ryR9XmcQM-r3liqr?M5ha_x1|t z^k#Pf^MQ|N(1vC&l>+5b>yr$QrWoa2Gsmea;H1Y5niy|moD{Q41T`nk zywGC1@v%<)<109~Blvlr>T{dU&5z|BA)9uFdFgF~o{ChB`q5-V?Ilu1c=kQ9%f4ww z-2lIF-vctMc1Z9*TDj`FI(1+JvV{UVR!#WLhV9wq?e(=A`uKG1iK6YpJkOI}n4pR( z%g77h0QW(u&(>v`1K_~4c!wg1U;#Dx?rVzGC!D{5*&UcX8&#g zOY z6MnTP%h2vwkWpW}8!XgIYRgDy0W0_1zcwgu0!;5erxl=r(5?=Zkfrj*DLbO_QTsup}O@E;3Cc_K)k{BC%C zAf|ay1f6;NCyU9%g0JM=Hu8PHnZuT?Mr}Q16dTS!REuaKM{n1Ttph~-OT_fbrgM$2 zU2TvV&jHD=Wf_WPPQCG|+5FVy>5Y)DIwplcz1p>VTfdf;zq)t#KxPNQoKe$(%q@#I zpYJQw-|mC2Y=?r?PjRd@M3A;STq(Wj9W3w|)I$_O7%kUSoaw$^?5jg`x{k+8ii7U7 zxKb|+%uznLrbB{;4}#l<)-oWw*Crwsgq#TS9~Y_}QEVS+;-S`3=&iA3riRN^g8}N) z(5b}#1D zBE?;K-K$4{2F#o&$dGE10z`j{ag9C_W_u<2gMEBJ|t9pVM#ru^O?MTd@9?IT& z4m0o+FkGU7!5{PmPEi}GcFL18ZF9SJrJk{%6nqQ;KFD{uWsx$FLbGz z)-iD|nid@h1|HBhI)DxeU!6D7Y8D9sVoNp+bCGP+$^e;ft-hTxA{TB{WKOQtG+#8* zgbBi|wlaoscRsJ(k2-hNPJ-t=-ebYv?L~ren?|m^)S<;S4XH~8XisovSysOXy0TSN z`Jfutu>7XcgY@3JJUVG$?9WZ!iWwhhT5@Mfn;wziQoUt@T*z#1U|wIoL37% zgxdG5`dRMkCc<4RXg~0axM3`|uv^)$chdYoieGiXHD{YghFmaqQou!?1UYLziON!p z%)aazNaLg2?@%6CG~RXKNzBpQ1NP!+wkl_*aYxPPOy8nH{%F2G>Y9h(_MiTly@Kzi zVdW1;3B-&5?3*8{S*Z7RnrPDzU`sYuk0g&4EOi>KH}L~H$2H6? zgO*4JGqrZtUp4lPIGs0I{(fkmOyxRZ45A;2`ATiF`z zlEybs?n((rp$geANL6k~!$rQoIX@W2?7>Z2;v{;KxRaQjg)QN+`0zL#rn-}PMww3wUlrceq^XLm$)mO_${oSIhk!?2>sd*>Yhzu~ z5-y);ESMN>4*WzL;Jn`gIlBl|fP{8+rTUqP;72eSHIIGVF1lIVJo3)2Z| z?|X#;EIK6mmdvwmW#NEbtydkh9UfART^Umg^I!CU-M5TjxPci;%4Nj5E)p9#_>OEW zh5mpUPvj9eO#QmL<}sQ=Y1FMxbybMT7GLO4Z_hkagwuhB<}R?mIbx zzQ|Dw{WLI-Zq#I^XM`dQd?p&$*YJ{7mq(oMx^5&7^fFKyRpo#@;nMa`1_w%O)bf=4 z(-r6Atx{fQ3R}639ry`+B#{Arck;KU^L5H-hRPkfunPnB;2EJ4WkM;MVy84!bg&I8 zC&UuXd09kQC#rRRQPV-b4gR!BeJW3!rGOUC-4CsJt;0LW)w`0Y%YyX~Tn5G8H|aF*FtVsnN?LldO~$%18* zr|5B?`X~a$n6U(=${@jH%JUXk>&h73}-MlCRB;BQ-*_s%u7viywC>cgfKg3%lyK0zTaphHyv9b@3+!9HKLr<-Tk7T1jb&NM&I1v&C^3e4KU`7V&zHM7b=Fh+i z8`7dnKXtdoXT6WNzOTidb!#Z{*KfUwFLo1vwbyUSx4t=NnupUGwCkkf$AR(_S3;ol zr1o~i=7PIdE&~Jc%Cs|GWzQcu`;$PYq0~1PLLm}F4=`q!JlOw4Qc_(dhf57sYZ4=Y$ zXAYA6k!cl62XXc&+5JsAR|{8EjG7H-Y5wR;zo>&*R5B>YGs#x<5_8x;&1!WLnU9gj zhb~OpZsuA6Id_2wO^k3x4;zoD9`{;U(Otbc#dNYul)){J?@zlE;y{jmpz&J+8a-gaCq-IH4DeSjhT#` zh@~k2oWxv3!eaKx^;N?iV1jDmX#>T0@#9kZMJTVBbGOw1S?58GZ@uAoXx}@P91V@g z*;$QZf5;u1(d)@`ym_P|+(V{>;%6wsZr{fZehnECNuoeh1VfCS5E}61ZY-EGjHTj}sj(TIS&a z@|NOekPmCuS2QiHZW}a4AbKq->{7ZE)7MDDE2M_G1=P$;O%7$?TR9;g{%uPTbhp$- zLBBWEZ?x|2+KAu0bty&NthnHC7Eg>==v6~`p~3}FR)!4$G*F#QPj)6HywkH#gt)ZXBMnQ01twhPGxBt;ktXkU!8OwEoWSlI%A zjBRZu1$bZw63anCKrpAjy7F@`-l!h(#E%Qqc5U-Ky z@Mz=w#B4eoXHNg^t9`pU{l&{#7nq)NL0Kn*SU^!ik+TQ@d_o0yIE*_V+|+>YJS~kro&;J|-_TX@Bew*t1Os?7FA-t<4O_!06jkwA$*G z=sa*c_-EFwJY#ji7W82BqyB*^W5hf=TytB!h;LeUFo7Ode8AT9B*qY%jKYGPf5_IE zDnQ?HkL(rA;54OI(wLwfA?gn?!3UdsxDPVx(L>JwS0ev~;DClfRU)xbs&}Tu zHd6beIi+*5w~`47eIk-{x5(KIUo0WHUpvP4k0qzvCD>Xo{BQ~n- zY7H1cKjLA9f*3Zl>gXr?Fvnk9h~q2c@ckk*J$~+66&=*~D9^=CVJRE{b$@aH^zc{) z!|Jhg2D6X11n|aF4~uH7@AIn=Ws&xl(b`cx<65j*H!p9WpNh*ub5-iIC8hhgmNQw{7tZHhs)>F-bu>iFK27$gUOw^^NmOD9r3$|`NpEFsk|c_A@SmULON#u@Ud_sdRmf@ zRZ5p8{v0|MelHW~Zqc`cU%8~@3KMT8s;T2VLmf`acKtI}6@iii z+vN@0rNs@;mqmoG=nk&Y(a~yV#o&1-rmxZ|jL5o51X79z)ui~k5=i8JcaMmntu3%m zN_Q2T|HeBew#%1cwyo{$-AD?ed#I2~$vQ-4yFvqK>%K+3myZuj*jjMsHa0mNB0~^c+k-WBlcpz z4?KN`eF|~z^#On2DM(QJ{|N5O3 z?0)rKYHExOt@Wa#tb6c)u?RX4x!bAuIYy#k*~~(PVA(4U@1Tk-_8M3tVXDQK%c;=| z8wyGUTN}JT?9Gxjbr4r21Dfz$sFrajH=9}b&?EcoNOV9Ve z8-^Wz&0P8tC+GLbeK4H|Xrj^4xxebCNAS9aZ;G~Sid>ibPOT$f;Mso19Tx6e_%Q3i z^}aAmnGem_jUbPWgQ7e;qv|mejD4FsLn&>Ysz+rfR3K!5oa~5bLO5Nu=aaMr`L8Yp zP^NQd9disF8~I?sM$?7_RJce;!o15#Y2FG7R7IPO1uJ}~BI`zfiDsGq=xbA#Z4((i zxJa#Q!^#N6Q|$qT3Ct2dNAzp1r?Iu&92h1 zZ}YLLRN7Psu4w-!L9?>l@Mzq_*Sfv*z9Q}+imQQs6Vuilx^Xjn(-{RjbJ-)om!71; zp-Z5u8=<}zJ+mm9u=_0fyB1Llpmv-Y;E zjGcgJ0690Dy?ztvf_6%PykUl-kvIJEb|-ChFuA@ZuQlZoW-ko7UHlP zQYPA%E%8p9bV5vOYIf#~qWOI!C~{=Gcq0EeX%;gY7FBdLVl~D;7H^!&i4O>CkRHv4 z*8`WDg+#X5plp6VZgIwtw&V_j=wg?F@$wfT7fP;2J#kLf{Jv$eF9}PX5V?Tsuoc1K zZ&g>((L;^=y64*x{M5+Ex}x@GaiPE!C~f+bwEl>J%F3jq+{?IyTuQ%sZ#!VFv-NpW zK|mw#Ft9oA(8#4HK6-Ds{lxpWwBaJ2Bu3N5^z@%2pgTZfkdAKl#VK_7nXbab~5 zI24SbDx!ww>xxGsn_t!)XGv|t>tJUKSm@|H?wwWw8ouOXh{IdH8&1tlKpGp&<%++R z7XxD?HyD84`e#+K2;&7j;jWz|IDw<1i?9YP2r2DoMe0(UcB*abvRPV&+-$Vgy=0#^ z*vHDn#ktmJd6;?Xyd8d-8(^<1QS)ob(Zs!99xhcg*9+|Qc}V`Q07r% z9S_$pp*q}+Id8DZ9U)#?0@W`fSY4LoK5zkOhwWNuAV!PK#MGf8$y72pDUUj!fiNo! zPf&l*`vc{>U@XA8*n%3K^FM&1$=E(d8iPStCP#v^ zuTy(Q>HO5gi#)J`v^@Fw_H2vg*xO=y+819?(b$XFb-o=6gQU7(!GOIKi;O(72AV;dnx;1KYDdG?6?aJsee``8&BSu|tA(67 zW+GyCtO=mTWdvZtgDq!B5*@B#i8!s*3<-MVo^us4cd^e+=hkIxODFt>?>|X|5kLj%gyb~b}Qx5P97JwH_yW1SVQh?shQ5Kv5p zW-gLUshLe_ywU3M4t?2AWDT>XHCwHIMIYYT>%uD>)e)hdTBQ$UA#dS@=UGl6nm+iT z7CoZ;tRPd!YmgwPuU`15k$5*(v>Z zuH^qhTXrnOzaKkI=D%+gSS3ICue)#Lk5Om|HZ=LsnI=;oO;hwIIFI z;b!5*8iSluc`lt{?>lU@CEXDBtHryAT<2{)JVa)v{k)Vh_~uT-rjZE|a|NVaG-P0l zdu|8aXcRBngX>9|jST6^7IE_LG(0ofF7Ulvp`m=pm6_O3Ge}VP%zm5Sfx(_w*Nbyy z)p7aOCQi1&7j4VA0!=*2Q>ERx#N*byUNxvKO6u$h2qEUpaZ*I&hVl{&>_ zMR2Yq4G^OnG}TF`?n&NbJvEq=A$KaSb+l-2zTlvQ{jmea4fxHT(XJ+z_pAat>_OOVLs$Jo4DOJQTfA+^%XD<|OPQd%dzHn4 zY{~A}#yY{m8UAVr6kB})AZ4(;&98{$8+v<4ptR_HYx6GJUAF?orRstUlOE5n_zLBB z8UMc6=BFRmE7)st#b}Oj!*+oSOq|;h+~B<0F}8}gLF5Qi3n%_cL`-H84{R-|xj8o) zKgC~{%zP?5JXi?>e`GLX{=CQasdBCaa7#$x1!D>0=v&{GrlC#eSw%YQ4`QetsVU`k zTP0Pl(t`n*t-4&-`%2HI?&BY|(GwzzP8d-FIlDMNJeeErER)fQxRo)FBr>7l1OKZm{HOISgquVb$I52z)oPiUHgWXE~AlRr*wk zZexJre_YTu<)z16*)vx(E&`&*;4vO~Hl(BUU|~+dx=W44qkMKV z6`yJUlbC@|pjWj6!x8BxpvFeAH_0`33)uXVcem+q@$-UHPw7FEJ6pf;3|pDKuw%Cs_;aza&1u-KKEFQl7+{yV~Rq39W2^rz28bd=X zmaqPvwFCNVtTVF7cbk(J+(mY$vKKv0I#FOCC2iC~RSR3Ll<0sNlgehD{zmUR1PRKI z+4`C^*!(-_^6eu$pLBFtm(`y3j)%>5tMR$j`<+RA&L0ZBs6H~U1M4DsT`7C0&jV9| z3KOB%RKf0Ul~-v9>LlgYVbg9o2Kqhy^{D`Lk65EJ9vOS`n4;IIRK4nCmX2%7v(K+@ zLbm);=D2PcMh&M8H%uPgK^5gnb=2pqDGE6rN=8gWuhsVqOF`@AqQPeL{cz<7yIg6S zH-7fqSWd69|8YxnPlV8a_J6=K`pYl>2V)#VpPK!j-MoL;8NsOhAN90NRhd0RF*I!`VLxcqvD*>__UxO9_175jk4TeYI@3A-=D-?IW+3jr%5bUf{I+qET*mN_#*>_r2>Q zP3sY_vcw?|K>#CLg{18bYF1hdD6@!@%Bs8W?NM_E?jmQ zPDI)L=;_ll)J)IaQI%pW^-5#CKAhJ6+18J{?5`_C(L#LbWD(%+Y{DyvY<06 zK%qY(y8PVDoE{fu1zTrrmh*vu`=s{$7WwTH7Z5b)bbOI~5Mv;p1Z)ItQZBP5bzaln zdTf%Mx1W4V^S4ife9+_?N4qO;tj9(#n0P03-b*|UrzCt#y?tejb5g9mFB{KD4QnJA}k z%*}!^E(O>Z8m1K-zZ-Z)XJxwtsVtpaRJAM-k(UphUo)9;6d%P1x=0Jw?Yq;|<=$tV ztJrZg(v+U=RpiaguCsL*Qm!t<>rCoGTpXKxB_L?u9(Z)4K4iIH0b7ZauyvUWnVI@z!cao{nt7{8JTFM@bW0bPVYzZOO<-{es zw8c)A+8$fu^ALa))EJBePH3ho(ee0 zQ^j%y7H0FOrS7!8R7S4IWyzS49NA2~!BmmY7MjR97IpLHYh!%Icxt78=ZFmfPYE?Uc3+dD%Hc?avVo)y7|Fk`}B`SKtYVeci? z>IaHr_P(7yN79;@#y7w4+J|fmuqJt3gluG zBt{vwv-V*Tu7a~Ik#U44Embv90R`WbTgmk)`H}S})YKGg3L@cjeI7CjSy_fBgZgkK zI@=)5^31Bme13^!cwf|}de>ON`I)YWapgwr!O*%~feYATncpEQf;0UL+R{{oW};{6 z#0fe0$@@3!DoPd==Pzh(iV{7tS2{GYTbsfubF7fQm8OwM@>lTQ1+P~p37Cb^{|@T--yboNJb(dhCp}|CgM!JO15Q9 z*4Yj+%~d{k6hrEB2sCQ`2#St9jZbtdc6AO53kL#kmbzE;OJ?pwK9XSAMP_d9d`Fb-s znQOcA%*e4~yc5oyne)1MCv0i2v1)4feLoQ+=A-n0@mR!)jH!FOUQyXj@iGDQeBcIEgmsR;CV@u(^ z9!vg8JezFIbv5mr1RuRVTcl`D*Gm@nUIIik;uIFCYW`=vXqHf^Qw4qrDM&_d}_D;#ho*sGx)pF&C zg03V4`r!2?97a&GxH$jtD-}}nC8QHTgPvbrYIOu!S8gj#;4JHd81{2#P9QpaDT>Cq?VUtoX;*QRt zKI)GSTK7lv4mZ_-+UfzR?Lfd^B*^3Ql%BQueXQlnTl!Ut#0yie-L+GloT-MM0&5-7 zd40*h$y8jURgVH5!k&n_btT2B6Lss8+J_?bYv-WTZQLN2yi?t|JA+ep*RZ*Ps&Us>d1>}3XYRysOeSgH^U?r_B+8wECOCPSw? zJW-eR~1Mx|s&&s@Bh<$@{MNYm2ZcU%tQFtP@4Y_O~ z)#j$1$bfZaJ3DZ;Ac;84jmvaPI(2}kh(v}w42G~!qwl)sQ(f*h1Kmm$lJSZGQNNHd z;!T1sbQ*P&#)>uS)^|qZAgJ|&NKGfVdQ#hsVlDT|LU51QG%}B@z%6|pD7uq3JhX*g zSxpJsMS{A`AwXWcVo}kY32sp*#D4LR&Evc`Hg;0O7+w^V*jOBu_GDf0`lc9Hwr{`Q z(zV=R%dn;!eHX2mrtP&HXP+a6qip5mA=6QVYPykv9Nr{)72k7yQl!b(Mf}5R?=?Sp z&osJ^hs&5O_(?%iABTYKxe$=tPDkf>s8u7o@LrgXw(%iu4yTu8GRWKmy1ietR%7`w z5_!m&WZR1q{5;wIeY&K}@g29oJhE0#9lD)bz<>?Njcx6>m92lWzYcN`I~i)08O-0o zRK$}YO0YU3&xYNcMjS4+MfX@g@`AC%Po;LN2^@p|7B_wOmOzEs8KYS2R49vpNd5;s z!@DV?`pz1Gg~N@caSWH4TaZ5~E_=u4jTu?iB!c`vhQ z-XF7(b3)FpF&!*9WUqH`ZKH-#IAao*O-}feQ(jB}fKNW)4Yr3DW$P!z_&&14Ka{v3 zS)YCu-Dc#{IF5jBaDP;hM{+5ho7Yg87mrMF+4C|2BBiWGNuZ*V6-a4qf>FHS>ohvF7I1eZ{p3KWL|!3z{^2~dic zU>o{-o_F7O&w0=8IlG&Ga^~JU_s+GMJD-_v98g<@0FN3E0|SFVO;u430|O6&f$_K< z=MlPOdN0Tb{rAFFRZj~8BZw6PBjOzf1`1si@e2dPj~@dAX^nv)nS+5r^}3)_R~iH3 zp(Rigq{Pa~&dtp$Dk=d0C@Lwbsj6!7@CpbDiZC)V)6uxVWV8@hMNwH|FLxLO$IC1H&dJ7VqB0^C+0Y;T_J-?xNA_!a*Z~ zHhChE-$i4$`OQ;!RqgrpLx+dQ-@c7vw6@@h3=nj#;*rr4dfi%HULz7VCFId8p7fiI zjZGwCmQUM@nwo}_`z1HGvVf3kaNI2|BQLM1vE^4!g9p{Cf@-3`FA>(x zU0$KvntF)}s&4|`oq7hZODH;nEXqKx^>!YERaK38-V$$HmE%9FY3T6VCP<5Na~i60 zy|EW5dM7(jrXnRQ*qr=wp;=Q&R5;aBc&konu2JJxqw;W`yq1EHjtKWe3eP-Lxh_V^ z6~x1@<;AV9C+hG0GVi@wR|}h%xI83JYwU}Vi=#|hq9UwZ<4dQAySH+4&Cf>V;$ocHJRmeyd_+e+5c64tmM zx)BrFCVa1=rPK4}VKC*cGR2lOIY_WX%nN#^w3o%=4sq()lZsKTI%Pk!OI8Iz{;~n3 z9)6*3<1)Mh-zK~-a+d;Zn^^CezFIf7N>fy)bdP)zSB(i7#Hd@qEFR=l?@B@+L2O?& zEhTIu9zH3H;G*}sfS*S|03;wTDI_W>D8a?g zFUil}7AHmVzc9Fa+BrJ}{_hzE5s}qs2KN6kg0Hi?y^rrJ_t*bd9x+K_vHuLRtKXH5 zzA6}MiU81?rTv?@$jz^zRY{MS9}tU*(MfpPif=i+d%P&Kq9uy!J>)U{)eTc610W!Lz=_e?WMPPAEzv!;aZ0^nW>=A$2%L)5`J_8@f5?SB5oKP z80e9eukP8uYZUX{{JuN63-JiT5!vg#pK8mnb4P8|<9({Fth~*~(&9rX6>EB8A)uo& z(E0ythlS%jlieLrP7CL`eFjYjmFZI4RW}Q9G|i(QC0Y)0u2A1)E!s#hFpMJ<0kdZA zgI;^In8u$9%83z`_DYm0w8n-)d-Tz7XlkaDfK3Ip!4wXGfKcBUPq$16saI=oO9Brs zH1xQ1j0bw7aGASvU8pYn;pujSI%m~skmbVp_UGBa`A6slS3nDb3?-H|}dvv0EdICvvABnbJ%w#gJ~i;d202?D=39GK0bzg6$NTUSjj zGZ+tmh2h3^1cnidxKGdOqrQ1Xt1(VDbSc_APpEw0#5Uhe7wg_oz0;|`wy&~IPUE=k z2U9q&_$u22@avw;&GsB7*iif(Aq<9z&#CGj_rUB8g14+L)8MCa@V!zhL+6or%(D?Z zYL{1xu!15A9OMmObJ8p>XH_?teT{o30YazqyFdrn)h1)(xzuK%6~5H$`_T590=-M6 z7vv5^M{;i=4>1H8EP;Cy#=O5LJGAA$du`!RYM%880Nn8e+{Noe;4kav9c0yWL~vXH zVbxfjA~<5iOi*eL(|AOhI*_BjV0zc##+E9L8~Pc1=&&j^!kGy9x}@QEDtul&r{~G$L0_l zZU9zCw!JIi-L4`7qb?`e#9h)My~?+Dk}Ehk$Wa~omGJYC=WdBNvsV}NJ&J4H^2vSG z>?vCX5qzoc=Q|ZfPDfQ%@gO=<1gfXmqSt6s-L!;EkdJq_R`d8A^lXJE`_O+V^B3Ln zc1&#|zYZ#043_PGd8vv(gm}ATXc^&>YYemQn(<@-3ChpSWFJ`jw4SB3C(6R>&zm-XbkAbgFV-qaJc>p-}C*O z{|Q{$VFd1^SGZQ%&8IFVBq0Ua>j8(8bNB=*AZ^?=6rmrIYCU7EjtKlDC+ka-0izJpDA%RZDi6F5hJqc<2D+ixQOszEOV5Z%f1Q5 z&X-GVQIM>Y>r_e36ER;LDRyYeWrsPPt9Qwat&#~Rn3@t14L$RANspe1OWlYQPpMhY z34eHnyDUjINO-+NzFo8+L0Jfm)zd$G3l{sjMlqv1fZL%TcFr*s%vgE-XoPhbUMQL* zrVUg$i(?4$j+5M2jzsHdQTYN0jAX@o^E$abZelIdw42aYHN@e?Jcz=HynbBy&PUP5 zw0;JXK2y^1z?7R=0~0RpOZ$ODo7_6lNu<*N9TmJ!&1sy z9L_~%0`lQO^16}2px0F<%-M;)%c(|<-_9HA<@LE+{WzelGpDB&&grI?QhK&(8A9U7 zWCht$AqMEcTcfZCfV#g-SM~6}OKI?yuMA;I$;W)VRb)OuxqzQ6rO;vxKB!fgetQKQ zv_Vj!V(BswZ-VO0)@ilzAw31+LvDefS|}GN@Fg&}I4ieD_oNyJpVJ2*)3%s4QZl~w zt>qy&h}S>6TFvrO?d&m6N#XE)aO0?<&ivwou+(6&_ZM?N83WpLW3N~|JNX3*L zG5K;3kbnlZgDnd2Ir+9JZDGzWg?>#H_-XhBFU^}M#D5uBJaON3L*!wj-a$koTe!dY zjhusJkO>=+Im&)s?9%;w&wtFb9g{%AGKBiEY>EZ_tFAG*bF_HX*ZPVypK#FfLInyf ziKoMo`)1&kQ%}&lW-AZeJ4&h~L-fv`iQSgLdtT+jm4O{4yn9t-BnYYEyPmbHp2{D+$B)$xRg{V1bCgE}N+|JQ%Cn&n!z1ZUGg(O4$b^;^P z)Je!d-n0fs=cXKu?7Ip{)bum85s>L$c5yqgr2s|hbt%0}$?CXY2P4sc<1hpPd)^;3 zKSN9^3A>8QcB1e&Cv&4ZONAy6V(?l_f?S{5XMLM>O@8*edaP6D{P-LD(eDK3P&qQ& z>%d&vEPP~#g~KCazcz;&=VijK&OuG?Ss)6bmV8tDxDbyDq(`40{OyP9M`-}uVruvV z>o*VsBPUxN;I5d}lUUFW(npv+OjC|jx{BwKRo1kOmS{mZJXR`f0VL}-dFO960FgFh z(dNwo%T z2{!#4&fCIM>Fm&lL0D!=oz_V=_52PJl z0S1)KO#pv_TE4CCZQF2rC{~bbCQ*itIYYeCkDUqIj3Z0`L$wNL_8`s z0^Y~tL{UQH^i(=sTQZLy07A7#;Y!AhK1YfZq>UO0I{*rgU-&M?ehC3VL5h8Y;EpN^ zS+XBsqk;Y@d-{|CfX}{8M>1yvYFwsOr=y?#C8T{-ZOK~ouzCdsk>kK42_Jc0t zP>^iM_9{NpC79l2?yPZpfC!;-^&1yy+j}8Esr3M@@u&gr1NAdT(6)AhKr^Ol+;~ui_t@ZFIA` zGJXdd18<%-Na_Ae-Gw+K@NWnATMbsn?8=x(sCeFC8hAW`o;;S7U%7ijd;Zo#F-2a= zUJNeas+timcPwY2rWkJU?^cns30C|4h;kY%{RDxqD!Du2a#A!GdDCjbe61~fbj>yl ze}lR*e6ygy##M%}sHMy4+0gTrVgeOl#@rDbJ|_`42(13(l)(RV&knmVYD6>Bi9MM9 zIpF44LqO&P>#Sp3K0e;&kGRvo`S)r@o7 z(8n3RBNV|;K3bTnvK0SH)n0%*{MuT72YI|#Ha|p$D6{6mz-aWCCO8`8-4yeaZIkBo z{;>W@n5q3&-w;BWEnrrG%Vo*NJmjhQ7jkve*>$ASfE&M;yv7iycvhA&VK~8!T>5(| zB{|6AG45r4TcfR||n~manmc6Y!>57??bZ+Qa))z3Rqqb(ME~cN`0Kes}iru)vweM{;eE&(zUuh^` zXwjeP!uMbp@FrxJkP7kP5c^V+=iL8wv@-~374)^Tw)gyNq3U+8URETdU7W!+%^}Z} zY^Bo~E@0c=*C~Mn<#700py9O#AX8z9y5SoYLLCy`68Y}3MWJbgkj$~7`}>#el5P3a0D+ATFw{z<>$S`)GX-fBx_LdC3I<( z)d3Fh1=Hp}ro_OQQ!juZ(>`r%!8hW+es;o3*@-$X<&1h2`jO3M#|~@#JxuQqJe>X> zsLkbhpsPJpF8vlC`Ld|A`IGZtK;>(N5*%1xd9jI6Ihiop=;h4|%{#XayzX>s9isNL z6FP{1MEgAM564vc)lClS&_4=DZzz!{A8m0O6Ml`A|5j2;U$TdIo#7S&Y5uK@CBlmf z%MBxRu@-M>9`FJ3%}Mp^{474N9q}%fGX%t-o4#`JAX%>TK`KoMUQ@>R%P5Ro$YsD%DCr~1@$(ISh}eq{z_LGCb|QDf`nFO!!Lz35V41TZ8L~1B-#lc@-X4=n)BGQec|! z=!B-q^NIxFNatpO$k%+?H#V6&7767cBF{4+tew$T{8JK1Ppq4C(v#OJ}6d?7GQx~fO+T)ipYeEB@@5L%nn zRX)O`#MUE~S7gWOK@om`n3TTzzPoo9n3$Q7MB;{erC~&(`*qQ_p8amrdAeJ?A}W{$ zNhuI&eMYDf34nli3s0=PA|5ib!jh*9TQB_XxUQxI^+CkvAD4|KLa5VlTxubxR|Zkh z;1fxATL(nA{fx)Yl(ai%*Hk&>M~fxvCl&8m0VH7;0ko(9ybcL!Hl zr9r%&2+ib8-Qc>peAR1;k%xghFqUiyx=WJ}# zm*%3sZ*tgfMERp57{o8`xv-;%5|yT_0eAl8^)S!M8&n8%#J4A7_ob zJkYGU;p|7ItK`IVU}d~uT4ph{&qSFNINa^(a`rxc;Rkm*6C z-P#_@6=2r0jl-qSH-tu#{l(dFCz(gB4I3j={XTOC_8(9^AzyR4B`0U%j{J@|Knv2Dz2qs@`9nO!G-GREQDA z#{(8iAcYMpwE-ENMc+%=IjHMWlBr{fUG6DmwPBN=)J-F#t$X0?^mQ#{#Ne6<<`Mmk z#S=H=&pUMayOrw5Xgar82lGsV{)P>rISHD^%s(9;RkAp=u?3Qn6(x9HQQYzGGL6w~ zWOqs=k|5Hd`t=*d`=_73rCkYxBdWiSw^{-#aX3f2D9$!o!LW93j*R@XluyEQIS!mT zcfQrSw-mUr&&71x+kl=jcB^uYD$p%XHJ{L3xrwgHkJH^z?n90Q$h4l7Ugn455s^Dm zE8wne9`A5nTKI&IDi0l?VzLD)`27))a-9q+3aJ#@G9%?nGE z)bN?CjJ4EbnT?u%X7B$j{TNb70opDKJuK-%6yqbwmK_3|dbw$i5xs*nK6X}1{;z_f z1maM|u>#l9hg}!U99dQdy9`9=6DZ__D?}ybYsi%UxLV|kkYvRdN$zKkO~OBT707a^ zD@%_MW%WWsxx#@?s=%Am@(d4l;y|ET$gg%1HAYUI9GVR_^@H;Shc)}GL2AwsX{ZFr zcEV&Nb#2Rm_$8wj2Qo%Kf8Taz$t$7!VIc5P9O~vV-lvown6jS}8E!>=K9#s2zqBjq zNfWuGRs2plJl$|A$@B=}KnMdW_F&NoILP*C-S@Xjv&}H7P^DeaSCcz-W)dbSS6(Fb z#AIJ$NxeGoQvGHqquf~a)S&kzQY6g@snrpNq~L`%v|#Dgp4OC%Jc=usxIAX+$a3-m zh}bD@&*E_kbbEgYRm?Ebn(5>`+bmN)VA#dz5{dm*G>4?R_`7kJ=T2`p=$L(7mRgt+ zY>Ps_KuE*VL055i%+&~RkfL1cg-HhlHmLX}#rXNPKdFh`;y`g`@(Wh5`kvI!GYhT~ zHnqY{H9d@o@ib;=<*EzWw)2O0O;ejWm*=09bSv+0*w5dy9}$-V-Vh@DGs;=9{7@O? z=~%Pj$9k_yOG@`J>HVk!(?N~eOy!QFa!B zi{d<#K>K2eF~|DTlG{E9myI2usbHI*#<;(=7@+FxUn2PR6xtXz zdXu?@_Dbs1Yaz;f#U1+x4sO8xQ z)p+uXNk{j+n=NfeH9Ww<8(CQV>qJ)*E>&0`VREgy@J;xDx1p}jD1oOGzSY}IT+V!R z6khn%@r>gIy-aYDHXwA^jQp&#zz%ghqmt5l9LFE7 zc=;Q*=^S>v-NBL316;1E;vS-xHDG&_kX_I&Z!g^=n|{=)9CV4ON-$BY+`tksFBa=B z9$k}yj|Mo7JwxN}6lfgJx!a8;P!#Y!zgDX8u^x^~HK#3zBG(#b!80z+LzalNyS@AP zxCj+(6n=%u9B-i6hFXcY-)qfEI0iQj*2R-w^ZN46ijW+%= zFxa>d4i3Nqix|{s`2va&p_MWJeme;hgI+1$kpc^iMr-0B3#HIV`CTLgY=H5PySq)q z)%9Z-WP%0yWt}Dp@UZx1-|-70sw1HHh~RilY&8xC>Bs=JCizc?Gv1?IxEQiqStF?W zUcZo?mf{=D1;w??9WiLJGG=DhXlH{p)jiZHnp9uM9L$Q5-d*V!YP2Rf7DtSv{nU@s zm+NPR#`h!0x8U3Kskjj_0`oX>#<&BM<)*Ak*QY`QwO98c&GL`m@Qhip45RhFwQ;Nb zs*8vQmX%$T1*u$b zF57QwcGpUOZlW40!xM^9_WJT}d4j^c@8L z;*IhxhnwGRkNj``?hblyH6{UAAL8s4ZvU-J%UcLoB9;n4^&zXa?|u`k(eOH0;su!6 z3na-W;;J>4yk*jH-_)1}hco99%yxZvoEXH;i7sh$c>%R1wE0{`40`&Hwz;Q0Sie+7 zq@PjU2haB@h(W1&v2pf1QOk;RRs{DKZ+GUcUnzyGj-q@*pMHwA!rccUIwM!a-H2I#1e5M^fCMN_m(&D*zsoI zeuB^K8344Vh76qz&L&B}dhIdc#;Szyx3rbU(|7__ZmM6V6U@8}!cY@ZcZd6PU#ljC z@LC;i>i)w@0x;mNk?r{4ZZfqSHHzb+#$_L9@evh!=R!q>$ixC}DnOBouNGxPjrA{g zY?tXWsP@cl^Q+D~jHcUkSatXY58k2G5jxaotO#tVC7T&DOYZymaU_2y&X1O({T&3^ z^h&gmh#cQ^OugEj9MQ(Z9V84(B8vgex{8T0UIwq~~NTd9AYsC|*O^>%Mh<|G1fxt{x zGHiCFEtGbFo)0R(n2B~^*wMzyMK55r_hMt9So&{v=mvFdQF(=64WE;lw|OJW6%MAA<8Bx4YrzIL}-@>d3wM^!G>C zVfV=j(Q4?=Lb>3hjV^QG8rrGGCUU?>DlGs0TOXus$-6ITpc_ri1|>Qe)YEb`2WGNW zoqK)YR{&B>0%Ht~;y6;zjP4J*+j}fbxJ|V~8lLl7v$kX**(h=*D85oV;IWB3P-HyZ zs+Jz%(zA!s8!@e^J4wTw5r@KsTmoHj!@6W=Fwas#0XLh*fE(7MnZwW%o%_Y;vY7+{ zv`7FvOa24Pzv$L1UIKLvS`;ihCB6U?KBEU0IZu0F5^jqg{G+nTOW-bt7BzQIrI3zR ziw2d70grogGxL4Gblz*PkmKt!;M23xuN}q?rpO&E_(9_&B)!P9eY?)l190=@j^>3p z(QM1;#k<(B;$Bj%(4S`9Mbso9j|#arM-LJpVRw;KhhcoWz-4ddwHRDvs{|Acg*UIv zsykNsFXp&I%kuMe!Nvcs@Arq^hTI(>HZEc9rM4j7+Ty6k%OJzx(@>UXbxr_KDtWX0 z(Q(X-j@y9I(@3168IHv2O61OGH%p)^Tp(Bp(+}K%HH#0qC0W1N#5`N(>*bNJUb}uw zhM@npeoMrPd4}B!IhJ^^ra_9xbKg6`aoH!9vobemJctp(%zWlj`rlHVjuew|qvS?O zJ#VikyLL|-oNgq%G^wQzj=J^cL54NLT6e`}>p4!f5X$A`FGcTRM6`}Mi1-HnP1B^{ zTGk{_hGsGl|EKR@l*%U@B>ey`62G$OAtRKC?i<4`HqmPMIte)lbv{6J`%e3bDk%c* z!&Ehxn%NMjbLn8vo9h9M3%if&V!ZA$c8cv&^*PITh^*K#x+~~(j*>OwoFxulc{X|@ zF+W90eMGTS>d(AtG0#hR2O&oA*q~zzWz}Gm79E~FV9FB<^Q;gG2^*zC5VeooE^-5S zd>$N35+fEEZo5KSUM;I3cZFK7;n)9S_#g73DAmrQQVn_bw)#syA*AcrJpI0~Y+{NW zKDj2C^&+i&@E1?NrlYyDAm<&-V%7!DB_Rhft}4634s4sMp4B|an?#M#PQ}$El|miL z+sl9WF>9OCOt&;>tdIA)nc)|05i86Qsun5)`U@e*gaUrR*>}c8L%t9ig#u&^BMU;) zvWx#hRNB(th4#ggAo9`kxbl51=aC6|0Y=eXo>*7`ZK>3vFwe})-TLHQW5eSi6EquC z9A%(oZC{)f$ZxPg%9(V9olE*qnB`Px&+B3FOW(pDdR3MBDW1&i z&?ymRKZbB~bAl!VTSsER`21gvz^1;cV;5zk5UJ~e@keXSk@? zdRo2rRcwPRQNPAw3w-xd%oSb}6z0od_Z#ftB5bUlzXz0~*^x3gPs!s}RZB=)fw#?d z)JOfqZMW;HmzaKX$y&!fsMCp8w3RB!t9_S-(}Y*zMFcVQ-Q zDS)_W8`~`7+Ftj5W|xt4lRe)k>P)n<6o7NZRu`Gp%?am1&j04fPAOB?_;tvpt1noi z0PIvRRX~;LGoSOh-OR}FTVoBU`MHmb+qlPm3^2;%x_NO+dXwvmaEyUM`xT(Phglm@ z-WPe7Sbh$rVV&Q1LwBkUd%s|vb=j$afPD%X6=8Q##E8R|{o5JRGYvD)$e1)b>4oc@ zVOh{E*CC$1Zp@UK<)oOh=IG>q-laVE8hILi;p~iG`Fc&IyYFqj`}}&RvY>)nT?xjU z`4PG1Zn>gLc=DIAz&${#dLdcO*l7 zi&NWRoy}|#TkqRW+PhW?UFB-cFHQ1bCIyF1Os6$WVyi9vLqkQ$Ik{Hxx zgUo*g*w(1*Wua8l(h(Y<^UM1g7Bu!VH3%G(`2uRxRyV~nN~v(|pW@-t_M~Fs&&>a{ zBaC1d|FTKpiUQ+1fle-E;ByjyyF{Q+a_VRD-ESXgqR;@_S;AB1s{XQk+TB!Ij(3*p zO(A5ly5}w>9FK2V`B-!~NiVj*BPpHxTIFnNJjXaLtw>8PMF^j9-(dMEDbebZ03|UZ zKVR;8Qw~)ocl%{%0{ekN=;1P-Wyn>JXD<`h*<`Q6mM}N;FeK&8?nUn;)*1G?;tdh2 zsq~BJpo^FEhB^aIeZ&a;tLF0W{RBt)RelBsakQFWIA7J?P80|S}IV!RU!57`wXjhEd}0EfJeA|nOtkSh4le-Jkc3CSfU*U zk9BOooQ7|cOt#aCG~1pMBjR8^`zA)UYj|esR#rPECQ|+7kY*9R87z@3sr9N%**AZF z6e@KHnOwDV`=}PKU}C~mE}Xp3+DXW=JCq&sOkElY%D)wO|4~IWGw0#|FsiD+@u_l1 zTivj9nZWOA6)H7}BcA^L1=Emfy^nY%#Le68QJ5lm!Xk!14I3L+p9!&NgOV8ufC6s> zOJq7@nDUXr_1mmB9kGzS_)(<`Lv}QhJP{A6Btb+}*UJMv-)C$#hob(JhLe#a@>i?f z=?CzTT2~9aab!DSQO&XpnNti}EN>Jliq z^f34yz|DJAD z^xhLP?K5(Rtr)sXoGaC@s^f&AnQ5v6&o_R?Dc*>j>AhQ}98t^*GIpzSvl|a2Oq_ zV2TL$%cXma1q&|PyhcO&$X_&wWL~NFXCK-g7_vimHZruoENyYaS;d;p9z*n%J&Ckl_<*x}D!IHU|!rDMJFO@dZ)mn;i6NT%v zB4V;_o;Fu_)^1HpHuh}Il5m;AHD0mKUe9j{F7(#i1+YY;tZT6UGC=PiTS(QXy@$kn ze%J_Yc;)7n&FEh|p~@7}gRSS-g0o>6Lw05S-8Vd(&dnFmW_ht-5q2 zFMCipFIB7BfB-LFnR`}uzg{f)UU@XK0UtW?r62*d@Ts3Ih=+h7(?eWkUSvy%v7C%j zF!gWTU`uDnz{B9qh=$U27qB8TuxJTfYW|3Wz~MDqk|G{AACB)PM;Jlp#D(Vpk5Q zvL%JHNEOJ@G}2~@q&Y2y4b!!2J_{p9t;2 zH9<0o6;>4DE82gE>+)NDX>R+(V^^;sU31#q+4cL_q{V&y(R^^vc8f>&@SO3QC}(ReB=T=6hKuVLu%OZIWloM?O0m|x}*6~aHX*PW|a#KbD4c6m*- z?I7q!P{W6ixdTrE4O~tF_*)rf0e(WXUwt0La%dcO6dpWl!Fs97AQA-Q}+K>f=x3`z(ezj%)o!BDK#8#jF%5b*FkTQ4gZQyc*6Qzxb^U!`%>JjEY$3LIJb?-qJ`t5#Svp{U(O0>X&Mg;E(3=^*ZuL;_PvcdblG3K=9rXTr7 zKmP}Ipwlzv<{tT@(USWrG=Z-AUkIH4ssYSRJo3l=FXH9g$o~r>0*hb$2K@LuR=&@! z_M&LmH1H!Y8u!7#;1_d|b1i(h2WhB3slPA&9ie!G7Hema-3d#9$KfXBsWFREW@w#j zw|n#LJ8Q@GdsGx7=Q$jbXuDsucK)Z@#R+t=1Hs!Y%<_vNar@373_ z9k01?srzP$1rByM4OGTd(OG88Nf~F3kGlHZ00z}*OXn;xE@Ia~Bv~Ft`!7+B5={LEtUjF87qbXdrO){)i zGPs%IZ6*~oj6MFc^+kd^q-a|}S?XL8u9|q{S2ri+qIr^%m*Sc?qEx5gf9|4^f9H4z zozpg`x9nO9`Pue>XFxDRcYqWm>GNIOBwYBPtT2m8K9Mz@%)#>$4z>c-Y&iXi^8*m@ zaQZpw`nq6k_};iJSbPRALFNJhci+R z^wmxLz62>nIXPnV>`nYnf~jmeNfXOq2Ij?vEyq!KU+V(BStILrj? z$VJ8BxR6TNR6OeNdT9Ux1G zl4b&yY^w!Ug34SCj?rNSJ&$P|WK-zV6?B$)xpePEe~7+%A>nbxJZoUx5T=1Cm964xBpPCwMa`h3N^_$^bLaJ&j;=`WAK4xF= zm+5WPjydbeon9Fo9c8_&;m(A(FaTtL?GVcopAp;x^x0kz?}bpqBfrUUfsi(jQij1x zCs+n)d(a#wQmyX#+0S6pDqiPl+|pGrm!X-yQ~fVvOB%`+ReNXF5?z3M?1ilm0?Gzm zuM}>|1fbq(2`B&?>N0-u_PCg!7tqE5&qY>S+5L*K_msqVb0q170#Ao$FHMv#tLU%Eg4xH z$@rSTr6nWRNjzavbLKM7l%%X>B0Udr6n#$hnJKRR{OeLqP9r0=fx4owS%-Pr^Qszn zaJNeL6stQ32d2!_WT~&@y)Z>>bk+SiJQ`xi^Z=1OB$jF9ka4DhRvMasgN1Xu^p<`D zJ;x&Z$Eiq(P(n0b)aSlNn_1s;ZHQKR&4W}RHb$4ta*+No2`HSRk4e5NkZz)zc)|&l z6!)=$cg@`E=li4af`&G?KfM+;Q<#SRZ{lvJ|GKTXf+heg1{UN45*5HQ6-^+MmPiz- zl@jCNRsRz}-8)AJCsq+DJa6agFskgWg-s=7hjmW1$^Lr&sA5tEo8Z=fk_*Z}ij~Ro zw<>02?~aQK1LGz2XD~I*bTK0|-L>}_C8+I1+#d6KM4&^%9RB<|LNUajxhjS_1Xzq1 z`T1_n1Q%}jym@>BOMi_^H;qA4m>tT*FwhDUa6SF(S|_#$vfzHS>^Pst3yl_8D`QqD zce>%bJ&qB$-nOy}HTd{7R|XsIrgLp6+ViR6t>_9wI8n@|1gye`J>8%hy>fI?a`S^j}*{I8{7^(~=OwBZ_K@z2bx0?oj^2k;u~3_rOdOI~5e? znOcUakNbW=4HYS9kZjs@uBB+6oeI=fP3O+3gOX$VbT=Vn0W{mSsW=+*Fu#2DP~a!s z@*sY=>T>V7*sslZ6W8O6iv<=Z9ii0Ug$YL|miqFRADJgDSh``aICbzuX?hSZ(Z78kSXEtSwp?3ptLud|RcZJK2L%t7t=Fs1gn0 zdx({ySzX<|D)4-oDxccN62<*?$!{f<4I69rgLU@oZ!u)-!E1ePsfeyVGT^ugIM2)z z^Q`as58tN$-8RwP=+;Cg9&rY29pD}Sz!P|6a72{#j2L14 ziK{^rPij!-ny(UrotH|p68Z{I*Ys0emzvS-IVHkL-=WZ+wtc}?sZaoWw)yn^hhgTH z=Q_lQKGQD960PUSB_kL{pS64_!TBgs=sN%rt$?vx!_5JkwwUl}UNI>#U zyOy!spkWe3-m_MlX8`*Yr!(8W%3OM@*=x#+l-!!XX83F3Ezc)8!KQ7K+gaKlb|^uO z-sxB(0bVI8{3lgyp+RAO{o+vbC4tdOW@UCFm!#*;K>#?o3Ja}J`TNnaFf)bEW!gS6 zd^8e(c!TSB^9JVj*l1fBfJ{2BcF$7f1c1Uxr0JZK9jcNh0iD7@>K-f=FbAxQG=+~5 zBe-fl{7Dk(^5TVxiHQx^2sB=M9Dsx7c}Mx)ix1i2ASwC%2whSd=EUe?*b<9GGjvLn z?D1fCiZTdQE~o@`lfBeLC;*OcNy44}5WO%j*rC2)$Ar8XNwnU4bPJd+&pjSFMZ3q$ z0NRo5YX_BeHarU&Y{R3q#>YM?_|DGww6lGXh6%U$N=hBEQ7kL7W?gt&$G%k5gq zKJkl&?C{iN9Lwff`)}TD$Al~0UgIA-{?cvnjSb%r1LFc{<@lDjH4c@9IE8x+(-M|i zgY8a$-z9Vbe#7CG@Qb2^yac#{-sTs>_6N6SPbokbmW#6Fed~Xyi4NiygSGJp1~>** z8Fu?u%8T(+$4-^>Vm6v8lDh15bm*;W^e3FYF&bUVXpM*K9ATX;Ns+Tb$5fmp!^&uk z2PV-PXPg}UG9oc$&nC~88lfcfiBmq{(GZ(V(a)pC; ze*$>oq@glt*nVTAMjRr1@_R-#@qsnbW064vTA2aE#Xw?bq&)P9LVsg}VlsYd!+vWg zSx5nx`KYF}vP*|GXGJ^(ayvj2{?~|sUW)?IL!76#oevvh?Pp^jt-`WsjxPhNk8KL9 zu`isb5hJ>5U(tB{V5kG*xI}uciri4gL5lwLl!iT9U*1$nsY}>J@3h&caOY)1wzYi0 z8_-9Ow=vkE2eCY}RU|+TDAO^jyI7wLGxN7J9kp#+_lO4K4#*|LhB7pCT?h@ zKyUqQ(EJ%=0{2%w`aDW>#t1}R`H!03r!rZ~JPt_<_&oa)=az2KLp423&4G;adKW=? zvo1OpkCdM#6o#7ppi!zogZjDNKSj(G$q?l{m(-cBriBR2s`}*a^W5B#aazYL2&cs! zb83xMPp{#$V3(HwcIH#0)h&CYR1!LX*50}*L*ZfET}}dLf<0vkYrQUCVUprHbpio> zblmb|M&bsDwF@D_rkTZ*-rz@prv+1)uWX&tD@ZAf73e+QGB~i6sesj57bb6X8iiS zxYJDe!{M^5eygI?>|p=JA8fSK*-%JUblZ4ecXR0E7Fd#eQFk!p@0x1_yJqt2q)mNb zDI_eZ-&Fh89Nc{-f0E+hsXcQ^Uc#<(B|EJ|Nh@d6amUS@IRfJ|wwhz91+(JtujfT9 zII!b)#b@Q4=x^94ADnqR>xUoSJ2IcpU>M1uxI!T-)Zw=}v{0~;H*h?%v4;cf;xb=u zt+Tbp2B&b2I2}n?-GQo}BW-ewpOJ+h&_R!9&pyJ9M2e*L1()*!wp+3X;yF2>+dm|T z;tm9P-?Y5o5yHwGVag{6R5!rEl(yK<+cKKLpbLLw~sgT zsL4V0Atr63KX*OD(e}sHArF$bv?IiS83_DzJVpsp^5Q0POum=12y#11wCK$Nzyru! zw9)Bvfx2_c7=8)5NiN88KrKocw`9yF!k@u%RmXaOk4j}qN-1uY@sK`?9l>t;b^@bn zU0BsQv;YHd@94(U>63rBl67$|F(T2ZMFD7}MvbmIOMuz?1xI!W?xU?g4ov>~l<#4e zJ~L7-i$vchu`0yUx&2MG|3yxndkLK+2b4ur3U1uSGed^3={mlCU*yAw!1$seG)sWb zX(gxxdoxC#ha?Gn&6hJ!t5OKl`RCb#$ykgXRtZAxQ@VxM|dg~)SIfnq9CJDr}|omfKeM=%)D)*?j`=4jyo zDhd?tGa+7ko1LfdVERQpT|yNl2zB|%_BUK(;ZNOBp_P+k1u=r{tpv26VD=9UqFXa% zNt+zOXuJ#I?Y@D%}5{fn#H9<^2KXUxMrfb^t zkNZ14DupQ8(hsJc2G=1jzP=RU+rM*Yw^PVk&~MYHznt?O6}G`aBggtp;=g*VA6<^N zn5zRq*}HOTJZE*rv{Utfd)vI>Gg+SKgZ16;9}v)k1_J85-pTbqlmjMnu`?a6BtN*v zc+8p5fyUbw_32Fgn<4(OvB#}Ibk^Vu)=hsZy*Hq6C^BedQkxVMG?C`i_Qow+KP2b{ zLtn}Fa&ol8i1D!ujl7X8Da5nXHGciMG4?w`Q;32A03;fGh%9hF1n69==?(6y>GkuP zSaRewHHZ3o=#YT&?29Mye}I=-4n|Ekw~T8yh=e~DGy9E97NM6=Cp{=cMp~*(Ntm*n zNhyy5J&N_iG6=;JisVisa)Wbt{ZU))3iery?rnNse?jnmL#ks6ABE02Nrsk|!Ok$v zbX}9?xv&yY$1QF7Jw7Z%aA)Nw<4UjF54xVC8C=*Uu;AqB14Q6|gC`AdWy<^Je#Tcd zruU&hLr{bh-juiekL z2;wD-Aq@4V$<}+qYpcZR1v*s@Eiu0JdsI}dA6{*`d|&ItpXzjV;QYR{NdQLqzuXLr z2!d4jafHRge*o|QE%Cs)65S_8BSZ5XU8B%{GPoL_HMh|m#@ z+-zli=kfoL^)#Yz{lBQBRQOrMf8Zu`%rzB$`o9vi|J8?9D*WXCBA!hT7*+&Istu>3 z{H52kJkgntp5e!$wEJbTkpR$|WR@!!&Bc`pbB|ga`-=uyjKUGA5G0#zUL#AfKXEkBDvY_yKSVEqT&H7Nt?>NY%no33=``P5QyZTbQ%$Y#|5a6LRu z_%vPDLcdTKElYs)|H0Z{M@9Lz?V~s<-Hp;E$WS6BISkzlFm$)H#GoLdgObuIIrPvC ziV}VRDG6aHQBaxzRJvi`gP-SppY{Fy*4k^Yz5n13X70J}^Xlt7&f`2TGXeS_`?w7| zU$HXU8i80mW2PP$S+lO@sY-uZRk~>s)%Ob1NkqfxCKmAL{@V!cROpfcq7{tKwE3Mk zglg zT5Jpqyg4xMmG*ObQklLbvyuFZBAMwI(T1nhVd-NzfR%8gnd>xCaPpa7D4pgH=~E z7L^D!3y1k}albF6=gkG7U9n@%Ry%@ZvmgI?+ub5<79|Z}Llzp?o{rpzKCSKPQFTN0 zydyvUpc>FmWyreWJl}9^*$3Kdqp_}912W&Hsi~%k_s|`vrV$II7Mw78;)Z}wDDTIo z9@El48hM?pYVHlRs(u`KGW%`Py+eOxg|t}_U1uO=AL6`!%-2qQ2C(CVmFfJelR(5g z<^Gd2=o{}%B_Ygznh1ps&{5=C zN3NAc_$Y}?aA_+7qY9Ye>A(#CrcH-Vce0{QbxsTX9)&_f`K)%}50!@6e(|3O9Zd>N zON5sT_C1$XEROP{0G$+OQPP$E4b)U zTw`smUmNI{-ePJXX21}KqJHn^>(9tT*0dUBx|(YDijQS8nRyXh4}woVJ}E0~zhlU- zjLvteyEBmefp@5zs0gm-}bxL-dpcjvMU0-=DWD{ zo@Sr51Z(`Yp+;V9`zaZZ!e)5`YC|A$geYr(_?{tDA1gv@{H&|Rmq38y#bBC6lW@8o zRUpe)m#4~#OgCb14cW#i@Qljpg_Bh4?}H#Y%D2B#{ys)S&R zT#}C!?smU3IYr^!>r)<+9ZkxgQpiExRdB#9gl1ow%CcUKqoiqwiK6(cOTG^tk6*U& zMNH}4teK>fwpig>95mgJp4R4lPJM&!`}&uM4mNLjxPUW?!ET$sV0vp!ERmbxeq5$a zc%U8n5cJs%FNq_xa@oXRP(H^Ck6`5_k`hQfL>xZSH=DdKfyk;fO$%`Kr`Dk=X^!8a&FdcwTpRpfP&G<<-|tl1HdrleBiB|Ggx{iq6*{kffB!m;`P<#d&V`bx+581m8T;Mq<-jrvt`bh@$_!cGk~rf zY>ZpQpZ8lrE!4z=j1oTRu3eN?_}f526M?LV6K%a3AY2->hZs$&jv}IZ75#=LkblOd zC+bNiyGKr5m6Xuzxm- zo2BJApeH{ZHK^=q-FNN;vGdW;$<{IzMmo5`CzA?a;x)UAJ9_IC0$8nk*?V})7>3)9 z*v&u6rpJ4h^CZUOrLeo(UZpt^q1$)w3hKAY^-AXgI|?g~+NW!}`-s}gWJC6++A=s_ zE}|0LGG7AAx~-C9wJZ?K;6(L|1sxzNw29pys)W7Ey2dXzGZ@-riO6><>A(mO{v1HJ zi$DgHyfV7AqXHZE# zE&tC+#6AWYL*)uLfXaz)MYqdsg8FWSv4oMku>+?x-5xPRDFPz~KWGVZD*5#OsyCt$ z7IMb}H)e1)O$qdBc#Ii#BYy{ko5n)%8N#B1*L)|X+q#-ZE>MFNMF?48efDt;xvNUb z)6Y9UCIqu#THCAch4k$C01!VJ(!o)d@#jD90kt6!`<<2w--lc zS?Djsos^1DGo6rkF`Zpt?xB!DofhFWq{IpDV%Chz#%J^1--K`E>3y{K8tI?RHyWBwr0*29`TCJ<;%K@dq|9jkY_m$ zHQt{;>o;6$%V}#)c#8+4fW9<#hba_vYy8ImbU-R?*<}*jC;WHaSAh9O3)zms z)l1XB<}ER5b-rtnouJZ7mi`lYtm2?oGDU^)5hBt8psj5z-Hft| zV2fEMsu<^mn^~C%bUt7#Uddx_s5t^ny%`!6wJaR|PGXf>^i22dopG+TlpvJkJd?S~ z%F8|$*Vbx`{$j5|Ey(!DJPA?3>_1RihoP2g^%dmFz*HHG%U5nbU+Xu>GTC>CaV6C; zD3;MR@6E4Gs(6;Gbo!gKj zFY=B_$Kp6d%{^~$k>+5zfmB$RK=j+UDorSbefkIQ+t&f>x?V?5Iok}=IHZ?S$CLkj zgk6hFUm;tHMqiEM{vRJ&n||NiTA{o?@Oy|{pB;_G2(F{{wv5<+>1G)D{szOR^rpVv z3V4-0S@H%uB5*QQvH$;n6DbF}uWPZPP|?eqPF)mx=6+6A|F*6v%Z#69=H9iNh2s&? z8{#w;|7$=dS^9fbiX`4{Me;PKuw7#7ezS7IJFa;^wn;2>#z{?uB|II~=(U=IqvH3Y zeRP8J>kRyx4~Ki$9L0XyCTT=(CRY?Tnhq?}0v-=ymX!6#N!-4OPTpWJN_qDvxAXC- zy-N;~T1Bvw5^Znwfz5e4;0))2?4K1AdGfz4A<^{@Xu>CF{WdshARx~!u^KU^G2ua^ z?}Yz#E!5mQ;Op`$`W#xAWWMn0w|);GMvropl_U4lNQAxBex{LSqYY%t=DxH3kS3cH z!{0pv^uMq*?!q@Ub&9&H+IPIC3-yQ+jei>y zP;~+0ge*E84fOnrUZvLmE<5Nlt!q`U7@hvZ;CkCAekUbFhbKD%9N65?jAnoV0mSB& zhH)YUDE)*$T2$NgDfw#Y?}wOW>pW+ykA-9VoqGh57Qwhw#$%`xPycPR?9GE$S@cQ{ z=bkMOK^X$5Br(Gp02$SI=9NAinMsW;#{Z_~LgeOm z|K32Z>XdIQMH7pp*?!}HOvub4FuFrZ9iPDBg2Z?^z6P_S@?N&Ii(DQm;99!YrI3xp zDqoa7Eu@tzch!V!?XR?2h}^xeVKHyjcw28b6xh)r?K&(g~Hu+ zwD`D_Nf2Ef0Vg+t(kgsb_@Hu{+$3z1RJqIe(dKg}Qyn055R7(Ba(Xk4p`o$=$ocx% z?_F~LX~6ZE_p}v(2>z)LVq7JS-#L3=_J>v0e82CQC6Hbe{e$#Hx@F5~8y9YKk0Q#@ zWJd;%HDSfiN(2w?+JCf4e)}Za;orq#xGod9dchlquktBlpzSKST!L*&I3gXkf)V($ zVU$u!v*FQt++w++H^MwZ4>P2699RZZf`wV6a_1NcTo}hfN30aOIL)S~nLcu41;p3NhAc*x4o`*y- z65!H(hD@~zV4pr@abx~VnrguQ`}fBXw}uWo>_Dlzg02CXnN5x%o!GK|&?}Sxx_AaD z2V~b}T|`O)Wfk*mG66yz0JV++4lGCNdQ(sS7R_4m|6BC`E4%)0e*AwLqBV8IkG0F| z227eA#V&5PP>20@CJf~VP{aO@I_^7$^j){t?}EYq|LwJ9(E7d*6QN)fa9V^d=NYLu zP+e@7%=YTPw$#P&1**;Sn>``IeklT70vb-L|1{kywU9xE49U>=>$;J6kpJn9_v|rL zYUB`X=h~kA@29ujs`3obRqwGUx#xuFhuDsmcorTolwe|)=w|sR6%Z`NDo*0e;bY&g zPSkE^?7w!W#j~}H8Qko-xmv2MnJci6nM^}c^fnW}<=*WT0^sQu5;qngb&uY+uWEf{ zt{6%GDr?o(-g05?hiwTh@O_;TbOUR#>6ctJJh<%2LjWlEIyu=@`qTFFto$yKvi^b2 zISM-{*aiO@1-!K+7FmOPH}OP_ffsdq`T8})f5#Jl+n&Td)W;Hiydh~`v5h%o$%~zr8#*gqYTe8sgRV)#HE#IT%jPo;&K;2Qm=sONdf^L+|f)|>`@+YRK!>-B5J z|6)s#tj^BPl6}8LgaIa0?N^MB4Xkk1n#o1-!`Qo-j-0Ke!3oVBFoi&+gBZy_I&u&d zq3gjCi=3)AfV6Ane6)OlbSvXjHD+z}=k$OS>ikU~eZTUP&R$}|%=>*A z*+mh(wh)MQLR@yC)1J|}e{rYk==&knoxLX0wbfN~Vxa`&{BmZNJU4;s?ZZrb*krbk z*^Ho|<-2zq`7t=|x8)KY2H!RaETW881}Tch8LF19yPD9A`Hd{%cgRu*r#gkYswpHqm(rx91! z4iHxOYHEkK^Rvta^t}YA<$S*dq%-_`@S2rcDb_W4TE0b(O36{T1b&Q58+kCi@GVCx z%xHUz@+vNH@Y5f*i6b&8!RJ$lFBvMvb+{y|JA)t7#ul*pS6f%kaz%Vigc!9XgE!ff zfj8nDLziY0ACQLmYt68BzDO>Vy%jA}S8X!Tp-R@4<^pmX9)(~I2wize4WP%$j#8ZhM`7Rbr{ELN zqjX8S_M|Z?ekAba8{2lXLTqe1WQ#msQyQ#zz2p2iMa$9*>IMrv!G{u{Dpm8^!)3I5 zv*VLFf2zrn?n;OKhJaR!k5v|!u0u-1pfS4-WH|7j@_t?oxt8r`+JdfIdoa@Zg_jfB z(JW0r$R@@OJ8J(i0zi?;Xz~qTKG?{gh||6F$%DS59f9kxM8sKmdjGkAi-;uBdcQhxQ|rOD1-IgUrNLNIQcMHLG#$_)zb+m;UbBCsiN z4`teWJ-?{X4~+#;{`c}qsoI|04^dqx^F5n`7kAp;?jD1Ha;~_RXSa z0jT7dhy0em?cPbsK5W{Kx#w$I{zUK14R`Zd`DwA`0&_{eacwyUl62J_M9@8@Vgexe zD%J@z$$<-n7pXcX(SGOY+-06Pg4;EENL{5U6dZG0LRrxjPI3K1ubf!$o^SyoCcq-8 z0^KxCK>8-2qtwQymCm3gwMO=W=l3PYXy&}eOct|WcztW$e#9^EQzlR_?`zqzid=Jk zV+T!KL2YJu+Z}+2Ng6mG%Yc6GONXv!UM#a41I@M-{&o#2#}6@#CIrQ$ji20TXOfqB z9_0F%?kbhzaxA5ox$XG^N0u8M;{6WybS+a{}pa-o_|^ zAZ+y+2uh<7Kd)xzxAP=s9F>7ROC?8JTBm@bFCmbn(L~nzvl_fjV<$s6d2&$OyAiCC4J?RoGa+mCldIsGaZZ})edrq)0f^% z9;EYjc6x(WE$13vON*bb(L2GDm$)YB(J8yBF1MS_F4vBLp zMTQ=KvAH_#3%mFdxuZrCaje%9|3K%q(T+)gs1ca}!k}g}(4er3x-1gF!DNTg{Vn2$ zrH1WK`n4lkZ$hjP*@1()yuz$EN`ctFZ-0VhJ4a~xgGsHrtCwsY(xj@;_x2&^R}af! z=~~M(QWL-gp{!UdlG;g~$Z4%$yOw9L1ttSPe7xG!3k4kygvH9_r8;YFJ#6}&wF+$W zUkkbwttgZbjGgK+2^%C3Pv22KAm^0p+++A)w5u5UCt%n_#PU<>!Uof1pPYiP5t*m)iRL6i z4DO{!v;%TS0ytk=HnPfXanoLD#{FT2xs^yfiz+vhUL88(LYOyh^L%fkj4jsP&^EEn z>P-M&zRfHJ(s1?U3tsH#t;_x42vm~{O%4u?5i{KThe1VrI<$=6x<^bFop3F)VY`W( z5>bJIxd(T-!uHg9YNc)d7G(eFHS5hCRN`5s1h0 zAx^0*7}7rINF7eEncSp8x6FW7OQah}=MHh3g_EhR)Nm)a`jr)1hFXd$k4JkjS3(Tv z0>OMM_|Qpq0$5k_({GXr*HF&Fa+S~@DdoI1hxjm8P4ri1uK;1T3UyJZbwaEP3CqEr9_c@)_6~cOVD@8*3&v-~5Tb#`HffMe_Q8EG)gyV~pC=v7iW z&p&TI0U!V7Y&1M{`Cp%@K)dlaA!Y=6KKDHTa=R+c;HQeg)i5wCB<61a1vRP)K8dVR zUdbl(;xespNqx?@&rQvD#+2osXMVeRwUm+{`tUs-&EIF?N^wfj)0ED#`Jb)1@mgLn z?S&CRE^Hb}&Xmrw!n8=>Q2QB0owsc4F%WM5 zCi5O8jJjeQ^*US9>j7Qrl~yfA{)I@kp&OZbsH>E&hKm{H3)^RZB92`c$f_2vh|UC; zyY=Ml0-<5jcPRjJEVf`%Fj~5l2opvohuKd7I;K-6eLyC@+unQd@sGFXW~H~(#_3C9 zlroq}*T#P*+ItGt6|Fq5qca=~_fL~f7r=taL?fHZVCU3-kb zp-Y)r2fJKtBWFbvVf#fC;KE&M(Od9kLWq%7Va9UEix0IF5A%?qTcP*a9yX59wcKru z^&~*Fk%kQcrU(q*4!f^^r5AcSai#fruN#=)DSDv~+>?F&%V9OdORI_9jZR|VOas(< zZ=remG9zHg2eU z3t{emVOAQ7Gf8U*-OvQpa}RoGJ!6q3tK}iYr2m%#-^Yx|vIWHT=m|!U>%YBDy*W^4 z3jgIe8%y=rJh7~8Q@|1Tit2p9amm8{zwUlK9kH8IO~0g)D70SP&Ab0J;<`O9m2v^` zdud-sB$|`}YFP4y_!sV+=6}Hg{50tJrTzAh0vwRl+-MdN59SGH^eG=Dx|;XDDr`hl zu`1V;x`+qeyv4}wIWbVauV3b04;v~8KNXA|Q5%-=c$xnjf%F2KE&LC9Q3a$A$1joI z_Dm_sUKG_D@RDw;eH*WIxkSAAn5kE`0O9M}Z3BtB>qw1)S!GKb9_gId&#Q z@(aH{mfmU>eVo%m{|yiFJ?Os%x9Nhz+(*{r|3_$w!GfuZijk4kCVnHbVBEqsC$^h0e|m1 zMN7040AvWd{^nO9`!>}0>(38QW)tTCmP+ZcuM{&|qatT}f+KBno!bZA&#n&JFSj3o z-*<)uT%1|xTpXW)D-R{jIst5Vlk>Y3-CGeQ$qoB6rL)C&;lK{Y<7|OL_`s-0<7L(V z{{Hpc==tw_xXprKEqD)`xCdkul1?82-yzpw+&wy^Qr+bZAPu&=Qr6v%u$Ii1j>69aPpSPj0xFk79x*rEHF z1du!u7XdJV^v;m$l_at_FH#fhy06;i-3Y}#^Bjkd;H3N--TK^C5@WW2y#WYk#$cZl z$OU@!hU+{t>>3U<9gp8JyvAQL;aYYtu~6V7K4=tuKRK2&Bnwn|W6ylPuiulQBBC5&ubSLi)?@irO2=9;>BU9x(( z^_XUMQn$SZ|GHTKyN)+5P#gMd{ya``L2a7EVXdFWrpw%m*><~i;49W^Mx+9 zkhxzeNoy4ebldam4NF0j_$v>=OsqM_9QkKV9Du(YQ~WhAvea`w|B=qn+gqYRTtEY* zgEVQ$&2Ss`!$XRHe{|>- zb(MB@^gGNapuRTQ-s}m0PIr+fEl0}Dt=TR<`cAHRG!yWu^ZsC@%g$m;6p}H`Pqll& zsA;?J(zv_Dz4T=4mu?PYtGAmsH`az018CUJAb+g@$Ep zL!r)%f0WMGT0(BmaE}LVd#0MX{N_=GlC;RhREudq8}-m^=TB|*)YF{Zrg6pZUdN*R zk^r%bmhbAvlC4i4-&uIK83m#_fwcJBFeHC?%Vp%J&b(I))^ej&!$oMej^6#_zvmj% zJq9dBIs$i5-eGZXmWw23@-p&>8-rCD_}T;W8H=w@?}1RlVCr6*ShN7%!58IRZKw^G zg(4JS<%Q*D1g0@o+#jt?Hna}J4!D%`>q?v-pW&_prmY3NF+Q zNc)eUb5TfhU)w87(nCMU-L{~w)h2q|4WPsFubJx=rF0Yfstr;Q!6__i%aN-{1$E^^ zeQh_KldplBnLXBNbmOj=k$R_WaK-I z{lBsko?62&-L)O%o8D20g4dJN5}>M&IK4(DqR`xS;3_hNC7^uphnHNP@zm_};zfAOIq0exqaqRmCaSFea zb)F3EDD$7CC z8}j2?P)jrl)A=-3PnFKd{_nKnCc+fZ&C3H!0r@7+-&>hR=RVC=blSo!R zu6=ih#P#_p*DvJq3j8-=_Tfot+%b63b#I9iY)f&oF{C*$)xsgK|h>X%01}D6MSGI(BH6%QZDZ91BJw%Kd+WKCp4PEG9 zdnq^^CiAcxpfU&JO*Bpe9LwVQWS3B*13Y-4u5clybMiMy^o5%`w7h1wt16R40d`P5 zZ5_gGVq^EW(FBB0gXJL@ne~c%ZaPbD72U?_XzD=u8;9u7dn2|FD#q>p^zokj=n$lb zHQsyhIeqpZRw#LJFV@h3x@N%CC-l;}i@pwqd$36b36bJXk^QB?Z~~?#(&07U>AVo{ zaNM9`3QC|uM@x4jPC+3DxZ?LuU}VH7O0;lHFirv`_6$&mqk%9F-aXz~x;63P?StX) zz9Vv;?t-8D7j_Uqw!Gdl9xGGms6)({$YtqKCVzd*LY*o8`?CA-!QQWJsbMV=uYHDfhIrnl?DcBiVO1$r8>ucD9Qmu`FE-eprh-%7d2M& zzXbM>S-k}Aq)B`KCSO+EzY)Hy~GOXB_v3~?q$)(o-T^77|$d0>f z#T^kG*wYwEqHK?iY;(3EbjZ0kfGaW~kds#fACXlwperXx_Ca(uXT-seDdITn>`6ZH z<*!zszJz0%M%^JFM1?&4(4h*w$x(7zdqiPPTz>Vt%QsYLfF}d&FR;>YVP^-DE26oq zU&J*8#xxja7s8@HbnFvolO0Uk1P+etwevAZ;XTz#%|-cTVI`|FL~mOELy4sks;JYW z8HZ@@;!ZZ}{i7zV3u`PzNhI|VddG@KCodmKyyTU)m-{%OUIxO8K(D8wYC+7_{1wfoaPb+Qa-|Y?A z^fF=O-sd=Y$2;JP3)5&ehmU?7XWQK%u_Zg-S_8iyFovdNQlh&I^B<|FdX3{5)JD6+ zT`JbVgW@VVEa-aKa1I2_j~Hg>U1;DVB0X$$u$K84c~i`th-1Jg0fj=Oyxp5$E6nI2 zpW}5odxNZ(@ecusTxpi`L-NOWN=~YUFlmMzJvO9g8Qa}E_riHKF4EOEYJ2-Hy~A`Q zcvMgHdOzUa^&-cJ>%?o;zeP&SdBmRkLM=WeDah_`p8p14{T9`v3%khH178e->yA1v z`ocJWUr7cYMdpVN^4__cD7$hA;)*54^iYBmbR&!xC+^*2F?6X!c3}0Q(SL>-_9rrU zKrW+qON!2aN;}YNZ16=M!>v~?YTL>GGgsZ}O8(4KhP_N?d1;QW0Ji>6<{u9deZ;%` zWDN?x)BAI=4c^&c?BrND6YfRF*?22Lc{x$bsv##Ejb{mY z?W!g!?c0JeL0Y;GfRET@Prm{&E!e^Ebeg1X@TO-MK?DVahFVMNw0dS86s>z)85hf)g zFjq$L{YQtrjxx;UFrwiBU!KxhiVbPoz5&F+6x|@hD9O|`B1~=OCvNYF#@{|S!gg~=DqbH1B}h9l|KYeK zsSo`6`B(C`7nKU7ogG%f7B53J3H;`1tRr|AkglLZQw zxP>d8o<@kN<)WIfiBLVqbIE8433eieq}5xCpliPaw*s=Io7*R$*L{H07-JYsRc3z3 z`QCjt-sS959ozD`vi!@5wcfc&mA{S`Bu-`8gcGIg{0q1Bckp1hx9!>SpRfmlX`!cJ zu=YJQ?fr?cQ?*~HD}{xt(>>o-oNJg)fDBh0pAfTMbleJ{TXV~42KiUuP~ExCE3y!+ zZWJFLyrsbT;^^8tr30K3fYo-Xl*JKRCm7swuzz&~43e6Zwv%lzRQO`SV=3e6k6!)= z0E_?&HBiSc&qAcyD?r5bY8q{@Z*k?xPJemiU;Wo# zG`$<>nWE(d6U_h5^JHaj+M=~Y@miuf({7y)sD$!|_8o)sFH=rB!568+;0vHyf8}BM zOKR^h!EluLRb-%FAN}WbNhmfCDqnm|pW)#71khw5`X_W3AlKUm8LQq`@w)2&YD{sL z(Md@5SPMLQr!8~8Q8Iaw5VNJ0JMziLUN~XCqIFJ5=LoFx-3PN}Rj@4`ZkLoVkTjY% zzFD(0rIedDQ}RCEuzyu%Aemwo6VjG-)xE6fD-LM9rq=@c0fpDDUYF*E`&~c7)TU6t zz5LGf(G53kMDKim!scRkB-iG0(Bf2%w;o~6sJ_D-MMRCCq$z}A@p+ynLHQJs#igdM zG0iYHV1*ZSDoBg&yf_(k-JQtSVOf#{UnN}aZ)+Y+RA_)HwR{G^Ujsv9ce2P~^3(zgzGtOAl}RwAB|Mdh@$-bbANi#AfMfiHNB3f?$gV40XWb%M7;JVo~pUdtp_ zNMZ)55Bx$;D#MPiH~g0eXT3Zu)@1^dXlDt%KEq2aH|x(1gaGFo)QE)GLe<$bX@g@r|Yuc0vV0ffD>$Hj=363?$rY zU!#!8yB}(hNQvH!(mSL`#=Mt7G&rQfj~R2eh=Gi%>mh_A@cC23F<-A`wyL@lY09jd zp=n83DT}c}0c28Iu$y$e1o4$#2#88_EG2w7yMcbl6z5}1gJ_d?{%&x(dn5#ygL9Y~ zM^qug+#gtG0JVULZ9eehJ5rT+yI@bUZ!r?3$=5g_O^24I_<)bwyv(~wfe&4~v)IRs zK6w6@<4Nyih|o+~Y>mv-eT|HNN<=kN9DX8X(LRc6M7lX0h?@o$n4*Do6OkSg+(d<@ z`X6%gDMP3K6}*aV;Df*eQ7J3DWJGQ$WuhgOJ7y$GF8A=)_x9_ekZr-_px;W}`E-<~ zg5u;}+8UyCGk6dywtwqBSc+@3?h?X@U^M0K)m)F4Sp*moZejhOehl1^GAF3jBCJ@% zHxi{)5SOtoygW~}y1i)gU%!R_7;8GO9*{zb4X1uIUW(N}d9f7J3-#9<@qs(nDmDIg znsHg90<+-yjtt?r&9h|f>X)fpq6|aEgg*UJ#D$0ai_sCbbx1)xcV#btdmS$o3PWqM zv$$0J70U2l&wMHE_A7Ys6t`K2ob)zY#ECs!p7Qx&Q_q~=1|L`6rOlT83*&kcM4QC* zEX6T>sY6`xhHY&B{1reYDW|Xk-u4B-Rg#qO^$9+?{uqEDFF%q6n4IDAwu3{{!V|_D za0B;Tjmn>GF`ZgJjrOoLB-oF*&f~tKtz z{U&bndPCH`u!Vq}pnUPF^oDey`wWd-C~4+vLS?Y~BjpV%^vCQI!MxgWAm91eQL~L!BA9IqAm#pi+j$6=Zi|Ex4No39Xwf1% z*v60pA6El8lE%6*;s%m)&ip~oY#qNxPKKeZTutZ6{1x@&Chu^WH>FOl0SHB%wLc5? z*$OekX!50pC#0dYS@9E#U>XsEJpqWB%_UmQvd`7s*Ztw+9EcXZ;FrRsP|FM55n=s& zfBqsJw-s-T0#FehRDITY{c)JKo9DB$CJ9e&)VG=mjVE`-`piz*~qwnI?0EaZ)dhw?@`0$T5b|IPbMM*l&g!zN}2Az-KIq2Dy1!yK57Hv zuV>arme{GY#Sh?nJOE6^IZFuQHHP~2(2-ej@eFb%-|FLAm*PyqYwNZ4Q)Xi-Tz^|k z@2DRQ8F78wDaPzFW^xv;PE?We>p2OiysC(agiNaDHUp?%&D`2W%^JF6i?-b3 z(Yj8C42Mf2LOS!24oLuyrZRN1{9jco6Mq0Irm^5c`RPP%^$nP5nN|+v)Crrj%G{c# zpl_l*HChYb4}uC21@?XM|COQCSK{pU_9wOSvp#9C1w#s%`3*C2m_nFGq}ZOhA#FvY z`N=X)Ms{wL&0%~9kVbB?&x?@(N$&Bq^^ z9=MMTF#?YDG>*P1V{6Iwm=uX@Xf{b;C~#Fz_y=0F;`fq*r7ji!$h(2f8XBzwxRdZE z*}26?%_d7F@*RSX_8I9_x|R1 zNaRQIgdQY0w^ zZ*6Z>-AgdXChglMT=xnf7_i>x<;ol%1dH!C()ISGBc|A)>D{<3B{HvLttusbt8qBz zi;qmuM>zgS|GmkRI;mMnF-?btTI&bq!5TC)s#2j_J)c#?kDK%WfuYBVdD+&nVp60+ z#}r)`57=%a^D+mz@Zjtw(s9S!h{t#!Lp3g4D7<F6ifNe?6H+!ho!mRQPgyj%k`vyRNmdbCE&V=9 zD$M=fKQ&ST5pSbFewuuT5j2@sU7hWZSQbU!FG@zBkb_0TP%|>@Dtc|WRAnrZ`n{{f zSJ+!r*Vz6+cZUESC3@`9d&vhQj!<0^x*%>mBktEX+b>*z{Q*Zq83MD{SFCXr#ckfu zYsdlOBla^{@hTL(@pfXoZXUSJ+rt$r;-yi@^y7nQqAtc0Y3~KPcqX%6aH@%JiXrv7 zwm#q6d9~kBl$DN@rZWo#Wz823c;B;QhN!igSN3u1ryr58v@%pX+&;(f;KIB7aO>Qe zg<`ST(M;I&qF6}<%ZD`aES&6h z3)m@yfFoN_mWl!TF=n{q7tw3$+ifDT#m>r5_&;buj5cKWY@iHCs%=%V@n>Z&?h#na zuaC*6Htnsgq?uK4B23TC0J8lkni4K#aU6a}@m6{ub3E}W&e(Ew4#;c>IWx}B+;-Mo zcprQGP=Q*!5JnJzE)J!4=Oz>n$^dn;BH$82?5S(U0UMkIt{W^h?-k~TQeYk_eNG}B zhS2FTmi$UTD$|oFMpm2}D4pMpbi%d8-9;X%i29DB7W{2PD;!l>Vb&@A;T5qYU$Uxf ze?$szG(rPeVeR^(1F_SjJ>KoM0`F5j&B!kPpi3^|D_?Nm2)_BKKR?M``q@v?cG`<>7pw#3G%}@pWbxHn{Qcax9Jj@jMR5?DGFASWm@w zH$5>nvpkqO#j^Kz!_(ylROlRjYF~rhLcu#LOwyqOh75P4$#`uec4zi^HFiLNXp;1z zA0~yaDI9^3)6kqUwq1*It*|z^a|ztqhfb%641D!5sPedXg6I=o$Ibl167EK|`mEjX z0#+*YAJFAkD+8)%CCq{4T?|;b!`p}BX~{e&)Qr9oz_xz00Db^=yWpXQjlNhqG|DPZ z1q~+N?ep+WA||oUE?pp9hIrP?qHcYUMV96Ol|$O!Q(v!sksRVn98~jb&ZG}qS`Vwn zlg#YUV8=~6UO*P7e`}T+QPyI-VVe+9P{J^Ur63A61w(AzQ{&W^sI^v5zBE^(I z;3Ta-d~lHTA2J~EF(io~&D5?Ns&(!V;g^Y#rKtE+$@bI_YVm*qxRQ*h3;+Dq!n`9Z z%mBIyfw#kOwo0gm!!Z(MW>bF_JN{=dtRylyuL`CGYn@UlmTCx)(HJr-)r?SS(tl2( z4N8V_opbZ)y8~!Msn3CWVJP^~Kt=L*+qKn;`3BD1V5*I24z~ytV2FaOaF+fcGkclO>xJ%kpVmMV81!$G5hTL04?y7a|^>uG{ zCo1nM3Dy@$1>_#~KYJ}(D?Uuw>DKgy?-eJ?3#Ep}-IkPA-AkoxQjQ* zZsN+$_Nw=5<&z%O+@UI6E>tv)3gD`32>}a%gflUQyj(@X%F3d0al`P}vg|B(lqIGT zGlC9E&QGDdcC8L+flY&n6733p>gr~hk8G$Q@oBWBv84$#*?H#@Qq@KK@=nvSatupb zEKu%e;5SvTdLxfN;*a@xJsiTolyTt~66>R4xxM#8OND`zxm{rzh(-dm1mK?bCFNvO z9jCNwXs}BgSP*ouAD1qn&o*NFWN%}Wa7?)XOIceqp;Q-G8fO48V2;$#--O66fLHaO z7(9*^Fd6s>{^k-1V9mh$*R90+9~uEpY=IK=W6-}@ci6j_fcIU)4Y$?N6jQ)$P{12- zVv(EJ^Yp)t6t?;#|GJ69K&dZgTVYd@^4JGk7_j>(pBJCprVJ2b2DJQ0fCp|U|1I(% zPze6Ng#fo&nGyg;cmL0x*yo(Fe2NBG*qN~8Lu(Q7LaPH zq6)K19NoyDk*-YqRO2F^{@lsx*>8=q3pcVM>7Rl@3~N1|pcCtifFjnK&(UWJl|Q-1 z^ya5A$>OVR#vucMPl4mu+lB{UzyGZT(uyC?JsR)k7%Ah0uSFeEI&f8-_@s0;AQ)rj z&OkWfp948hx{KSqkXA9ZCSy)lzx0xC^8>;>hFSM6s(Rk&{Dz6~rgrV~{dc1EqH#!b zZPq*QD!%#Ejp;pwLapXwp1i_*=*|1KLV`)7Yx`yh9`m(4ir6pl#_P}@Ttt{-4tDpO zJq5S1B9aaEN(jxd&nP6wx3M}i1%X+wx(TqoCLRN~Z7thc=4C=_=i5qsKan;=@H`(D z+sW~;h~iC--6Kc`)CLN5N~mGHi`vxVPp3WO^wfE4one$rkZqmxeY|F!pgG?Gf;C19 z&N!GZpwXZSqc^0cg>kVL^{Bc&Oyl$En={&pH#2(Tuh8~om;fYW9)fFh=nbbrBaRi` zdEs3P{5nkupt^%QG{_9?5a#`&k95#_k>7cMqRk#YEKNS@7^|Jx7B7SX>0$m!Uh?KwM9dP6e}ykUSVm3@lE}Yif1R)n?tp zgLBI~a@|iuE#l4D?bF8=0D<0zpGMV=-ys8f0kN{>@T*3lg=#5cSCt-^FArm$O%zEb z#U8*E?MH}{RPe}_c3pnOrtir+CtFAL7(@1{%?K9+F2WGGms<6Ju~l_Imb1|d3ii`H}xBc73D7ZJzaNy`yQLL zMZiQ$pN$_PK$ArHD}o@cy!}p&8aTT7>$NgEnPoi2558`8l~v|yYI*}U0hN84cD8p8 z2kM4o9YB(2Z!1Hd=bKu>H5O=rUK04AWoZ2kN|Efl0js~{-I3cigf?6jYC}tD9Vsh* zhQ1AhIQ#EUFYeV-1usq}hMZIhFe*Bd1Q>_!_~e;>cL6yWz&}*yZ+@w3>#110OY-KT zN|uu+?@in!=JscF2m*QGex8kU`TY{52lN*9y+C!{^A(56-kRY6BsHu~B1jBo94b^= zYqnx(T*E+v(I%!eQjaz>(f$Nxxslh* zKkui7=YCL9exv=fyf{YO07R2mDSDo8Hz`YHWDQ=}E|URtFL7f8wHlyq6D00A8Bw zwDB-**9xBWkuj*;%LlpGMyi8A2BzJuTC8|2`>~|GIWgY)On`kL>! zAtkzc6fmM8GN=vNL?u;6eefq!^+v+ znIV*G!Kgb)zH0hM{5;FSKkEZ|Ti7faM%?%i#YN?|*peqRcYi;Le&_j2q$K$Ex+X{5 zW4;}dms{~6K?=J4KA^G8^8hkS(qodyT$8k2C*@s64Qc){|NbOl>wShHgiKsWv6vNY zH?~PubBM2M$E)!qn!FbBif3cqCZuZWCO*QmjihRNV=-1gfVpokv~scoG&y#B^Zj;b zt0BR}P)P1fX;V!2d)#xmq~seZ{cbNRQwRx>PEeuy+iVvqAm{Cl&1YM1UE20XOZH-8 z#JVE@HeP|^na6gCTAq6?#bX;4&v&L77Bx2)0Jy<$olY*QZN;H@vx?4Psgemlu4Uc` zYlHwzu@)p3)HWRbnrC{6kY{3kB2Y`nEU=uG!r1#+hBaB2CF=R@JdJ{OJ-6Ezbp-cs zrRUgv<#|W_R*O;wFU^)V_ux4WpD(*jgh2ayYq)+fQsTt&N50xP@ABQi*y5u@@IqU` z>{^Y|OyS4(=xPm;;tMU?$89Y2fNp1xRdEljpd5GNepk+FIR~o6gIH#R&*+b^Be<|l z#%!dPE#Ia)j54u)-E28y647@Zc<6P_4lHnS4;F5URoZpPQu}qT{S}g8w=g(^6ApQn z;B2N&8)}Cy4A1Scp7=5)mZ5uZXgRmim*(mV*AUbP!io*Pi)NzpH{4%-dcgZ* zD#gkX(XkmyF@b&#w7TBgB@19L`t$(4^^-y4T-|dNUeixKi&@aFo3g_{f_ctR6GJy< zcSdxU9A6v-Wg*)6up3k^JIOD{>j4Wp-%Y#_OBh`(`f!p|aZjll#$a1@Ayq%blI>+d zcE27ys!LBU?=YXj3s;%fYkiA+)`F)J{J3o`F7d@9oE4`{KR&Ac`aE4KW1r0D6}>gZ zLyU+X6PM$cu*T+4(vr^YD3W$P{x%q{NOtjBrRM?_)km9{Ff61QP~S+vha}hp=7!!_ z&U1V<$B3qzC6~00yw}WN=IBk+fuH^eL5-YAJJjO4gOD^-9)7njIsV`=BT!a2`D(@i z9{}^58@rXQ@njPUCqrVJ&hI$@1m)xU%{#hu(3Xt~bud9_OP%cRbq zRui%C1~R#C4olE+Z8CsG$lxnS5$jUucZ$o8Gv;=Pq&Z$hJcb5;_Iw~JRN^{^@Uy7g z;|LxlH0Oxwh!>Ex9j?a;Cq0Pr+qmM!Uda2m_>px2_7gUSH zZ$v-R{~*%(b6F<3yCk!4iDn0gjRlW$!}+zp(Gyy%n#C@G6bNH9eSv=O72h;#5@nUa z1SR}Lztz_A$c@?BZTR34K;!7-q6H&b&84AM%OGq?w}`BK!rPGSkXLj43zB;i^nT0c zoHTpEmmY~;JBtlJg;lGQ7l++19|w({eUFic%g!uBUw$!xP(g#lqf@myYQO|I!-fY5PBhlD&kK01u zIl&R^aL0DefM)9+nzfDij;|$rs%u%_W2lTrYOl|silEcQE#t|E{8rlC$3w23S%`x# zb1(OU$2{DDwn%#<8s?Xi1G9Mn+||mZ94mVAhqu|`x9g1unbS0Co7uul5g$a_KZIlb zI6`WA-0@MDF^FIlQ+TNYCc`+7o(|Ts1LbQs*ItL((N6VT+K2>M$uk@SEPV7qeK2U_ zPQ3i>`}`&W@;FO%qTbk&zvwe7xYWSi6sH>G#@*X2#+`!vmQAeeOO?h)#wM#8?${j~4pP2x;w^VlN8C8kqn2w+QZbKjVFDhO4mCw8iXj}HaQiV&BCR7aO;*D8JqF!;L zUFCv!YqoR)nQO->vB&hQQWwhKarmi69r=!=FaWUBATqt@_s3FL{HD`wI$qKkd#j#z zJ%<@0cJoEh4H~{``>pD^!5OEw>VP;FEZ9qGk^kbQgdJXW^7B3Y2?dZ}@n_Pu+hCeC zI153MI<|>$rS$wS*obCBC6&l60pv!)jSd!7$cpAET`zdBFyQv>i#q9iFkjq&qsZt@k)Kt?cK~I$flAONa1HWDk850op*=BLHZB^|!vN5g?nP6#?IwCWtHu}s#G>?0slmtRo=$?uL)4HAXgomXMo zaewdlTqxlKzP%_5b1)*xa$-OJ342N#RW6)~SRHRMZD}Nd3blEk5C*ioM{{K1KDE?t zg)Oi4WjeI+6emp1YL>|u_(r1){4Do9YKyszn-pa_^()I1ic7GR&sw0;jG0m4(eo& zVY(s_n|?dRgM@gF1xXB(CL{`){H>Uu+H;KMowSF!z8N->nQKRPSBj(;`mS<8N}$=Qy~mKQffR;Hr19{wK6_x+5k)Rwv@ejf1DKgHX_Z^`*tYWN~ zGz>WF+5l09J-*9n51z-FC%p!mn>R;PGG@QOf0CSP2M}f{c5n6py?kXDo>%1fO{$HU z`#t-7$*({mIk4>pqhOvKW1h}LcZoJ`M&CAiYde87mMbthi~Ak&oB&k9!o|~>_w{p? zCFJHtv7?FJNp2v>#5aO!e5DwMTlN~)s+i;SyWqr))%yM6ww}fB2<5ZAW4k@^oZHU& zg*Jq?T$e=w7L$gbbN6xIu8cKmOo_Cw*;-2*iwvgrv9Dp(;@XWR@O#omu_6_=jqDL8 zPY((ij_<#T3TFu{KN3K{4CzYq_~aqdjQNr{uAKk$ARG5PTG_{_5-;TOwFFMBe$mZL zeGjfZ?3zE*7HBRs{?P@;uT^TvN_|jaz(uEjdH)I)WrdLmA~T^ahNR=AA#3orvNEjK zJ4asWSk7L%($ENj!CWdf=V)*Qu&KJo;|9LYHoFuDuNsYh^1-ACFye-|dN(^?4QV=H zvAL#9MYJOg2o}I!F+q#ZIv~{{7SY^B=Un29Y8O3>a*-$lOssz@%2z_j&3QcF1oPf; zMfP8s1rTBRM>`?Hq;IYoBmri{t02UmsGm6icQIG^E2Cg8gz2wJLJYjapLoPI^M6Y% zQGoGn&7l!CZ(s<#;kB2T_W+ukj9vAk10F`7e8|#ZgNp)6GQhIGON|+~PwQ94&1+8s zF%rsOWrr6d!??^DTFf0`o;qm@{BOUwxumXr`%3v~1HKoA*P+@D(HJe}S~5`Mk^IMA z0sKq$J;rzm7}tP$^WQMawX3+yKNkrbz4k}Wx_UMIe+?L>+QL}>fKTvh%pF+%&qs0D4AmQpa8yXGfU9-LaY)4q16vUfH<#cCC( zg#ha1wLd}?0?5URTuR>!Ay1ojFS&QQuHV*1R)LmRBAJb`J^mHl_7({UId(r?*6Yr* zCS}D&P_vJ7ZvGwg_m~t3t|2~zolQws)VLp=sI;%Asv?#0S|2RAX4%%5^|h^2`AKVT zmC0ba<#1h8NV%2ti8`%}hm>-+JI9p)tr#>R%@5!2F7rPQYT}aeSo+oV{%efalP$>^ z#g~Zpjn(z0B8gF4*!uyO-p`v2!i3?->6(hOH;f^QpbZPI2Je0LC)Je?+J~?uy)8UR z#&86T>MAId%Cid}bkie*uk<^O4{A6j9dB@4L%I=OMrIp%2~$9MCc)eqd?~~azoa$N zAVdyD%~V$~%(T?$tq^51Ofw_VugN};jb$#XEflS>$enkhZR8e`l-};c%F3<5HU)(w zjbpWfQo0Uq7JOl-Z>`7|IFfb&%uO6P4WbX3%5=SE{Lrm;XWdUr&KI7Ctr!=L0Zs_l z^Kcu?=w~~m0In`Y%dq+QVW6FOf{AIp1$KjHfW@}L{HwZ);%sKa~Q=li+DJ8`)%KCM6;Ka$E6`XhGUd%GTWr0`O+JWlj zeM9<@1F(o!?nL03E?F`j2zoSL_k}{UoVLGM&pMMV)U8mXm)YB|v&ugxt7B*6M#+&q&Tg2})yqx7obD@zlbl zeQ0`Eg2!vu9vUHHublJ*fnhk>^ypqZ}{u} z9&m88Akdl+9;IW4$N0Iin#x+7(!KsrsC;$_h-#OL7BSA5j!xURxitY>t_dMJ+Hbzs zds@}-I7WlH%cnvsaKi))wo32n{t?Zxd)tW5Oy;W$yhZp#3P@R+5D6!XGnIATD+9oC z`R12h31lj_8hiDaWu3yl_z_~H)*4p8$7M3pzUJLv19=S)5Z=F{LX|t&hUGLoeYRR* z1(q>ce)tohXLuZZ{&r$mJwI$xDd4^E5vi5~BkVO7hL|<~KmMumyhOH0Zxc zW!VG;WQabhWekNLlZE% z!=Bk@_7AZJvw)ir7HOQcNcj`xI@tO&C5(sv6AY`_E*dxFvJcW0ss$9Gr>=}rN z(AEjEOKhliXA^Zu-Xw>CTEv+Jn21h?YfYR3<;ibiqEsWecGs*tZ3P4*woL8!I-D)q zlBWY&=A#Yk!>0j1;-egY&|n<1nBLZbNzoGi)WFxNQqU|h^Hz$HFOs4sV)`~vcElu1 z^nt8$gdu|f96$?nAJo2m4eOJKzxYrL;J|nIC}=FOuS_5>aqnr@ZJXMU zmszdDUyY3sEVZa6QZ<_SwCZg4d+*m z%~4^lwq@HY{GcHWSLlxzyy11wl5Wzbm5D3412?+*P56{CUmiN2w+S8sl~^2pqS89` zl783<<|(m44uHqlc@nZ$uVTIHHv|~xrRT!g0+kLP0!V-!7d=@ku+U~n>GAzXq>#{! zX@8>Q&rRX)Z8N0Dsi9eJ@Q$@X<@2Vw++QxXxNQ)dZIIuB7I$(VsnGA&ql>~$<3P&x zX{fU^^dy3gMd_s+O9*P!cSbm~9y?7@6Umcd%KW7K==gx%bXz1&nrdO_uw!?=t7)!i zZs^dq^{DiCkoD|Gy!*veEZv;*3!rIw<(DAV&hQUZ4Z{rc`JX$^);i8lJ1%EBF0@;+ z121ptFxN~R<=ha;>BH2%Cdu|_gp-3WRi94YL_5tW{@U7{I#530$KE$4zUT)0V)7`h z4bx>%!-@x05ZLS~IZBT8xA#Vbch+)#>rtb7ScWD4xr7{OR^3qD*{_Hul@V+1p`!+&bEO2TyO8C25OGc0J z3dnPiNJEG|bpXYY6reN2yHKG5#WcOB|d+(;lq2V}a1iL${Tf2F3F+0reiz1r42RketOeJ{2G$REkx z{i-K>9zt>&)+V6~UVRf6(8XjtpTE^F zX-x3Xj$MEfIvWT;wzAQzGktDJaAi&P|y5h=>LmKwOa zGkT+o>P%&`gq5FHQ$9w-_ep16_)HjNW0l#6r?K&Ydn~^`kOYd+f2!W|U-n6WMjM_e zzZ`(#H~-X=1Q&Pgi2)zZacFu5M!i@nmdzL-8!c z@Xu@9y^&369O+j2@fEP^w||)*r7+gAK+S_a=A-%6@Zq4+)C#q3!yt0?j^5mDUttOJ z37=h;B;$qjmDuctqFKn_%gxyb5(vqNcnN!~?Fa(U7}E>W$}c}J2tYz#<60L2_ffgH zYXeWwRSPvqArWIgMvUAfh1vx?)Zfkioe!eGHl=0Mp<34t+-7pN9tkg}&5S_yit?J7 zN39+4UC_7}c=02JS@#3n#`aVPr@uas{%4w(MjhF(5KC%{DWCmxFZ)1JRn=*;l*Pk! zlU}H20dR+-_kiOd{Xa|uPd%_jh=dxrAri+V0`@@UKd*w_Ae=tdO|tz$ZGR~++$der zh-{lkA`*tNQ(x4PU|@Q2h6~ zX&Q@(t*>slmp&4#H4c2)v2v$VUfWB(V!mwYM1IitdkIZyPg0?d7LBF^K<8iw{8|89 z5NaF^JT(21|!PF4V0{WY4lJDY!^a~ z<3c6_C*xPgcK^cit~dhzCn|{Yr5jKPu}v8j&`DiWYb;Hcb-v6y2NcWf9fPM;tDf7~ zse}4bq(=dorCcGA*iG*{S-yG&)50}KR#|Da23W>&F-E*Cz4q9^Si-~a2BX7u1-AA+ z$GhRStaCS|wJm2E|F%nu%QK};?pd@P@ABE0>){34`j?}qet z-TnOxmGSeT6OnG`!=m#Iw{Q#phRd0q-HiQTlVf39{l9vUbBya{hB^wZWFQ4?T!C`qTGTY5B@F<5`Q2oe{PHlMl^uaP0XtBHpdL!y3!vO zjKy3v=3z6=AAIZoJ$V1024MNtQk;qL)eU?t$}mTuQ}qhQa%(KKWM$6<#j~X!O0qRt z12FDJUo$+8&cYp+pA4$(?3$MWU zU(CI?JbG_Pe8J@3_Wwh2re04XFSfE+4lRYyM^^0XOfYPZod@^*k zVvN@rfJ(wFCuF?}Bz3?+(Mclk4f&!}%*po7(91JN1N{?SM}KUS7eyGJ*{yV4LP)#@ z@9t3gLNh>vmlF7K0=3M4$DDYb>p>~<)id=EfAu(Lz=;r$LpsM&0}9*WIZhb&AQy>* zZBlFLIakLQeP;Q~4KqlmL%G;f7JemWi6%r!FQ8DbUr@?<$>@nk z+VRTZ0J6|UoUS53#TLcF_{V;%oRhdaDE(x$b24Jq7-I}pHBEHU@>#m~;FFJXWUJ&U zsTGZL4?$U}#r-NR>@*}oi>E9;u8033Y1Zp3#8nh|bRsCUT&Z>PNAO^T2vveKpPG zRy?w`D=kJ&ru^;wy?IZ7AR|4xIRCRyGNpQ}s^L0W581YXTK? zac)Ct(hu-|EZUNQL#I=8ILbXm0i7DdPh}kZN>-0FL3Wl;D}GckORIak{#E2Z5Foc~ zCVLS`dPNPp$> ztB1f{N(ZRKp}nexcEJbhOBI(_%{9_-p$wm5DEG0*on5dcHNoLNmlx;M=;QT^bJtO( z!`1Us{rU?z%q{j>Gnw_kGe07_$WIP`)@g?3;v+8raEJvUJ`m#}dXjLnuZ^syRwOf3 znaj%Wz2aclStjEIPIrSOZA_U7a!2^e1T^LB$Uid2aD9#fHGxdCz^jqY?_7_Fxm}LTpYM!%Q zSE`*cSioO~6W&#)^jYc6Ml6B{SmXR_7@>cdFaw$#@G7U7$|FbD#@S^IM<;JO!0CS= zfFht;FZN3q@@5Y9%1VGWHeJ*T>^ckzJxy~3g6!vLy46!xL-{ehgxA4}X>#ovTC?GM zskuAo(4mRvu&~tzR{-r}@}{`sB9%JiU@2pgR^}q>WdDZ`Fao!*3<;1ys@P&x#@`#a zqIV2Hp*Ld!wG4^SB|@d|N5p~)KQLoBKH;zIu#LzfDWo@Zr=f zmjmd$g&HNzLNHrgp4YBrJ9oc(?$*}#-#Ghz1}hh4djz1+Oz*b7tl-tmJ4Y*Yk5dTI z*&h~wx4%m}K#R&Uf6Z!!9ua0N3;_l!#Up({276!hi!ET(Q2|G%EB^9UV;=-g=Ttw7 z)E}{8(|D3;14p^&mIYQ(O)kX~si5NW8cfl)Rxb|ya!4!d3-&7eP2i#(# zX1xbkN{(8G9i@F<$lUPXbyXMhNq3lXfKqW8?y1I7()dI>ynPR6=cMJ%1@A*9;PQK( zPeJ?MzM?R0TQ3WXA%xU=Dn%pw0VvS_3rlBoIX8k3@aj4`fM}G@zH3}&f8({Ib2@uG znHT?+$l-i`tvVz+{X~4~Jeh&QSc>O|N!a3tjCR1bn0oo1?%S8EZ=EU`d;bfecYb%L zGV63>fd2sh#oB8K9XYBP$FeNsRfp3IsZvDmiW5b%5KbY|S0wpfVOR}!I`e#ZtP-Er z1VSq;E|!aV;W9=P?43m2kz_~loq^hz5Qx0ynaY)S+p-BOQ{4=( z9?%(Q(86`n(K}*c{f$A&)@s9^rvrAQ9YBT+1YL7Mr_)YlB4iHixmda+g)7=s?bEN5Ts3k@Q>|QA1^H1Wgkkoqu{31`8_NNS73Ayc`KOyq$6^pvs&pv0{ zQD$ZG!coesp-Yt_ZF>+Ea0fPK6-i{%gdz~0~CIG zn|8a}{*SrNEXMVdRbN;pfKJ7e{D6L20U%;@Q42K4)ZrtB5Wd%T)E+xi3`hh8s`|G~ zrH*?ftR2nVeFbZ&&|sMt$(z7z&J$ z5*xfKlu|L6&G4tTIJxnBPXI}H-m}*c=+wz^<%9dz0X=x8s}9{a>oyqRZ$7uIU~%3l#N z9m5qP%yGBdl7OmjpJuHZ#3hUG8IcL-(3 zw(U(zG%`Wh2U9O1%~%zlY>?1kBr*GJjaRU|-2D6r-BGV>mvi1=QC3RhS?e}fV}AiR zskTpKRm2p|^H=v& zLOP-%0GRHd5X4jJMM=Noa6hA>{}p|Bl2!X5ypW`ldgHEp5r&zFIkvD|2}OzVIi{1} z$e+~0mc%#S|GkJmy>p98s?*Xh%cQxuDlw3cLp97t0Z5a7qvKt?!1naHXe?Kyy^jCg zIdi;`y{l)~9|)-$X#kgY?d~8A98qaS5sP|G^mbh~u~C{@F!=@$o&W~H>`$Z4;iSyrnm0+J`J#@5;KU{gp8_>?5E^yJ@II`vR&FQSLw7?J2+B1)WdC=;S3s$ZkEG8(gXSr0lS%>yb4<8)>)9Xypp-XGGA3Y+o263bj~Z=3RoRw zYL!i?hzdAuSNXJNq!&}Y1FQpo=$cOhr83}l4IA$+OlL}>-oAv{GVkszRL~IXuyqOj z>BV!G3Bw)$x=jeRB&?Q|rfvA@jv5i75usc5XT_c~kriCTIyhH3oonW)x&-JnuY|(W z=V7?Zuwec7k5aJHM5@^@f?qd0dxrSug95Uh#LP0y&2M*iU2&~076d7XFJSKz%t?t7`St&EFqKm5R5Ag#%}4u!^;MqL*$i5K zICz%rHzr2~1hQ!F&V6zIlmXlWx3udlIsH6Yvw<}9Kt%9O9l~q0TymgDETNB|vHffY ze^>!KJy7Un7U2Uk_`8q?OO4kkh(;n&e^9Y);+eY9f;x+}* zx=rP*2QIVExZVL8lH`joIwm&Rw1X!SfC6BS2XKK|TYn#Y&>iUU`E5`(?Dg(`DFrIe z+R|^ma{kwht9e(4>}JTNoQQ4fDa*Nk`{8_-^q}{}54-@K-FG6kAsNgUKf0RkbtwfV zsX5XC_=eu$Rxh(Hcc4I<&(MRC)ke_VDCSH|GL585E*3 zwnXL^8V3_ka6u*$_H&`1dSjDA%0D&kNzF0%ZlhxhEsJ9c04oJ6QtzX z(vzcHI7i?UT{1ZBHu)6}qtNN-^{>z{h2vvo=Tbk172>08Cd}<}kOFX(;*R6!*xD(K zcmuQqcYh{5(X-XJaJ~o5pIKUS5jF+K7O$slAAJiG#@U|?3sGpdYV1F*XnSA>3p;6l z^y>*Q+yV!lYG5i3_df)MXzVc(8xNv)9O>p?eEohtTbEQnt7nUe*M<}@Q-C1pB*O5h z1>%!nchQ2F^{3Mrc-Gxs=(kA=#OFg45>=A#8tN(u4c-ki2RoJhyXy31ne1f@g>lPK{!TFuPDALx z;VmLGGBDvK?>V$XJu)>UVYzA2>?VcrgA$+?asA0@8yPTdF7_aY8eprBN7l5}>#^xG z-;$VK#1#;gBjvnK3h~;K69brZe?es*Tcck_F$z;CB<`L~kfifUo_p142CES3dHWDB z|E^jLv>qcrXNvwFTb_PQemUSVr$Yns+PxCiCyh*RcV7PQ#Vb9q?ncm*;wG&Eh?%ZZ z`Q2H_>xS_Svbmsb<_k507#(o9s1aD{b=}Q)-}*YXiyO@24`LMtuje|sgZGaw>kqf= z;u!1+u4OcZajbeJD}RlSIfvP(NRzuC4YL=aagiOo&4dh7$#KZS{-%@FcU@19gXA-hxt1F^A5&wj9NO7_llo?9y&s3W5qa-{_P?>;A5dR8~0 zHNz`)pCk?^&E~CO+7y4}N7Z*W?)g{+&C@@79-4~_&%X&|YGf}@U(zmJr&4^_u;3K-4!^d7 zB7vnj8t))!7B$Sm`c#|1A{}<&4*-9k%#PzcN;&$4O^0fZwM(Hz&7FlS?iGaZW_MWR z+OCdZ@AojhMo8iEhgzwBJ&KhFzj*ws@wb^H#mC34yw0Y15~&%9pR}|$js#0Ro<|{j zLBppl}<&xUz z^RD7HT>{({@g9ygMqYPNe65au?l? zC_yLwdahg_8}4aaU5a!^+~%iui~{tY_a)fSljhyu%Hq`g(85c77EtIBYY@Hd&SjRt zhIMI2YAH;dhTudVyE`|sx2LA_m64l%U2ML)Nk*!%ne?d`f0Aj>Xhh(4jW$0U^Oqze zqWkvA*@;Gs^`W3acMlui`3EkLgIEEkDL)pQW+(pocI#?c$g0aG~$ti9vpWZsw*&$lrG#6=vjmIQ8-ebn0S5Zk5SQ|ImncGMTqn z)*s;~QQ16Cy19dXoR;G6X3kbz-a#Tuy<0(kEi{EBi!iWi7qm!_!pbr7t5|T`y#L2z%F;?qzjH@8-+a)yTTi3SR|k^A??v#|VcB zEqv@M3K8-dDL)ioxMUjDc{aE>N5o8Hc~TIKk7R>~On&Y#a01(G>nC5{E}wQLK#t0ypFTAsD#pkC}rPpKH9fA}Z zZKwDa>H&>^zP=p((0QHPHiF?@kT0y#Gm6(VMOjv&$K(V zswcjJUYxT|VZJB0_lfpjUJF{tJuJ^z#StTe$ol1RG z<=nhF5Sg%7U(jeK!H@7>(s2S~H7ptmN&f1HfsrmMxNvK3+dlkQE;BL_Cqliy9NYct z_h6mOH>qI%d1dMJFJE3Wn0&Lo$-4G=)GYcW?slCNTXeN3r7;CEj*taGfww*3(ij=8 zwB14UT>r|dnvv^_k9=>9ew;_OefHC>NL9`#X38kxDc4S_r6zjL6QGS{hFmP08iJks zP#tN|6Yoc768B}G48JvBo^XyB7}7`4YZ@Z(kug=O6vh@Fn&~-Q@cUV?^o97HUcn1J zRC*O@giG;mzEG@E*1g-tc?h>vgFL-Lq1DfXpg2jN`Kk2ct!**VXue~P?KL<)(!eY6 z&bX$|$9!_s%FGY_mz?b%HNZ1KJ35EY`y&*_n4OySgLH1V53ywTH594P+XtDXN558~ z(@9gH~_>YP4e0&NtpX$(a0Kl(P>dZTS@JO0XNUNoutSRPW(3(f<~@xTgGZ_0oh+knqVDgc zvB7)A;K=-ZH&7*_@5TaOG+!;H%^7E?LK}Y?OU_eafThwrASe`@zbkU_spE2E?&9$B zr`_T{I~QCEdtY4K4qPV0mA0Nt%lys8LE1~kIhn~gbA_#%mw-yls8CSHbvxe zn-TnKq~xplg{MjsYh%Mh;}&O-=o^YkVY0DyieGl@HIkuP$rfkP)rk}RJnPk~f*0*H zl4Aq&Jci(R+xt<`7Fbtlr~W}hoc#*pWmWLkuJC7>0&va*n7`X(Qy{mjG<_9RWxyN4 z#k0lPYTfTM9|WFpWI!khMx}@1IeN)azAw<%jIYJvhccs7|vxGGxfJ=_u2) z^*F%`lsuefL}@+}-<`hPp5|yy9XP(O72W}*@zAHn9~sZkl1%+dlr?F95!0b6pPomK z%}2he^mpq|Q$I3Zi1y4;6!^Ak`KYKXA)sp;T+uj*cx$D65ir$)v*E+@0;VTsEFo4_ zxNMm$qaZ8xqJIorn%>`Zry&_-@Fp2$!2vJamDkWk&52o2ODpGC#VcnzS`9LmP$35F zB$*%Grghayl!r|`Buw$WXrjEA&J762a4-x6w9Mb4$hb&oEFg;M(~?vyS0uUIazPfC+Ycw}yvI z!DPWUlnb7kQC`~-Qy4s{)`Z>(Q*Qjw-SGASA2n|O#k9aXtLya>Sym+XXYIWJ%6b2b zoQ@bG{9fOMpNsYuKEj#u(mm!j<@n+*cN2yBu3Cy@q?!;wGDua_&u(dhkCTMH&*r~^ zO*_&Jmlhf*TvwosqklL{f>`Md;H6;{ve32KL@s+XnM>{3AxzpA7Y!NFsO)~9nY8GF zno)g4U^by7%iSBrupLN2)MG%#=wYvU8VvW1Kv<(AD6a0vxyBb3yx$cS1B=?Z&jw3Q z{A_gQ?e4Sj(166bx&xk-?^tOy!s7I()leX?DGi%q=QSbvOhWr7eQ`Y;AJTH($YLj( z4+L0%UvkX37>uhtJN+D13N0l7nVj!oA${{2U)du`KkPiiQvRgF248c%5b%3jvV9L+ zcw18ay^iVFRrg20W{7<0odvb*B}b)J4$-3A82F9AfZYaY*+Tb}wgjXU?t@vY(3?M?pIe{^G*#Uc$xDPxA}dF_Jal-QtxS}qy+y=x#fu{dkB zw$n?pBn`rm^e;y8lxXQnMB<}y|50lyw7_p&b_fn$)WS@a+lh4z!LyCLQuy})zC1UY zwdV&i4{o<;y&8Q|F3b*R#Qh^?#RB|hztigy+V7}|kZZ)s)4JT75&}!*tDf#4$#hvS z1Ww73VOd1uTa>$%kYCKd-%bzAU`4$_z0Ie4EKbPY=-(_tFN*gk}C{ zp7vs<8JtVFkaaga?LuhR(VrER$ewB)N6dV_5i4cJ(VE`s(CoI zHLwHu6q|lR`RX_8W*y@m*cPYRw!YsHPi%dC)p%-N_#u_d-8OS(;keLZu3ldT-a`=cF+T!zrskUt41rVJ!FTMyjCm76Kj>=xL#!1~E#{mO zH1ho^a2kfEmA005-@G6~hipHd91CtcKt2GRZEHzt?v+RLLnx9RF5#`H{zRi4pTFF_xlRrc-yRABs`YV=w2K2M?RIPE zxlh{1RAn%cBS29Y@ngf0gq-X(FYjRQckW8LJ_c^dr(KVm00ROLy7K2lpi;IN;}j#ts2Q`TOPivdGreRpKWX-vtfOp)wf~DUDBG2Tev7 z9ia3W@+RbT%|tzeZHuPHvUBr(N#c#yJ{;fUuBfs8VLOEJdR$rRSK&+zy)BwC?IHE=PZx)+2z(8G#d=e;RE7(nyrgXH!8q)&9i&dc~jj zCHWKS47wTSa}3xi1SwIA4Vf^hrM5#8#zAyut@uo`Eh7QC9eMX9Ug&3|I7yZB+9_F#-^JdOcj`M^7NP?$o0*5f1aV}l9nfy5TcQS?z+lqlPX zM!8@ut}4=Qb!G0C3iY@yNrMUo{9so}J@q#~_aCW%m01$KM)M}XKi{j^SigyX;b7$d zQ2@lOkD9y9h**g+oFVbqZ#*bTPgJkB>~_gunvwcC!LoAtyEW7_=Iw^&Xpt9-G86rN zc!Eui<}BM~1x}j6>vO#))oi*o?4Ai06*Z^46K#A$n%{v1k@5S6!wZDfuz`M&kGSjx z;^f#!mZkBX;aWB*03JiEuf!JXsZGV|KAI{ua3ICkLFG%0bJ%D2cz1N#$k6Udy@XyP ze+%G(@LDSW>g%migvB)DVwWqF=MzH2bLPIQ7TpA8zO$F5zRq{3*P24%6};>E0a%j2 zK#~xQzx(dUHHp%=fZj77tn5NnsN7f?;R7S+FJz5W`RhPDI^T~!JzS(4 zOYs@2Iyu=RS!a+|${EQ^Zhd`6Dw%^TIfK)%O?ei!(CIsov$Cc39m-_MrU$RB)>mZR zPCb=1Y-<M3W0ukyV^G>B-oyKp{>onBVryNl#BV}UBW)x3IX{QGJfKq=n;k+v zL{=$C8ii=0ZsAB4U8r!-yg){WQtZ8}jPK2!8MT=xe6-<)@UR+z;cufZT==g|QOkDa zS_5-8>`_50!_QCGj%a}2x}vQ6RP(>uyc^q#;_C9rb+>s@${Qk}uulKQ0v9*s2et2H|4 zSNj>2W)Y6ka2g8wzQiP#EEMt729ehb@uLk#Pp4DQ1u~R4jrOV1b^LA_)c`9MY*my} z$J?mCz8?kS&L&sdx)uEXx!v{?^h;vTgTiX=KDt=OlA-~HZPm2kRG z=yapYn=(!aqnduLqwUU@ng2uAn}MbFdTd?S^-pDo&po(j9L7>;9+sG$dM zM=|qa+RsL4;yx!WO=S(RNp>q7)xCIGr}{AV4+Xr(lYO`C-0CxF&bm+(8;d?uEh_7z z1ls_JoY8+gG=z6s#~CAe$6R|v-g%1Te5m~9BO2Hr7U1g8ginheo){QBi`kTwHmo<*p_mlk*Q zThFaR&}GD3k8+}%ien*jcUA*pBXA{ib5lgwZ`{Ypma+xE@MvYT+UEXr^=t}A*qqU< z9%NC4%;+-dLv;mk=jOT@1Ih>LthCd!9QeJ@N@M2pG6Ktrd1=SYXZi)aKQd-qPH}Bz@*EGZk+%9{x?iN}v8L^yt;|Tc zR;2IPkbM8}M_0gkjn|OK%H3$|A&ICH$me6fKca=#(Wr|Nbhv5HuTTydgKgZOPjtOz zKXlWy^b&OCLAr?-RQ|ep-h!@h?0V@sH!`(U-V~ZZzW&FYe*U%Phi`BPj8dUPk4Eeb zZ7vwbWKR7M5v;#cOSHyK`Q zF@M9OqIQ{AmY-!6g>nuGP;E^?6~Ej`gZPX(Xql?Qr5`c!>Lfz9TkSV5Nyj+7xtVWA3U3!(f0?MMtt}30r6`QlErrV{JHrUZ~W$`_R zqH|q$bQx9XB!YkO+iRzO^g+h`l0Z5>)#W1nk2fGQG@bl6`q%2#Yhc&=ewvT=x>OAm zUc#A8&lXfaD7#rbL7AP98`W1blF(x~v2xvKCF^Bo9cPxt{YSNS=ZhHlRM*N+=|Z>k zPGSw-fWSb3TM)@C(4NWsv~bYYS%skUsyGWRxY{UFXqZ9|yWtm6UR(6ywsF#_Ua zy~&+@LZdP(Ny20aSIcv{%JazZ%&g3am_YmwiFnJxFs@sRS&1#HPb>4cx1+Nittzzc zI#rB5;WcLvmJ6X9%#`ek=Rs@H>)bk1YNt6FbbytFvC2+4RJv#1EK{V*o^#w9ZV-*i zkxUKkLMvEdfK6*0qx?|uN5SFgX6_H)csK6ZtV+au&3z4>IE>$p2h>>wB=5(1>T?Y` zO0S0UX8LgWubk#YTN(ripl{NXncmGNmOu>D1b7ds$#h!WjEm79*9E(Wr45G9+`#P{ z_=<*T6BL_45vY=wOn3W|t(~}FbE4gK2LM#)KOY*l-%yRrL5PkU%_<6SOmu4jK- z4aKfpJ~4RHI$EMo7gAE$JU`8Anp54r46{?-!%monWCT&S{bl0_vC%|gBnnVYct3gi zEcl>b(_(id5GRITA3_AgREhH-xymtW(*B3To!PO>HZrLsloscx}Go7D>ne);~On$6S7*0KMEf29jBgX znvZBk5dNU`+|-Ld;`suGlZeDnCYSewK$$m6B3=ZlT zomk=Ai;S@O7_kkNW4G3c^4*^N)#zUq_Nar#fG+`eCqz5#&_dLbu>R8n=}+BfWA8c zFE%rTVg*lQF5(zIjb9<&xI`RLIC6iTFYDlN&fITiW+_LH6k)!M-k{83_U{ZzOAdiA zZUTTRETTWMQ_!F3F6&;SfZX#mLl4gpP+s%WUJIk1@PB+TB9No0qB6>H-(~!5Th=VV zV?0tmZSFD#^r!*4umtSNO;S;5RkhNad8E~Oi3+lTsC>_szfh{j!XTR5xNh<#t*jwd z=dl{;78DFZJG05kfw%NJnvHHCtzcOI5C^@L?2(t0EddVAgHFxW6_5Q+o9k%GnsJHN zH<4E@PimQxeEiI>3jL7PxYynR(3tCd&W(GicX07vI$496->+oi+Fq*MTRcAFvRCo~ zWvh*IrdlXXrw(KhBo+O%=-jZP<@?~nHy5`5%<3wbl>=zvL(;FgB)Qh}Tj%I>YV3^< zD@N|ew3ySSwo`t2acBZ%mE{OXPPZlGvr}^E4YI5#UM4VPAga$mG}rTL8{><9%$Xm^ z5P}H@(_Mx$;)gRmewlv$Hr?U>d_Z3-ZY>0CRg24CJ~u!2$Y&WJexrXDvbAfg-WBZq zlbxbzX;;Rs{WU5ATIlwM9#?{0eWj!^HiAfLlm~QdYbB$+stbY9&&f(n{iaLAF z5Wl$A7IUAtrZcyj_ELvD5!-wJ=?geqj3j+e_S&;wp3f(R$tsi;(&qQzb66#;5P*vUm*@N00#TXh>I zqh=STqsj1y30N- zH#t*AC4l$e$KUP|baG#7+MAvGQ7f$MZ2ix-Hw986#4+Z=08zYgH}5$kAx%bfq3iZ~ zFU#$j_iez&7`y#jxf$2hYcUxZaCiW%wzeFv;mEO z5z%v=WkypBd>MRBPB_diNBO7F0EKZ!o-msx97g^h(bjPh7q&v&#SfkbZoeldo@NUk z?KT*#Gnjd_F5W_AUP3a-KZ#*+=xdZ%-lP=SDstm)&-mrx!;R@5p{T~LD(!5qnVF(2El#>MNt_kg_6B$*kGsUp zGi2L5kNH~cCNqw0kJqve=d+H6vyQ*9XD!>v5pF9sJ3P&oB(8NdgZd?y1^VvbLDyi# z-P~lDdmie-lY(DP)jW}W#kwPXEuRHyr9@~dVnt~KO4=L+-`}TLko26UW|H*Vi@fT0 zc-AgiflkrYtk>csfUkDW%8tUArhf zBJ#a;iev%0T&#nkfnUa3E)HPVwBC^re(cKZsGB9Xkcomkfb;kb?2>k$qi1AF+0HO2 zep{&8C93g_$HI-1tGJVdj>1mL@d1Ar&7j0MlE~tjM@xq zdvYC}*7Jj60bFnW00CpKd|d$jWkaDuYR$c6AiTCGAU?_`bCAVkz-YUZW{$ucuRPt* zZ{@Pz=;5o@%3<|vv8H(7#^@;g9;DSFew_+#sBBG{m#>x-{3g_l6P?Stu7lxxTge;9 zqC39iAI3VL+z3HC-Kn_U~XlY!E(i(ethZMp0`a#OqQ!sgz1mY&S|KclA?BA`lS^cyE$CPs%IdqM^SmxJ08B~aa2=l~)7hzS1Obs@I7M)>M&YC7!xC)6pTcQ9J&-#Fr3XxBt$H}Mg=aD6( z*vJc@TQx*fQ;z0;txU;p*Nn~#oz7768T~!)>^ZaT-|W!$-m_mS4V9>TJu-3-qcg%| znioh_dG&4!e@r5`g&uFG?8W;A_oaTD&IG+p$M7>$CvdaEvf;Z=221rU`_ee9&x7GS z6900u%`g)@U#L3O0~86 z%mBZMob^vd>KkLxp)|iVV|ib_Jxz;q_;NDf}2rsmG5MYrl6y4tNJrT&m&sBOr%i1V8*xM72t_Bw36 zcb-kc^m0H6OR6B|?g8Q@ju&wzle#6Zs!T+GbD?q~{!g2h9FLg+j6-yU7XSSDpg*}E zF$-T%V(}tQ@L+NH2NOBe%PGJ+`xs-gBa({F0vWnbL0q$+bc^2%)M&BjgE0npWz{WG z8a5sFYsC0DoBI6_Q+T&-*=H7>?1cH{^^mP66=W8FQ;%YPg2_+O)!PfZ8_An%Hr{(X z7{%a%W)*McPs7~8(UDx8hZ3)rXFA3WsFB|6ymanB=JTF`FhPV=>^I%zHy#TQ)H6nU z1t}J0E1z5@7{}&5)fUu9y#(vPAd+*|dkgK7$?@Bpy7{_O0WL}BHOw)v|np9M`M!ZcElO?E8OPn_g-(?(g-t%j}-+!fm z)uipLIEUPjy2+5JMosTa;b3Vv)akd3!SQA4(7GWXAnKB z@AX{8kyxhU{rBRB&^nRNqNmaLprxI%3*;hKJFa6MfWWgmdA$2mxBqsp(viB4W)9&aA&=opcZ9xH#VtSCnkb5>16v*=t zx>H>ycA7!VW~4B_x{vlUcZ=I0SI60QFKGZu`C?OdVh}!?G|odITVIBdZE2`yv|oe| zZ=`Y*{n&hOEVa}Vq5naE5kj4doqgL|)g~6#?Pw{=^6kECnfMJ+_dg=zNwu%Cpv>;9 zVFV7oM;9^5^Qfl@Qm&`wzhH5G0rc_h@H8zfvR~K z>_xP+9-h7i+*d#uupZGZnAbX%@8-=R!*45k9EQKN+y0fB`ypMXso7W|zp+A6C!~_;SIQAKKvwxyr87Dqh@^F@=yuGLq7vn% z$DAMyiX@2&L{MAqh$E%+p>d(9^95_V1#o)!&<1nek?gSegq($`rsRfy(Y(^|o-mX5 z(#=wLNORi5(arahqT6&FS{q!dlBeie@7&?|@P(KBI(6OMqjC7!>5^2fcJ>E;o4GBo z5E^Dt?GYtZ^5X_T+^0oH&kG(jT>@Qyi~z17q(m6XgdoBkwqHOHA$0h5n}_6k+^GI8 zxt97Xy*lI7&*FYX+SgJbUi_@kXj$>-r;ueSs{2dHspoi8`&eSa!S}&L5_t}ymKi*0 z1k%Nb@g-ts{Gb*3)BhsX-2d40_+RfzJ0sCcRBwpEt{DFPL=7ZxQCY0; zRsZ*cOv<2Z=$}iSeg)rKPx{ty-TnhItc+6#S|tZF#2oC8wz2)%*U>+VI|B;yBNaHw zR=93@J;RR{VSBPy0+^O1diFez7yXXY-UW;ewjMO?`qvZYCYn|j%l_=mE&T)uuEKG1 zC8_J#v8Q(yGpsfLJxIAMG5GgEmcI`ML>+k&dKy3@=3^T@%LNx<&%|6C4TqiXQ#H*-MQW8-N{EvJ@Tu&dKA18e0%1_ z3j<8%qj{_5qG{_{;fS#wC5PLY1lwivovog_fZ@5U1kC_8rHP*WAL8Q#uQ_2l9cfZ} zaI7h{G`4nxy#t%i5(c1a{8fCG8FcOudwLk6sp6#7rDxN?119Fz#3%RKkMM@j$%B>* zLOESo^gp3Mc{V)x)kO=^uri^SwX!dLNKU$?Te)J$NPISl)#>USJ#4}mR5;L`O+#7@ zj;)oo9eEtqt?w?^`5&#QFj?m&Op_y;{JAvrO{rJ$M~hrBD1&GZU&2o{Ro0aWt2F64 z%Qu0g^EImzBBiU??F6aYH=Y0F2V|II{I}@)8ULSgma*QPPU3n+K==$WEiD0g_t>o|180T?;q=L>^qtI`J+U(_X~eC#@Q|LC4a`6pOCyfbbs%X zt_OAAKSoH8S1n@$`fI-Bq8`~`Jgb_M^3Ar6(idrq(PyU9dh#|yY4 z@}&o@J3JGA3zo-$-~ zQ2n5_(z1GwS*OQHN5=5G_Lm7mqVi8c0fh|9E6+vEw}t6`6XvzQ%^nXNwV#~-isneG zC=|#p^I(ww8L|Hi{>f2Zk_N5|bxiqAfB6lPS=+>W6!+D$wr#3b6Eeeb3R8E(*5?7t zOK(YTNG+2^8)dS6R4!@CyX9Bm)HN!?Cf8|ByuCJN^=U zYEf5-Dh3HXO>LAha{r9rU7a_YPxGGn67J3RMEfz_m4!BJQ%}fG$vf|Rv%zQ;miK{6 zaQSQX2wYt94H8>0`w|2ER;Kk6O@i5PX%4;h2Ssqr(5JqAp21I;Q>!lA5W)O3J``IB?~8pP`n<@p}bc@bBeQdUn-qwPE2@cQDy zb-M1etYeLzNujxTVFww<1Z&&=N!o@*^Q4R&jq|Hl$z_`|jTWjn3}P2-N^atRf%upr zpX!(_E8C-_qO|p#um@o@xtEGkFW_T!_V>=u+>H`x%$?C<3DEe zAUSiwe|3z#@#xQTalnh4i^N4C;id;0PP%mf7R^sMK-sC_&vS1-Gxu_uJv+HMd+n9+ zI$kn;e#sUIsXJr|9r>t~V&M6%3sB;mo`ANjIHj1?v;ZtL0ASfRH= ziUDllrP%_?u4I&M@WEDt2Z3BqW3S=(&lvk%mJ%w6 z-|t2)@@(vFKf-}9ng9QY#Qbv$&Ft%J{=rG(emh}I_7irRq~_~`yR+{-;NRxL@!OSInTBKHvg|+5|0{2rb_UVBE~YSuU&+7XEhzHWXb!g+A0Y_ z7|LPS(G%K7rq;awbCC(%hgM=in*Qa~o#ce$ka!(Iw0@1iS5!MN7H)g|&)916z1tLZ zVYab2vQx-*3&O0Yb~4X&*y_=rw-kl0z%$y4TE9HCyc!VjRo?iow#3cs#M)1qa1`Cs z@BIAGR!bP66NqyCV${F$i}XkVI!5^9wwJ^2zvs3;|5v}Gr%sA8rrRP!D~=q~2tK2c zC)HmYuk*DQBCH2+pQU$R+uL*N4n84Z0WWlzX={3()`TW73?((nHFy~4=p3oZMb3Yz z&^>WG>ia)?sFpr#(;`(DhdtVSU#g#>#aOCidT;6xJf!#mAHXpWfxd>}x|8mQ-a(*XZeA{gMV03ev z!7pIZ-#^VC{FRILIS@NK8robe+uU>3@1dWX>K4+2hP+j_-sOpF2QIf$%ke)-#PVE1 z^wmtUM(<|~9?U`vDrT_^_En|yKdzG^W^(r<(tpglkI8 zv3#Tn-_S;Cyx+AJT*>ttRpV36KEJ|x-bSKYo!qrfL)w&qVj;pSdAnCqPjkk*$l$2m zdB5qC_|fqoHK0DW6Wl*=>m8*rP(QxLYZxU7N)NN`f1(N44PLdITP5$*3_&HH4H^>4 z4w7ReheqSNne+Bh?m7ZBP9-d>qNb;2Cme2V*UH|yP8*e?q~b4a z30**8ub0fzNX5u6EYn!#0Qy8Zw+SA(WBR`VGRpO+`!io-OrZtaN6bhp!`B>*X%b#O zi*Gc++z&^3x21CbxzSem$RvqM!@XNK?I;phT6HrpMf5 z0RDN%*t6Ml3)l%?;t!t+0*}gde2^~iBz1{)#eblC$_>!xh^AfQw?hvHuA$A;4tg6E zNfPF^32w*z$>=7`fUG0HK}%l5b=_(Oro?|@xeH|Y{I4(aT}VW){2{Tg!trP)!o%wI z%moo9`PpISD@d>?|LmsrcCu<^9##seQrdeqbzZB-1}Z!USui7BQ4tjKZTUGQcI`jr z6YzWpMm~!qfg`RR92GUc3AK_`+UJh%o?t6W9^PKZNjObBcC)fWq3fCd~e0`tg zzBvU}V$S}8M-pS^e+e3XWy8w(qVxrJZ_i?8-Yv!t3{!xP=2bxq@EJDno|PXXE8)r1=-?@Mv4nX%b! zg-N-(gX6~^15Zv2<{Kvm>DOClX}-gABjpbBXK@B2fnFssCs6BulhLUuVK^pp9G=+{ zd;G`&)HDDnOR$-Qos-R^;N9kx9x8ITX`oIXWF`aaaW4u4d3`275#gclM{ue~{5q&B^=V$!O!wxrn&=!*AsW%mzK{%- z`x&>;6T5%HOxYT3HeyxHr8Y=56=51L&t8ho-; zH+kT4wBtMBqK>(K3!de+VJly03!s8RZe$7Rn$XB8UxMCL|ASJqx~)a_qxUuqf0hKs zTL?FZ=jbDuRVw?w$7X?_t=BZJSHP5Rzmcom0n*UwUZY*Qe>_&ar+Im0s%ic}`s=N@ zf&M2sg3M_Qkc0!?M)~&Q=NObF8w`3Lh?~r}TCY&bNVTCbyH1W18~^>2U$nT;Rd&t2 zgBtAgU`VzSa^$<+9v9QhWd>ZRxi$s7nr&x|#*eVw8P~&&)}RGP-JoQD8_1;N9&6El1k>`4j26Zty%#yWS94LZ=r^!FF6R~{phyL3S~j9qFkcRfbv+KTIqd~x z__R)A*Pd%=>91SvA#7Y^3HI}6_AGMqnV?8)neTJ$)nF8o`g$v$=#luA)n|TZvpR%1 z<-#j?*wXiaV>!Ze!f|EKk2x}@u_l5guC}ahcUssmR2_|bQnKZ}VYcEeWrMKxw!{mlQt zb;2WXbQ(9E{azbsPbBH3oAkJR@Qr;Fy>{ldQHm8*9+b0M7tXY?^AFBgE0)?XEGVAy z>?6c7Qh)^}Nd%`q;m5a&Fx_bNJhEz&omCrHT_7qGbL!pB1v)YlsdGi8%1kN{n{g8> z`C_RoX8q;nE3kJ=>Gz>oxPfn)3Fml0ir?M^Hj^VX2v~m;^mqSS!o{z@Zr`ylGCG@f zTV~@>{6u{ci?Byxr#Wk#ZNJq;gUT}&q<6@6MR&m5QMT@gL8;WzAc+h+6o4$XABKj5q4XJo`_zsh?n zkrj*d=4*G<4V$IHXoX*@e`2itviI~(*4}Tu#pAMzpxHMdSz}D<56zTIM#m#nI9E)( z&4D2Fg)crPeb#tkoN7K_4a27NzEFcZ+WE%2--JORgC7ni{kUj{=(dP>k zoXmHto)4Jz$bj))AdZ(3XzUGj9;;4uE6a3)V~svr&+oQyOZo-qLKmgOhV|kEb)YGJ zau@IGw0i4VuSsVnL$=f;$314l_W=y@TH-~B-`Z7Loav6Ju2a_jZ{ZuS_i0}flWgAU zXwbo4IgFbP%1Zo9Etu^PFF*RCaY$!;ILxmHrSaPL=Sa16WKso(k3>}w0wbvP1TPp! zQ;$!53;|QH0vzwHD1Q^Gg~}L%SysRsx4ZQ^HOuy(%%YT-RK?P=DR{gPG@*+$DHA)q zgV@EUx2aa1g$S&D)AGpvcG_LTg6-fKaT#o3Br(dVVjJIb+xE7fn#o-2UL71*nV-}< z+oqaspHKANWYJ!e4>%f*UhA6cQ$OK3U9`obR;ac>3FFXX|MlOFq703;)X4U$dl7We z!cZ%yz2;Lh+8BK>)3lb)v@oGM*nkXLd%+9(0S$0)7jfE58>os{?LSwR+-|IUFHVCC z#aMEY9t`}Qqy_`*#^vx>kd58fF_x4qs>J~G7po@!OJ&bEt)F)!#c3P6zU2WhzU_EJ zP}0X0oRTM;g$~1fs>TETH@FDLn}Yr^nt9zn>9*wAoyX000>VJMfu#L$xnDGPyw5LW zxUB!ypI{$mZnR=Ws`&EVJ+iw}EWumow47DaM1`4&1#B_tZVtB3QY zW3xA_L5;7iGXD^=eJJYrlcjRE=*fH(9G1hp?3U)+s*_2)`XBH8^31cPz-+D2Of(?~ zO4(%iicFtbAaHlGytG)kdcdd418)7;UDfAbE2TKq^#AlC2!=%2pdtjQS3EanyFMbC zqZZHl%m>fBKwa|KLyi=336|@1@IHrk3!ev@Wf5? zkWW=W!3kof=Wz1{$YVKmqLl?cyay!$WG#ozLt2W7fjKc&3iw%|30!13(N90-pMhi$ ze-G(ofGLU--xZ1ldT>cyJdq^#F7Uc<9R@7U=EDsKMu8I3OU#)<5h(;n+91O<-Go{v zC;qy6pc*;+uPTDF1ymcPz zCJZ`h!QBIZ<}<;p6oZt?6^XgR2vy@-w@RVT@719&eioNmFXRMTr5hV}?=VyZ`MGyrANyT+*9tdf(G zgL=?8rHfQUgO>#dzBT=emaueG0A=!=3vrrSN0RX=CE6 zKw>0Eb`F!>{z4>P+J-G4y@fqHjBpxQ9N?>u>&7u)i+fUee*fwA7eEK+R2rTW1K9_} z=MuGJkW_3|{{lOpUL5xeuQK}BlRV2i)I_EGUjXU~F&py~Qff||7_cGHJHbX1)ErZ@ zW4b>Yd$71xRqU$6LYoKb?;hPT+KB_dd6r^4qIg7d1;~>8I>x*i(L>%UZbxldqYDbm zXZ?;$n+sN#hrAsm9-H=l5PkIgM-L_|aq1Gpah*5(46E#!Q2SSY@ETV|`#!piz#cxC zo3?)8`B!_rELC?ADFT_-(iN_Gr3LQHwyP)=jAf z8+~1YHQs>mc-5#p#%+BaY5kC7*AnZ*!RYtTCF!&?wXJzmS$x4)fMi}*aO>0|n63BB3Z32zUhxK(lSJk{a!xa-s(?nqfw zDDIP_Tr~13ymJXhHHg2*`jfg!UpSaY^BaiKJV=VAX1NcLB0pV;0ls(qkdpJB1=S<{ zQ!GI_(jS}OC-<+sTdt|dA4tv+z7%R#soVVKtY3$P{O1@fX;}mfha=6CD;GcXxL9X- zn)FtYt9h{$U9r-9-CWEQ6R)7UIn>$y&S3z`7SGR#9=S7W&oE1_@*I_D(QY9rH+JJX zD_J-Nyov9@8Q7OXWxXk#;w4fp)bo$*k#vaTu)gvZK~OmD&suSi|BJ(7|EV+1MOQYM zyIF}E6G=J~@%Vi}rruUvXuDcYBvA_Ce(jSJ&g?G<6;hlp^9}xhgSHo6<(K*|iIQ#d z&GkwPgKIhwaCCp7f+!Wp)PZz z{~g`u-%---RUJC&RVa7-GsCSP#`>(7dXfE)BYiXluk(p7PB+nd|LjVN&zIRYW}+Ci zyd=R-Ynso+2^36m1m&Qn)6=C^w4%w|Hb%>qX2HT2&+k3Z5_mKA^XbI*!pPx9)NGXv zx6YH)m8k+u-H$IZOk2Gn(huEjcs~2-JoM5Cwf)=_M2~Z&XVMHeTez20{l*X`$09ui61h$V5cNo?w@}BnoHj%MP8vujVDVL)lU> zXv_JaoG9j-cNi`30356YmChCk$0gLIuj}>9F8llNRyfZLY?|*(^%!l4pPJTN=QJfBJn|;~EGT z{Y0J|#j6l~3GaHl32qyT1?XI+n-8x8{@i=d5giQ}PU_-po)%l%g#a&i1*q_qcIK&1 zZx2OgFnLh1z-WOhiC=uRD;mBk>gYm8b>8b%DN-x{zDaY(XT|-jSb2Lez;zl}N7bMK z6iKO|E|~&RoO<_80YCL-4sA1?HM2e^E&qk8);Lc+d4-KLmvK>gB07P*C z0!KNsgtGov@b>niE_hix<9B1Ava~L&AVm({{>oS-Sn0bPKB2jdBZ}a|)>@norB+-GU9!g-PAilC!A)I%=Tv21?G%L600K>V3 z?ckWc#K8&5w!4d%>e0u%E>3<``S2zjy^N5Mj5`|sidy(?EuFASxQPx>d-` zac4OGH>p?0!w}WG%6#fomJn(V2&X;B(38wHC8nB?_S9)kw!Q8^UwLw3q+_{E;smxQN^wT26>4`fFJ+(D;-!$8^zf!uaU zS{S}eOM_MRl6yRJuv8-Z`w^w`wm`NP%oQbQ&I>d^5eb;ci)3c-Fw?Sp5o&Y2T9a<{ zMzt$gt)MG7RpvYEw$gyVI3G}}lwb7s@tARBtoJO(S&MOgm(H`<_)z|Jv#nU3IT*t! z8I`m^Q#fCx7Pp8csvdIy=s@-On9oKt!;Tlw^Y~*G&W>i(jHo0`h07^0D&^DaV7Rc@ zq`nq&Dm-aXdIfgCNQom%IlqCKr9B6g^PH23PnFZa>Js@&DO7(-+ck&t3*4M&S6zNq zHU?p6c>YDNx9@p^E*%?eikR45NXX00G*>Rsyi*GEi%32+#bv zihf@$Q<)i$9LlJ3(J|G)n^10{U}redOH{$Bcy?JB#)`S(R~O8 z_!tzCg3y9Ya{J^@pCVF2RVgqz27;GpJKsLkRf~D3y1@M*HXgjs=@gh`6%oKHBd_v; zFUR(!TZaq3-cxdAx;Sn~WOs71I-eIRBRPlnh8~o`Y^*)jMl3H;;<`vCopYRVmQJ2l z9J9%QB4g9>cd>TRD~URBk1POZ*|htSmP+$m=HP{@l)_NgAkRjP*y}OtP}Aink@rWK z=0kpY+27TJlK0A$!~A%W%(qN(6H%*eK}pJub(DXm9&0Y;CZ2?M6M+wx zh-ry<9JP~h=RlG??%TsXxY>>)kht%0L}zFSKuuw-TfJmnczpAi=T8kHc*;0}c`O2t zFqI`9pgYdHeEqsaG{`=F{l_)%3HwcJww=5loP&EVM6l0`4GkQP!F`_gP2w>XBdTAZ z&Mf#R2_q;dOgyq7T9(BBYJf5~iP}+yRDT`)_F#+tSu!W3bpHoh^j}2KMWDY?fx&>M z14D>rR5C%$qFm!Yt(`Q^e?opjY&-&ZoKJp9)U1;Eh{EEZ&iA*_!(-YuOk8f%3ft}$ zd1NnkhLZjyX_$oM)jLZ_HQR1Pv*j4t!>yi~@=QNqXu1-7(%N|gK6H1`>R{Omi{PVr zli!{}Anht8EQsgCngUvJ5vbDHxEfAlYUmr#q#_Q8wlczq1btx9kQV{#3AGk@No;H4&5WUiz3}Z!#)i5P_E%oDPS(ABN%|2Nau*vysm>>2`Pr4Ic4558?^q zs;Xa6CeEFySOY3xZhF)1Ah#=^DnUc;qI62&N(HqbiUaX8%1dW@*AY?Ab3uavXDtd+ zbtp+CCcFY&01wGoN1?*IDuIFFT;wRgvnMVhu-ACllzj5V{aPG*dHJ;uuZy>Kr`f z!?4SDImf%fQ=w9N&27N(jQhPtvCueV#HI={tJ-eJ=0LYDSZ@*SC*VibM|%H0n5Vir zpI2%+Mw@Y{aW6z0%gI^lRPE&6_MlZeFNsQTMh^qfEbmj#=umIq5vHS7U#5`QWiNp> z3vMePH!?)8pnr=k6`ZY`6Ww*1g$V3Rj{}5AZo1II)uO6nzowN4L0`Y0_ZU9hh}65p z+QdVbcg5qVXaxq*G_0JSFsiMVRR%;$nzWZ@mV9LS9uS#xvxtMl@1b`kpE1s6z_6zfw3zEC&b}=bB$~;;_&p=2C+K*pKwl{e!IBU53 zET-}@J*-DsCrgcCe6LfqQ_jgVax`khdE+W(*2t-IX^N|A&MVR^!&)iINVY5J5-i3q z?`s{EaZ!et(fp|8FFtuV^$uNy>b9rjfmyjr=aN?&bKk44$?n?Tk5*qKBU8~wFYx2i zoV~*^kzn(%u}9PKqOM=k#9?I22G74cr2qC|7c&;*eL#?Wi00>G(9R!wV|EjYe(B1($W@+cRY zn|8}T0b~AEs;ydh_qhG&Yt-3B^Sh+D4UeaqoTZ;%qTv1MOkmSN)>`aZ&r95z5ro~= zH`V;W5|o53v|}0VM0rz>u$>r;3hYKc0ZDZcp=X*Q7_DycoMy8p%Gv;#$iWZd+j@W!5hC;PA<-1bH_1)TbU# zJ->}aM~8A1yIay)yzFxE;4ZD47@Do^U-O=(mA}XD_+#mDf4ztQ{`|`a%3%g3P@4lU zr5m+R*WF*I!fVDhE)1_K!Bj)i#GL(Uh@$%O7{6n-|71Z~(_x?b?IywVh>91IIsw-J z1L4(R?`z^an~(c%_OAJSy#jN}9c)p7CGnwo(DR_ApekE;QdbxqyX(&OZ9((}8tLsC zQ}4928;0E33PrMITvbm^wZ4elhYqK(9#`H9!EIaH`WDW$Aeo~t$<9_H?<(J&`gVIU zxw;zft8t%cTE;+jhheModIJ<);F750(}A9mpsGJxAv7Q;9c^C5$AQuz#n{ad7gqwYTGM{e0K)S966^6fz%Gw8snrQyxB3{{m4iKpl(5EKFe|+iLv~;ZEx6e0t zv=&X!O?WJAK6l}uXRjx$cQ-ODfFWZy$38(my{T^=FaW4>?`N|V^fe|!zFB1Y+^88c zyw#$=_@d#HaBS{2f(^>uox9(uV*+hK!l=#mjkrxvq&-gtRgRy&d@@^pNp}$nGw5wm zA4ZNq#Xq_G_C#=bT^gkI+ZW)(7XPXN)R$hX8s_TCC906fktRR5-MH36x-!7d8S9C$ zFxUWkL|`@0`;VwL`q4j|%Y>p+_}vV(>zWWs-g$r?z74*r~$ZXVUPGs!u(?+;CYI%8N&}FaCy=KGKEGd@6lotL)&@RM3|e zt9kX7;foA!Y0RxydR!b@7C?o;{1)xN{II{tqDZ%{Ph|2dt^7{+&Ui6lFiN8+Bfuq9Rs=U(f z3?5-Xj)>?-cc!8z#MGVK&dBILNM%C8J1^jW(wS*eEg0E;5@)uD%y6+OZ)DhZ2V?DO z@(}J2NA;4Yh4!bI+Vq3R&jWICK&u`;4M4gEdT8D#_ADwY zsn_RbrusXPBHndu_n(dLSXk+Zhn zpBs=O?i6BqAdzA6C-pBLpD~I+9jk2qX&t?NwA(7*F3n17beRUQz|?16!qx7{i&UZ1 z#u<$d1N0wX4oK2vHDjgX0G>VX8+#zCxzpJNz>)$&Y9g{{MS0K%aAh4%Y#~VPw1O^M zn*s+&2nu7L58of>yMY~MNUP=IK<5E^M$;lh_ASdGjIkwK2oAdCv2?uE*nkNdQ-(tppEn)+*kJ1hBV) zq$khuzP{8j+kQEiInPXtq)B4*LHQw%*b5`A*+GV=!xFcmDKpYO}>U|Iw*V2Tk`gZzp0J29{Aboavr~jFe`zl1kGJm1U5W2VB=y) zkw56-#a$3Xurq*`!f`)2jU_vg%={>+f#vQy^H(!Jj66sB$S~-4@W>Rnaw8f!`3fvI zwR_-+IbS?uG&Us{ASq+gGWUO}lBVS9>`0Os4?OCjwv?8dwH3q}u` zJry%k#G7lxP3*yYY?OZ0{=t`5tIf_2nBngdjeG>JRPX$}CGyW`RS~67Zl2$5X1JP2 zK3`7XE99qDmVqbU99O_-yJu#Rr-XPoH2wIhC-RuMo+&$ZTt7okYOE*8wA!8HOzFX9 z2+;2d_KR)A`}eaLYHfc3(#+n^&G+$1iCB0Zg8o{U#85|N(~Q(1+R^Zn@hJWFwbfKy`1Xy6iK+w-D$XKBFua3Yq3T!jq`bY$oAMv+%GxPK)ORfS zIHo+luknjtY*VRfeki2|Enm}kQNp7^32WRrs`w)8iv;f^YrNwQNS6(I1U56c$Z0{# zhob_;WPe-szRMQ0#-~cjI`_A({#iz4%u}GPutr|ZM^ZUjl7c8F&64!>WNKi)+NL&0 z(Y-_7hBkjAOD)KrM(<)Oo6IEn;JiuE8?Vl$!E^+yjF)e}kscEX&m$YPQh-sI&0ZZ%zq9OkKe3W~W);W4?dl9wpb(p`y38}B`DeO`5&E*DIRe{qlp@fz zQzs{gx-Qb74E1D-zUrFx;7hKGkO^0MT%v?rTXeh}e{&T-h2w30GbeQlJli&yN!}|HK$$!HGB7t_m|D(9OOv20M#G zNbx$%Z==spHADCO2*>;{@o&##`0jNB`x5mK1IP8oWiMj9{aO_gTjPlRP=mA>qO6l-Rc>s%+8}N)05>tt+I(2N>ujzMWPNb z;r)e2Y{QwyP)*~z%qpb*)fpEde?S(OlM1P@eHC}Jd6~L+OKy@(NEDO|EkZ1ASvpSw zU`A2~LGz?*BT2jt9YXdfexp>GuRVwSaQs`U%91(@tKoyoUcdIn&mz&N@d7vM z<)4<2Br(-Kkk$HmN_{D*;*u2Rfr965MH10mh{mBl7NZlX1Ic?vaDGlq7gh&TQBuji zzdLWTGT>tDmo$nm|IcpT9EgQ$cD9$F@p%-rc$s zu67N`_UuA2v@0x8r1R7eK0t>@Sre|6d}$_xZ%6 z?0<*n7ExgLAG|Fo(V6!j77Z~i_ou=gp+?LN{Y?ky|F_IeVn!??{*mgDF#kV3PUN9d ziJ*4sn%Axn&+NrDVpTDs`Pw1_W@`V()KU4=0r)$+7H@L>-I*(H#CvfJP;VJKf=F>w zN6W1y_+_|sxwcDg&|J8HVs2p5NOX2!=Zfg=JA>n?=(vCt8i}3UU>lWCRQC3l!pNZI zQZg=@&ZDv^Ma_XhqPc2%0~|f&%q7Hu9NhDFQ-)ernBJRpGhb5wwD@WYSS5sQ>g5+p zSTG>|(gZDCwe=4Z>Snv~XT&6kCv0W~We7hQhi8B7hpz;Et8?;}RHOF7MXzRC z6x#?lrm=vYFeF`CmGvN!9m{;k3CfHlNLM07mM+UBvt(+2nc|AvzI*qvzjL0B z47ahzWU7N|NMSVv_6`u1R1bJcecryqId`-LQP!wMeg0Hru43HlV+ONK=cAZ6>8kj! z(`BopnK1w!Z^jPy-X~1(Q5(~EvkMLw<4DN}F2sOB08kYLo~x1J0>Dy*M=yH+*MpTT z{=VCur6Iqf;^5ezhYtQ1C&gwEnN2Z-FWR#zRx!wsM>8yfe%)u=o@&34B5 zLJhAy@<0B2x+?{JPuUA|JYs$a!iv39$o(#5yGasOwgiO6ZTMe3(lm-4&9h5Op2%>Z z0LKs!a~j-~pWd0mCVqE&K=?{#oj9&}dYR6Fw3?#u^i_8!wb2Vhd5b9n$-KsH?WYVs zBrF>Dtv*IR%`tsx%U?Zk_inrVe5Lg*mHSGk?NUX*3M;(|^(u{{j)>;u6U@;aj9r&I zuOmVQr%@d=HZ{~wbB%ETU;F;a$(=KF7V8aS0B@%h_ZZzH=QOzDMyCA~7cK0;=+n{P zh-#;ew2M(C1*mZW{aO#a;J4jGx)89hB{GB_i5+E70|OMe45~X>fYMm&tX8 zqk{SYs-BIRsg&&x95dqWxxnp3!g7&lMm?V@fFjsF=-%WAFygp}8PU%E4M70{#QDI< zCD!UaEuD4ViMOj#hWsEr3EB7AE04q84^S>x%r^Ksvn~J;C3@@&G096#rg2v625cN( zqXGr)Td3!I{-vT{8zKC7K`0f|$_JCis1dI&cuJ~LHvVlc=QmA#h28fKi@jr|3+y(nLTaL z@i~5C_2z^St*S58<4tXDQsYi-VHh>mEbYTdq`|^Y1c)x4$5=7oykp2-U0_OR&3%TX zmr{q71btTm53O8r5~$+5gB-&{GmtJEX52bG_vo=-J6sl9Ty{sW_)hD3Y3wZuZ2TGa zj7!F>F@tX1>8Bzz_g5$(G|8P0%%mQ>!))1AP6CS<@Wv9!-cp77iuzmZy!$j*rFmNK zu@(cOr~1{IxKWA7;ss{R?Gf(2@MceYDSqZ?14QF`kjq*93Hg{@BV|du*4u8y_9>r$ zwO#X3rZLgmPS0s4l$;{C*u@ok3FfyD@ieU0t(RA@s16w1p9Mjq(#dP*Z>EzICBsgj z;6Q0NDJ-SXvn#hHyI1Zy%CF}BG)!X zi`WQH1{mUd4NuqZB*_RM?JIbt45)6o(K;!kZ=d$Vl$38rv9pRATdI`tf|tqk8*s76 zfalp>S!y*~`vNwZx@2ONW2^m0L0<#dTc+QBi2J&c_YNVu9c=T6%4YCx?r_2#LDVOa zQ{`4E6MZ+y+vbp?KIeyDmkwuHl->G*b_1dfg=fh%fmLyWJz)z)Gx4Z3yyp8PynkKt z$lC8qX!~OoklZ0DI|uGRwR>Wa+GFeM=Ww=!CdqgGT6 zUVB3bsZLuKKlm^S%RiuQ zL^&IZIQXKRRhOYFmBd`=*@=!7P_PK7CZ?9=Azgg*MlO~2zt{xu+-fHlvcz7A1UkdN zeaYER_B%EvS0Jj0#8`zHG1#@y(}ZbkI9Jg{OxRRwTGO62L6@$agwB@-bsi(4EbF=` z-k*f}>_=RE+`DF${l|GcvFYy`GaeF_-Xu?23nYZZ<1$N5p@2249ZD~~?z~>x5~}iJ z>*%w2j|w!#x#C1GMxt%!gqYYqs@S`_bJSM23~j5-Edkq`as27l{`xBD@3(x+gj=d; zt@h&rwhQLJTCdMNq@%kRu?!{2^N||>5D33)xUMo4u=sahY+&ZU~;L`GgJ+(C~{umZmxn`^M5XsyJ)=ds40DH7jIENRi9F%&{dGQfO^9tdG&x&I@?r<5EbntZ8} z@y{x41dt3T9hKc)%E`9CUEqN}KXyxmdAClWdTlESoStu1e{A$O5+b}uPdviAahV#} zqJf?D*iYyyy>;vR{@?0K-&Ibv-Vd(nwW?PNqTBtl8<45qM7|Pw7FP8~wn!55eo@ml z1L?K;JKd$au-;wsMky`6k&iTmaIaKoIN17}5qmcV&CaB;x_|6x+T{i7F;qB{V(i-k zmmT)NsaXZly{wIhyen$L3C8^IHG7;wiMDJOZTwBkmik40$W13~ij%7Po#>5#uv`M_ zP`bl8gQ~EA)+FF@fCQvAF$H)JW^7aBybi_J zO5S86rl1-FLED&rEN4scG1jqmB6WO zt6gu@<})Po->`+Mnis+pevo8aT^GK}-vc##{vZGsKOA>~BY2?IodZ1hDbY>#XGQ|4 zu`O(mg5%LP!etj=Y)Or0KOrmP8cqtfBv+rzpGCKY>=fQ@e7uX(+uqDY@8?=&&-!uO z7l_$@kJ_CrCaWzncy;creQLhQ@v=}1;j(d9R!1dc4Wkji_(wle-Hr(eGl4q%%ExIX z!bK^=k5g}A34#HoI>+lTu!rk}{xZ$KcM7#~Di5kp11{^SHM@7Ls}st0XfK3PJmk7m zGjpiUfC%P&8XsL=^fH|s_a&PT71Gk$3ztYL_tvp$caEKYIY)w!eU!VD$F?a|CwiS1VXr{SX}P`H+1WYXi+81>@VJI007 zDQ38Audnpn?xV+GyK%%awu{ZH3bBu!`EP>PJeokL%u9w_Tqnfx5k73R?p?;@wh&sw zKi8qxTM6}&=)h-x=Z`Z;Q&;a!>4_-xJYH~Ft`m8ERT)~Idxwl^=-v`qX(n$Z7i<9E zI-csgh&V|Ylf0h=K_OcFgw#Xqz*H`QW*`iZ{;V@?xn*Hw(|pOJJWDfT^T{*#w~%6o&I!ii(;!O?K!2jKDD*A4N>8cwE^*oX(3YbGy;-+mM- z6tx!l?o+C=by?V)VPib{`vOAlG15!6VY0OBK5E1yom=JwOEckq>v7Po_#wXf&C)=@ zufqn{)s8J&?~o1r__xVU-2Ng5JW~&9y;y9a2hU7VRQcBAMcL()r2Zq_`}W2O!o-lBq{BoCzIQzr*|??`zB!V6v{J0py>>|R|AWq} z%9_U!7ZoHo0T^R}5^H6Yn9Td^zmC}J^k09aQ$dKjO!>$+NpS z2NRCZvx<`XZ~YIcLOQOH5+?P3Qsjm}OI#h7AL7Jl2lNsAE|V&V8a+i*FmRv5KMr~O zkqH;-;^)RGQ{u1X&)i!wD?*+Ly&?F7?v@%0!O8C58w#M*WqGCmoUET$FieJ5!&-Ih z=p*rDrZ?NN9fE*JO%{p;me@Y4MLkKUHPV+ojZ-I%UaR1@J|a5MrsSRe?E6H&YcW5z z678>7aN>21b@`Y19H_*~U55;G8E?^?k-sIBCd=OKSn{At4p&g9yXp#XSpVB8^HIQJ z4P?*zSvimjed`a|tyk9kN_h3rpd{}U6LqXL5uf+(eeIVX(J$4oxFZv&GGy zALW@~QffM_DJS}ewe>H9{T7u_;B&M8`Hyn|d~^C=hI+8fcDehn-CsssmR=6GBffO+ z?azD$zXtrALt>ILqLW07{k&29%YV^qfIR7Y+2Ir7t@M6AQjVOIY0l+`_n)hzU?l_4 zLDc`c2f(kh9{!731GbiY{Vx&?F&X?X00>YweSZHh;tS{^#QOhzCNeYPegCt8KX@qG ze7Tsk)q(ah2i>?fBr94c4hDRW-%0eo?c~m#l9+h0_+k;Bvm<`+{5O6BpElHTY0=}v z(@u;PT`rG@7(dQN6gJA=O?8;say z;?ERiT2Ox?njB;f_Zs}bD0l&>)Jw%BVq%eG3|xi(&FOsA_GBSVQUqS*|8IJ<{|gz5 z3UYF!%?5W)WM3zVNXEYiD*YSUP|k{PrSB8}yrqK#M|+r>%!KB{-O6chs<@ZMG8o;H zA?n)li4Wgf$|ojjbKlcGwYR}h^zwHK|7p!mS{QA8zYO*PE^R6rXW zYe0&C2tDwW@-ZvFPiO6Ldb8%Kv}>(~!RU`t^5YMqN32XTa{jVMzvAG9(=Nt%CJu&^ z_0TQFg9n~YaHa8;obHhT{%`BJ)aw{jxokCj^(Fr3DJYHAdU%%aru-CBl6i`FgQVM;;_&l#7; zwABZI&;_Q&ICJneq_c90wFp~R`C3`7$=G28sPAZ6mTUt$rLo7KJ^gFo-jSTfPe#cc z?@5Do%r#-(dxbR>;w$tKV#oMUI|?84{>MgdQ2TiQ_4R7X@sYnNnT7b&OB3+PTyHOpidE^2eq&M&O0j(QsZ0;PM!^XGa<{RQd^L(X7P(Ti9r z(uzjJUXbbP56daj2SX2$3r-oqlxl!{Peo2Dl?0-@k|_D96wI@79vwU z>PG5>H-F)dKxDSQ^}U3h@V=FvW5cj-=3OmVc@4{iBkikve0Sq~245Zn+VGNVy_ibf ziY)Hs&u=Tg24ltUyFK%B0SF^8)~$@}x|>b{qw- zd1%I|#F+ixKT2~Tyr=I8v{t=t2)w)@3;S}i$RO*BypdN# zB__YKD25nXmK#mit=q0%l)_SBj;u|ouyL)T0jZ8=ELluDpF~7ZxedU?FP*w63}2q8 zrPW&tOBih}BcSBFu?Mc7Qd&OjC}k8Q=6BZ%h<+`x3nTd7UtWC8xyiP7CG>^nntlit zIAdq)o*`vpUm^+uO^yAbT5+Kj`lju!{k-Of&ULq|RUu&qEV)PX;!1}x#hjl73@4_B zST&@B<}ePajcu)FD8mR<$9$+zGC zq5{w>EFrSx^mRo^d0DWRrYF9X$xH1J!|%(HD3CtU4F+Q zF|KT$M-1;$fT-)56?<1&;djZAXl zkU5$BIoAMC{l0!CqtPc+P-NI>e>>US`P28w#!w@_-1!??uE#syhbWk`!0m6LN@6?C z!p2f=$~5iulb{)m)=))xp8?}bR>hVM7#Jg{lsO=tsINaI0lmnd>8JYzrN?7Aj>)!0 z(EUrAMM#t7s^>H#3=u^#O-M0!4x_c=7vUS*k&4^fot$C6e?4kVijp`}@WYkkD30yx z{`*$xh25UV_uL~7^Ivj23chc(tj%bcD2)v2b+?XR)y0pV9%p+^Yd8KjC@3 zovjfGm+H!Av^wBD#=5}we+`Kfa^Ru7kES{CJz=`H+u+^D*)zvSUs^{j| zZU8SrZs5jO%le!!-By&IV#xAD`JuQX9aG?J6wCV5!FH$Ua>=5SCu1mf|FGUGY;Igh zte)Ew-&g!~?Qo#WzzW(;mHQ69{B_Z`tFc8t{k+Lu3vN4x(@ri2+NpG)5sn|v9n=Xj zuw4qW!D`U2vb@26j5J?zQNsC+rDTbGmDegh)`oPTi-|eNEGDck1KaA6#L?;xUD5v*!q1IAw=$a4>GT$bpAG)47dJe zN4%rh_$;=1oRpIxBnN6a?iFUm5;((S-#Mc9Jx$&5?WRnv|0-)YY=gb*lo4bwcGETJc`4QjI~2l|8;<~uz2UH3~lbt;@hQ0G&)#$oBp~Fnv}ZM?IrXGn&#Abi*+{G zfJf&eS`Gy!Z$+GdirpS}A&qQf*b@airiJ;hnpRY+uIvVUDC?=#YqJFC-OoYl6}+{J zx=a|w>fVqIO@kwVrFFa}h^jX)X)8D)?@62|+tV}<{WIESRoum8xo)4)%7QbV4P|le z@NtC#0b`hJj}3@n4_xEvxuVqX7#}okIDqmLM2SBeq1aE~9MdZ~$UWd?`GM(Bn^{bJ z5|qt7cW^z)sUly`QcpqroAX(0?cu;RF5?bNxPIp9!iG)WpoT=#GKitagWh0DD_gnA zjBl12jElE^cvLE15WPyE=d3@#iMMWkvh)sJ^0n1r6(VNU#m;Pz8T;_7Ks6M^q_|MH ztyoAbS&I4V3!FjXGPU|zE_Upwiu-Q*eY=5a4NlIxSuz?hkZb#z{6@Y%1kdl|wjwO& zPniDHQV>9@DIeQVlZ>>GckRhF#odrA4zG3k{ubzi%A8F?T>^yNeYrJY>*q>0)+&Tb z#gL!;e+e7uRa7?z?0oN~&eb8LampV&pUgRrX$9CmmKPjhzf3nBI3Nlj-841$q34CL zMyQFJf!g*a8I%3|x80Oy;o1}2;}ANTc}_()HwfQUpiLZSn8|_Y`YfSF;DPPh)o!6n zG}L$V4{9dEk30BtpYara*XlSs{-QhMFa>#A;b@?Q;wZ`SEOxvwINTa97=mm4T(}*J ziX^z(9#|MzXo$-Fj>bO}0k#dm9%8_-i#LQx@^A#JEdG9*VU90m9R&#E9zFh>@nLvR zcIRYf|3AnSe;ow&c=HjhqT}ALHd^q?OOr`z{$R0Bt@rd`bSi29&?y)+A-T>w7Ndv* zt({m(J5ejBQUQB>$Q9{POW56FoT>*rpM+t|QvSTw+~C0Ct5%DTSG>)az6Wz8%!eoy z)|fnX&b@08WVLw`(oylVybtN5~ZW3bN!)ZG?a>OZyqX1_mByyy&`L zkm~oDzfV|gu{m8NREH@wx+E^_ctCk4k+QVIru6i5A?Sd;3xh6?W_){QQe$s~t@Pxu{nnmd+{s z0==hRll#;Lixx23N^Y-(fBjz10bz1GWU$7^D0;q@Z8tq^7k^t2ZsoU83OAB3zLFJP zoN*AcP!5E?*r5Wj(9Haql6er5$&BGIc*egzLU^VhK_wJLhRb-2RHUQ8j=<<6EDfMP zqYjMWVZ5kZP3D$Vfikk40$5SRq5SfaWEE1gsa7#)b;4T}5j^EFcoZe4Lj~4^trb_6 z^eE7GUIZIIn1kcMiv>3-9_FSDZS5uO(SHKvz4uQX(8$TAklnSqLFInC!Nd5{)zgRG%W}vpkeTv)~y`*}tHgdNIh6nvkH7qz0vj04=8??%ASd7?Kp1 zx6%%`Acb;jLX4~RtJZN`3>bC20ooV2ppPIcB=(bc&n+iqXa}4AeNtR_JA(x@_x#dv zZt&(47N_fe+z6OGv+jfkqg}1y)@-$`_|&giQ##Cx^m9iViX{;XU!&yu`({lM2S3Dt zLfkeyB?xP(R;ff=)FMJ(WFytMSDpHj*x34g$K6b%h1J$tB^D~UROGI7|fvz z8U}wLIp_3wG6_OGOy~z}azdxVxFGW%iVM%ea@>mEpV`b)e-Zvc?m$hV^*M6W`>;VB zfB3UndxP|?x;@uZbsreUc5@GwhscaW>OP0)JlL?*r%=?so*)DVua2#8uWq|n1KO;Q zV4Lqad|`}nLLt|V`}cp9t6r4`YGw}k-wCV)jf0PVkRt%MYCZmRv9jk}sMO%~YiEpg zRB%0|l`q|)b{xcjI1(+{jT)@ zq%XsP&`cDfK5;}lO233NyK6Sh{f@%^2LF|HOt&aX`_5E+uFc|>Uf61>54F&@J+O%W zU7|m=H@sYlre`~;?Xx^u0-$1y9wqu>(YRe}dC|vla7r0Gw)bc6pV7XZp=xF%*5{|9 zYBGKWH9OtLuNb&3V1wO0-GXsD!xg(MFm$hOI{k^{-eNNL1-mSxk~`#%oBHR+=b;~_ z@0bp=zjlaO?6x-9b2ZGN!Tq9`?BJ~ASU7} zzgT`wYTWnez)GGL^irwKnSZJ&vNb!{SSY9nTNib9$Qmyg2-bMrLgr zSKo5@!!M4Jh^e1OTs4MjSc}jDLILNiv?g&i_bj0?E>vgu5wFdRX4kJ&asJ#lbIOJW z&}{G^j<&@n58T@RP+%*6lt-&@w4wgO(O{3%vMA{@gOUDQWpaC-)SOGEqe+~^h&!)@WRIT=!2fel%E<1Ok7R76kiXTpwzKbC|q1|RIc`-Kx zI;0R%5($buck^SE-1!7;B3_Z<$Y!E;)w;dSWDy6iZ2FhNFENe^Yjk&&K}_s|R4IiT zm?~*-bK5Dew!_VT9J!X4GTNpKq4X1z>5W=on8o&-w0(3DSTk^=xC?CiW(b9qEgPez z3`yE~k;K^lEPyJ_d29d`&7=PtRB8GV)LZ?~tn|UBRby}w_vDsDeDd>KdoSg?mfiu# zb9JcYyL&;6NUuU3RG6lvrB}+)w1dy-pz?`Z9u1y7Kl`0Dg~iOsf=K--84yB0Gn6+K zy_nO_8#PQO3GBU^=;z0pem%-Pc4cI2#?hGn9vfMTDW(DtI942$tYmJbxDzF|SW$2n ze)B#p=ZI({L3yk>yy5EkTOeAE`FI`TAKF0w4m+!-zoirS%Ks}OxU<#$UT6CK(dmcY zeY2JMg>q>9SXj+~=O(*)kVOG#CD*8j*JQ;O*eWC_ki%{rZhU%o7A;`)8DReqY^dL^ zPljQBfi+~7yGO|2j~Y@Hs}Y}c=}_7nT5;WA z`Rtu{Hz(mOeWfVG)ox5{}4fjpox&&ikkb z=Eui-aVf8ux$2P>B~qDul-Ns`SX;`v^YT5Ho~tyB&O|zyB=c}1-`(>+znSn#_v*Bg zM;eNr&&J+e0>Q>6ib23)dTX52mBgdvOB%{P%24C9keF{pY?p;n z446SrdaW$O!kXmsMkyWMQ6+`{H0+dSI!8Z^dsl-!<{ai>DSI5ti1Cm5R<{Z>H)^U1U(f$k==17$ z@X3fdKEJeu=jlHlI=YyFs@q0c1+utOZKJ3P3|Rj61isBcH0Fic1s&mSALoGAD_McP z=>TEZ%M1M%7~0=MYAkBah9-NhYTqn6n}x{CVl#fK?>|$6lwD~{)lNYVyB3x}KFW&H zImh14j!r1QlV~9YlG`&NxvdqC5f4AL3{s)*pA>jlE)qhdrBkhKyiYIG-maE_7;B}S zkzk50R==Ndi8tmZ+R&CM$4moEAZxytcGs6$E+oNfS5Vr$=Z$8Hn6xEp?TPh{uRqR# zWKCImX0xXB{gnxj>aUc_G^RjvcmYGavZMxjf__agzwOrJoBlIVQ3saSFQCq^#GgaM zIGKu3M-pc)QlNhkD(&-+M?p!2xTGK5Y6+hjJw;>#rp&0q8cd zel_=Kdv$b>-`(q0#aNjEv?=(?aOjibeiN)}f&Pc5lK<7lGyWg!vMgs9MU8SW^8neh zBXOFYRKo~u0ZAoGcCt{0Ov;yD<;?KU|XDxC%qLh^+i`Y5*e^=NyQg>luC37r|4$qERf{(lo zJz>}Gq$hpDEuey{ivlT}{L~q2JCb3g%6tIH<>fzL z303!sE!@Ttkz5_|Fvu#ukmMZ>fs)envG0qAfL3FG(EP_(g>UGyot6DQ0_{#@XQZAX z_M|z_q600gb)p2y`v0Q1sN<9WnSVIz;)arsC!)A|ZZVDC^yo}rG>ROCOrZ<4v@e}q zLOSD48~R%&KLzdFo(xisI;M8P>oPvSd2kLh8UNI&^b_ov1FFH_=k|D*GIVSFEOD^O zz+gY7p~FaiqLd`7WoNb3Vx}`)8+_=|*+p;tLmBhW1f8i)yQpYspM26C2>Wn66^h}C z)+<^J##o#E(qIIN3Z*mpIu7#%di{x(gmo}_(`Z+m#M+IQ<;e?}Yl1S7o8E*QlL@&< z5VE{|fbqRb!W)eU=$hAbI_y2@<#^%@jfV=cT;<$4HJ6yOb~o zhs*X)zo+NhtH#zd<`?xwg?l?g?eQ+C&#XOKU3P_q4?>x@6u2ojZ_eHOXdo(z&Rdh9 z9ThOTf0P;j&TT{0rj^l19XHJYC|0rWv+Ql~>~gvBZ7XdXjiron1)y z5|zFrnUo}Ag09d(@H)A}PT|~}ku!!q+bPJp{AaG|&n1e^q1tSIX(u6h4DKPYLgeQbV8|Qm(hHgsb%^!Pk%XfSlHf_C z_L}$KD6dYYhRl@Odt~1$JqokXCNdI+*&^ib=xPQ;<_G8)G)9y9C)b%mX!ycVhh0=B zn{i02dG0G@UBScZ(_-D|katP)gJa#4OcguHPN({02NM?`42_q->Uolr6X8e`Qhl>#o^Y zA$5@EcHN5_EXCqTn{vt>3elQ;ih{?-^Yob1%)RTv2+fZI4mP;Vn8~fVEL0L5rh-GO zcbAV=H3?m$l7NnBE%$wMrEyrRu5ZbE`BcMCa4;#x0Ue{Vp{lCg)WdOo?UKSAH145P z55>6GY!6$@5}s)VBzy&U#OZh7`N}1t4juizm50$EX1Z7>;&c|4-y~13K2y{{>VLPN z!ALIire-m7hl%!?iH)ay7c{J#5Hb!>$#cmy4s2a#(QM~lX7loOavS;0)p>lUhhLa| z9{r`eFr7CuGfPF23N~x`Ec$Ha3sU1Tfph1EC0&UxE|-S{YoG-o0>qr08T}4LE&Dm&+OFoj1KSqf!P2*t1(qdbhN!LVB?w? z=|KvM>*-ZxzcLpI9nAK3pPh9n&dHG67geRh=yD^3+~?9Ba6chF<(Y|oDhC+^+iq#6 zZ15|IJ_ZLUZcZzePJ%j+wys=t)yf(#)#E}M?>8F+J%q^@M;mKO@!n7Ap1Se%pUm{N z>-WEPpAKCpGianA?a-rX4CX5dM~XCyd#Tl<6Q3c6E#%J_%jX#=Cl|OB1H*D826;N4 z)Hp+2(m!F^w)#1n3ap;Lpc5R(dsc7@d<5MJ=3kN<(@d{WO2GmqU_8drTIqwvGq;_Y zmAHC;&FXE}rj6EafBBr3@I)13uN!xevF9l_qG`aqyuhDD8rO8@^~n|Z+2YjoZFxV|AUjEKXK;B&|%Ke95UyY zB$p1wi(k;R@If5Eny&H@jZFJl*FOG=#W(0&mpSO2shtz$R%8&i$|bC#3Gwj!v6n7d z1S@wWQ%XU;5vGQjc*bLAa&Mk@tej^95vtxb(?t8?I9U6+SHh{P%y8R!Luv7x+T>>; ztWe*43^=R}2hDIQkO^fnrQu(r2BUFPlDP6%q%(>=8yg*!^q5U1uc{1l70@$4cF`*N z^Nlm3Y+Z%A3=Ft{{4-H#KS?8M31tuRg&tFiZ8K=tr*>`asM7^+CsmCz5NLrC88hSU zx{Hx!QX7qWJ-LzB*(G!fM^P5tULY`pO*fifn+xtU@qAz!HWclsMVx%l!kxmn|91Tj{?1daTtj$-lP z8EAa~NW-y7BR*~rSM7M*J$!Wl) zJy|5HndgCo=4@NVy5*WSM6_3+c-bNYp*dfy0cqyCk#P!ODI4XjSnNadr4Q|&L#$}b z6!A|Uo~-ika&>8v;d1cPUHT|VQh&MO;nLY82d%w966aJ23LzCJZHM#du6GP2XII-+%SN#858+HZRTwpeA6pMQ{09GB?(NKJtgt3V>vrQu1_G;= zq$BkhxnmT}unTogRpkI|4%EjVDCJM=go<7%sfB3NC-eFaYp(@$D8;3d3z7kZp{-ko z>jL2fIrGk+LC`|k&*w2s68+tyTSzf5zE6>gS4&TIM2MY;jYWEZ5uvrk;kT_MjAKnUTtEvN8<0WJIGf=^*_5SsR#sxLJXoyeY0 zpJR(>R?`HBy>{$8lNa+LB|CF7RX^9a(Z!ODz*!Iu-332`j(TYP`S0b2xa>9nMrz!1+4STYUCj zE!uOFk^d@O*+d66eE+l40BrXd8BUG}-q`1dUl?#Zv~BHI+PsS0B=tu>-I3S$X*Mxa zHJ-l9L7rnO+Z(bTl)e9CCy)n$PuY3*bN}yR&vEcX|w0Q+V}aU**bnpg{P}yC3sM_BjKr zv|ALg#hX@p_6Og2!*&K!cZqmm`4%v6BO3bV8V1wcA01{6w&3%G6|>3fhK5T)3g7B1 zKX?wfW6EkNuvLqrJ|+*)6V2BUnUbJkN3^I<)zOO9iQV%qarn1DfG)sFI44Cgf=75f)V|iBmB-oEAd<{GglKM(-&G18V z>-cf(SpA-9cN=QT4@~sXTUbhN)Km<{#rE0A1gXE0trN`&emjD%`VyqS3bSM-(Esiq zH}dQcgK_*%&~bt^?ZuRb*DsVLNqL6Z0GhxZBbln0$H^upmqS?SvT1PYQwipHZc%h& zv|CaBSu|5{4#P6T*X1A^ht^)jI{6p33quT`y}(r4Fg{*O{gP(D{kDv5Aw%8Qx8>)A zA)-~3p7*&--d1^cdfpr>xVwLQ6>~Gqx`%D8lSkB8riTZ0`1r$Kw%l10b6?YI0JLk_-Au~nq)LZcttoFvR zS9Ok4xzv6S)!t}Gb*h$Zp3Er_`oUCA$Wb%q`Phc3CZ<9b!0FHbWS9c8?e8N?0C)J@ zkrL*K?C`zYt#B?$QxIkCA5nQ%7OIM{uva(h9!y{Lvp3Di^{N~@6gx+lqb2;x4rn<# zoYkT;$4?!fcFvX|^FJ0=b9UU2j*^$%erGZVn}6y9$w|Zvo*CPH=Q(3z?QbOLin>uh z@2rviJwCGVmyC9GBYv?ZBZ==SnN(0kOR^X%h0wt4RO-c^tsP8N(%$6RkuOO_-%YQ1 zcgMvdqa2AzEcs=z!{EcN<7lnw*2^s6vBY-Sh5ZDswb1Zq$QYzLL1M3k&3LyH?GW z5io$MyDuTTD(KIHpVJLBTUn5cM8{p3m+H(O3;X8W|9Z8jy#a3!`}*P&XU~^z*`8^E zO&2G_2Nyn?7<#&{)ygUORtRxxveSP1fuW%o`uUQ^+-h?BPDrl3GW6w2bOD1kS%^*Z z$c_0lSKYeH(}|un?>wY~WG!riJ*!8GRyqX~y$7{E1KL2*9w2kQh?MsUk zv56iqRz9W4lM{E1dLKt*6S7@*=eC=~q^_Z}qc9nJp>g11=?!fYJSYe-nyKo5*Xlfu z#~&iO&oa}&-+_%IPBak%`XVhPtm&_P^qY};l%_Bs_nuUKnz@cb)b(oETl@D6fhpoi zgizI0x?F<59(TLd3PoZPQz@K|``s*(8T2)J4eo}PZYDt5F7)MDU^x=JewZPMTQBACh zjh|1QJ8O3IKR<2}X7z8KT$)*zGwGqI>KF>JS7c@de8=pE+D*q2Y2J*mJRVaSFTr22 ztB}$HN8Q?kGt~l~XSuw)+WI`!(X+zUZ^c}9SC;Hl_IlwwU7ubvlT?-p83T0zMw646nU9FGVP5OFQU*TFtqD@!6u(35${su*&`>o zP*k@jyR?LIe1+QdV9U|$ueZ4lDW|V;;v0apH(IYa(-?H*K@1rRLda(5fCtr=YBq82 zVQ16^6c0*2FmBAZ3F6c|UjH+qg?{;)0#uWhnmIq*{Cee%kF zJI_6Vyvh@L&idS>6l$LHKhNXx0svHkoW)}_q-6D6H2EINxT?fyw7Hlo|Ch zVu%Fwd|3$?gR<%Xct7eKdQIh=ZV~okftE?5-UDLc!r^D&T=YFE{&RGg$$1G0B}CU` zO!zTQjU~%<#|;R3SnP9bth7#*zOSV~fo>Q;@-izaTlO*b1Y}mGSEzb9BKSOBzOy73 ziY8Pp2eS2@ZZD$A_TjDo7E0h_E!zKs*UZ zvCN_FM?Zua(5;Z5EPm8g(`VeSVT^3AnrN(!afFby>0>pqqj`Wv^j&vx<-eu^{L2uqL zt^A5#YGnIwk|eE{aW^PYvbRj`f_U5S@2B2?xbBvvhN$Q(DL=HgHYM)dkT;k6-g$I0 zJvx&ru$#mjtWxNd`>+s^P5>rNNjuiYhuS#bL4V})6Ohu?j=+-YZD>^F(iR2sLPi(( z7!*8l#mjnzU^JXgM?mBp*vpIPs9JtM9Ho?QI3(v8VgQbskW=JWlRwgOL1MP2D*Xib zoo^E@OA=g*#6_Lj@N<`-$&9)rC~zbw!F~`1 zP4+`a_T820LvdtSahX&8fK#+z=t0l8CsHXuOGvq+8ePG;uw}XVnWcwWAM}6Qa|6cF zJt>03NFs0~j#EFmV-XC_C$_baDK=oQ!RA6HN0X_K*e;(T0(9cO}>akryt#I zA9C-q>@uZSprw^9kDKPKg`uwgW#;3M9q`sY(Kl`V@XhG-pO1b_DT7$gf{(IkyA71W z6g)j5R0OUa?lc5Gmm`E`hnoUA9NBlvl15=&iTj1MC$IE5f&2Ns%3j(Npx?cf7~yg{ z`|{0(rxVU70-U)>{ddD3KtDu$M;h8|I{?)9(ugWaOhnmy4mJ(4#jCWKl}7xVNr;0? z%1hI<4pkSkVyQfA#L}D9{4rrxY`#k8@x*#UU^W9jcxd^VsG27RjV7oY{nJI+!~HtC z8VC`c>C)V>X1(vy33W&;OTB_(_T`GG(l|*Et^LV|D!vcHWgO(bfAV|0m6-XNsTnF2 z;b<`Fn5_v@X4VGlY_-%7?3a{@K&-oCMVC z0EB%12#}ICdjF6SkO$G^&BkU>q7Th6-YuYmAs-^>Yi%F=iloK`OH^j0G-W&q($Ng{ z2>QY2x=Z<^5-{3&dvM?#_<~Mh7n0xGTzmz%t|f;F*o7VbI<(r1w~n3DJwujg+?Cth zFE`tMA}}q~5~(9D(4UTywEcO1l&S&YVAi%`ziHo*e!wC?%+;D@;v>hhu`4FG*`}5h z`yJfn9z|{wPJIwcvtv`8u!CjoVz>F6_sK`Y=%GCVTadXREJcz(JMFD1+NfuFz_)d- zj{=3l>=s{qbU6_Yo?oWRwUPl@(L!x%;u5Wy+x&S|5^z;eau$(mxi$e;#4kcsd1i2q zP0>wV+j7=*{5q{h<*ex-Jg6o>IO_H%grpIQl{$I$aFG-b0+ik_tx7FWpBiVzEvVx6 zs|I;Oo*!a3!t4v7@(PUng1F?v7!XXW+zDoISN<8qj{!_`&>mDW*$_dP_71RUte-O~ z5U_1f$INmgGoV0FNwH5K-ZrSCEGM1fI@6EaFZ&xNz9_b=cn5=4^Rd&i0oi2OJ<}DV zcW)VK1}eJ^iGQWxFaSr~@%?iT5#SzQEwf&Lc)d-k0(TRE55KTtFt@BdGCA8Vq0$`4Cfbh84i`r=pVJ zKNejWC)Hp&7=aunu=-P9iMQYQ$L|TN64T=Uq3DJM#1ZR$yO^gbuLtU6|2HK$KuPE6 zf2^;77lHHtlGtR}4S()g{@@dWk?fyzUAQcUmHy8K@YF$XUgD0<){f6$xZPh)7?XqF zKCnretM4*b3o>^MJxyM&l%PSUkgz6 zb8z6+plsu{ZwtCF<&8gx*v|6^z6&^rvSM>lJ)yq+*IG_8ngy2taCB5VSp&*>@Il60 zB0_`Oj+2A1#1*%d@vj%v@@W)#pdX+=W8@JrhBL-^^_4`mUi)jQZZCNx@*_G-`q17T z+L0p7%NJZ{@nJjm$-|S2Lrt(Sr*fcO7#A)$mUEMq_T3GCK+ery!`C|j?#1BJa(_G5 zb$W+pD%+%@=i$qNeVRI3T?XZx-PyH@x&ig6<4BFQw~w7N(zye~v$HN9!L1JJ=-xlx z-~zF3Mr+aRL}d|G->Uw8wc+tIM*V3ww@;_6LWKV6K6G-@CEYzFIGrv6v$6952Et%0 zF(9k0|0Rr_gx5N@UhaED))obzb1mv69iiN(i<3 z_JrJ0jIV~bH}<_Vx9iV#MDNPO_)1#)E^HK4i&w5YzT9Td0P|8kwAwa02Lr`_!PK-8 zm^p#y)d|ojO+*(Sa=fZ!tA+hmbeM5LhQc|^>$mJ;ek762o9<#`PYZ@;)sGLRGRFHg z;{aRICtI>1TGYZM*u1!qPfO99R-uomEeSR_4lNAdcW6rh#+SKuf#UCy~;Y4+i2f7hVc$~VeK%c!S#L%9A zKZo`!KOld+^ry$eTUlJ7O=1Vey?2OuKXPR?bDl5uj<0x;X*W=>Enfcw&cE^9P3#Kz z&_dcBJC3}3RsP1xMQ3 zkEvv=T_}o^P1-Cd!Y}M*BwYRc`C1WpLs&(aRq@pj-<6S))?JN_)cXK$>E#SzayJ3h z*a`S0$Tf#b@B5Z{{Fp0cW*${i0IPKKtN~e2XMmRQjuv~y;~E*7PoAf}81teRTre&b zF(;e^TJAsVARjqqYi? zgcCqZfyDX8`!A3y@qAZ_G#?r=4Z+8rU-vi}mpKcxg`f1(%Wi6O=G&X&aYY6G(7*6q z%6tcamUqPk&kKitXK8B<-_Q%ctrq0t-d-x}X{TVldps&&fsruz4Ee($^B_DE#p%6g zH( z)l=W0Z^;|`PF+1Z1QjLYe$vhBTdkv$nl(N5%HKSk*rGFxIP#eeJU2GLZVgK^`>do8P0vVxI2lvXc6YEe)7T+4iGw&KdgYhqFF0cPnbyC-hVwtW?L3J;2Alrw+E3~AK}w<&7@E#wmh z5Nn1_CuUsBL{2&se$C6UzSY-?i&j9^oNv=Nkz2{Il;RjdWHC0(0M9qsK)rwpJr7HH zjN+{L3L4n=DsYA`qiphFY}{n=`vmC0b0`Q%J_Shq-8X5iTmK`R2rhl`2m0*GoLz>Udn^ed={Rebq}1d4h6jntT!l;JRmT zMUa}szI@p=KAxR!Q`4-^d<1;J7DvBb@Q|ZXKeDd2{bnh}LIFbu2gMPInC|tow|zQX znZ9G7+_ui_KZ`eQJDZY#UiIm~yAR%6D~t21VJ2QAt1MY}!jL(mk*J<{xNhF31_xI0 zdz6~66r?5|R}BEEglDn!p|Yz}JbhZ={aTe{JB(MmjMp!Jc3-lw7rvbhBH$!)_86XW zeuXuq`sL5}P#2M)MDi3Wz+qIJ`zhcsHPauWheJ?~3t_-~YM^4;s?yNKO=>TY45FK( zl~;+V;0fMzatVS{eAVqDm06Ox$p%OvuE@Vuh#`z1cifJ`!8FV)#D;_tDLu>TZSSP)g6Ni(84;Q)PD2#~1- zuAd)+Sbe^5vS>nTxcig^dFoWnnv>d;tftYYjFwOBJ z!spx+HZ*PmKvQBG1&@5B9(0CZ8u$w=ktD!~;t@(oMrR+6CPAul^emMQcPA@p=??2P zhO!hYvns~|+U(C`ArIJb5g;(3#V0X5043KkBmJZO!S~og>HeXRT>8NX2LL|=IX_^3 zoG#vn(tWIkEatl>bT?fQ>bur(R!2)G%MZ`gvvjX~dFNfqSL2$C*~aa6o%; z%=%b;k3z&{To&5F%|^24#m#>_=(}h@Mp`WhoDODH3q4ed6T2#X40%WM`}O+562!M6 zjLjD1eafMW2feW~@%<_NLR(GRAqJp-8N~93 z%2fq4R|szWP50z^UI9>60>D;@`R4XNJN~t@{^|lIc#y?d7-ET;aQ?kbKA`D>8~xX0 z_K)<+f5e_KQeHsT=bxkeBj8W){>M@Me})>5wi6}|j#K}PFEvZTzm58@WLL!d7rH>w zEWyhyAXupb$}Iplgs$t>W)FMksn7eD_$FADVZ=Oow-{n7u|agH%vvm+ObQllp=;r! z;j~ks3(s^k#ZnhDH-0pVbyeBvAe&0lkW$_}`g<>8wa*7@bo>}A7A#z5M_c`UzXoB2 z{KCZv*}r6AxEb+0=f;sDKSEexVazi9xvG zUky~#bje%u&}a46%=1QbSQ*##D`|87lXoA@tS=n%FLf7OU5u2<(u_xK0>&TZR02~8 zqC`4Z_+R|{Crwt$4ARz1JiQ}oOiLMT#jdku{HOgV4>gX@PA(f){jc&aFNRyL3|hWY zjYmjaVVcI~{++8K5`E43VR}sOBJE0Q;@~J{z4eV$TX4&PuhGGZ|IoI~=9%NfA*D2B ztG{i@b?kuS=}P9&zRLL-t-XKptBV~@AXm8eVF?Bx0&Mye0bL}s8^aEjn9HATolpMn zE+2?gP@pj*4C}=WXCgeV5C4SAmEcDIb(r zv61Ashw@gLhr2!JiFrxY-+opS5d>wELTVeYof|zyD!Jm5d|RrmTP^fQtJMg3R3_E_ z+z@t*bkhc{wJ z$8+klw1H&Hw!*oNR02mVEX&tQ&p?o-y>alh&U)SODdpv<>A|^1(4&qnGD-rc7^hJD8UPQduhv(xKS&4N3cb{AZ4JM?aJse`m%+@;?dYP~G?__7;_OUg+qu$m|s8*5+3Cy77Xgd7dc^+c(^nR|xlTQ!bB11=ziKmAW zt;I(EzU`#ClEAdR4)?pJWB}Uz_MDqC8ol4TYIn8PPMjU<)JuX=$V^j~_9Sz=2v{$0 z`^*T~AM=XYo8m#c>bv&U;fT;(NBpE#cNw)S%14_7ET-)xE$t#grFE%nh8 z7&25nKh5*K#6dGofxDjcQ=l|7e0^x79fW@SY<+$i@9-QS9iDjjs-2IFRtv0>H(k@S z@lk@-tv3AG^7r>EnSH<-cm1^O1osB*BGB4>Kg?^`T+j5_xzJ_aTuC}Qj6+;c;v}4} z1)dK`x?F>*vw$H`<+IR^2kI-t&6_NR3mzo#6x9ltH=13XGhRzg5cb+@CkGk68E$>B zaw&R|<^qUUdbVCyuV)?|4oJN9D~kE^c9l>vTTdW@k$_+cN!)Oh<-k==)Py=B>3YyB z(%&lk2VDKN6XY8iY|?JOx!BTpDy`C(=jxd8`Z$fQJ&G|SL)xdej#0|9yH>{Ba63IC z4D>@odE>id&Ap*P8YSopNnoJVBX26OfTO2VHRH6|Z6)x2VvQ+vrjZq+ zP55-_o}>T8&iASUB`Iks-{>x%s7sy%hK!6BJ+FiJEsHN;%4v-Ze>WULf%6lfJMW=x z8<|^ek4z-BOI{{zmUFX9i?mWzG$BcBmd4tlj^Oh_X3fu-2*kcg|sX5d}}o$33LR zLmz2fXk=zyt!yM~4M^{fRbL)3*=te#E1`Kmbl3;D5!wHqgNR=!+gGLl@8{uQLQ|YoE0b5RWou}~wfW=E$z#)!7e-^A z%vmmvP`|oPOAqo5nadfFNtfpJb@%G-$OMK{C^hKs+H8h9_7EWe>{W*Er`r9WXA~3^ zWKswbuNLJ#LSGK&XuOu?`iqMyrXAv;FZ9y;J8GHPH&#mw8l`yNa;uZu)*3Gup4QqE z2g^HEK5h0^E)%QOCWMH`=StM3o93LP-}f7^-X5{Lnj7fPwUU$aWwF}rvC*`p4fN_a zrNboK2H@yN!G}-UMn-NQXL>GQVSAvDVb2Jlp71u82jrt>TAVmF%*2B>*rD7AeRsf} z+qJA1td*hO8UQ=X^uJ){^*h_PKH5#n@ZCE&IQY$eVY^aw!YFMuR{ujuOZ-nR;4h1w zdLJhJy?SSnQ34)ts$1VYzml8|&q9GJ_`BX~flRvuVO|*vv_3qZPG}f_?ONnos#22~>yV-SQaDlFE1O+lJd)7O8p5 zGRlW!vc#pC4F_};y4+w!v|bY*>R4SZ9CIhQ{#EUMKr!LNV=C%-6$yGF+@qO*_8>Xf-v_6P-5UD5%A=ZHn-z* zqHs&np?haPxb78*#0nFjC+@W2foTM~acbH_qMWsRpIm&2_So$a$dYH^E19@kJ7YK1 zj}RW5D7{l4a)HT4n6VcPKu1z`dhL#W$PMR2q`owX`-X?XOafLZyJ^p->Wx+l4BwsB z#Mmi=4UNR^rhfZGZIyfaO4EIk*p4kT57x(=CD;TM&GI){})b7KS3dLO>dtSi_;$5yh5MTi~ z`%ayiEY9OzE@7wlI6C*8`bFqDut_wC9R@MqBcwX_7-^tX+ew%04U_NySz-nqRGKTxb(w z-3bQQJoo$z5@$i!uVqVvHqXaG_iiFIMQDB(vjS9Y+iNYGYfqrPSa!H#a|YX8Ipof3 zx@IQ!WVH7o=1;&2>Ojtf;$vIrk`@!9$)&ZOeYI|bM2WMmiv}O6hGXIjIGs=|AKwNK zwP%XlL+K1%?|i*IrwAjKKKVub!2}yv5Tp6+Dg&EebOLYLvxa09xUT_OtJH?;LmW32 z=gu!|Zp#urN79s#i4xQMBoLB7ciho(-}K8CEF>jsJ4>N!0KD`EV{DA}fYx>N5j}8x zjV2SJ<>DsOD*ci+i6_F2h{SW-J5<7kO_KE_Yf;L0bZ1G$(;tY5^8lV(RDoP*IF8Q# zxsA=noEWe+nUdlN3oH|IWxo{?ARW^#Mf^Y?0yIYXtp^B_VyCPtvCb1qUNX?3TI{@- z>lAe;oGY_NX3=+N<0DT3Q+F&Z#pwb`jR#K@-+FpkFBCi2o=JBPmAAb^+_ld6?g4-@ z+Tq?q-D_wO1G#Oy4CNOQ=0#NZ#(%Y5=rK8cx7pbRj*qROPoG*ZN9jp(;Upw4e-VBC z6c_A5WQsx`~hlca(=fGff!NL)a1sw6Ti$@qE;b#-Xxk9*Y5m@Xet{#x-!#Ak*nSl=_5I%3d2R$nj`7IEztSh){W{U= zC1o(3NCL@m4jkD53S!-Qrfgq~)o87W714?x=6$FZvd#JV3I2m^ofliUI{hlRlwhp) z7S3V%vn%-LgFZ*U{YLJz0j^pYoTg`LIkWX*Zy-D)Fd4ufqs6N(-uPc9E+Q?(Mif@? zxfa?LT@0UtJCM&zCsh)c60R#Psq>19KQJa+O@AigbzM;ehZ9A82-~c+A3wYJW}sXa zimM`6rnKgCTh;;4Krqgao^v{;V9e*l(ed72O+fHn0dm2kU$5KsOnnbesnyXk-Ftol zrb?}6X#N%{*F9`3S#R12Ov!kwX>cxbU#esKnY@ZiRiaRFZ63^+QeU)rA) zbon4R?qk^kK2$Ovhnxvm>=aXs?v%OCRJKj;*#HUa2K)+(QM`x+--!x;MQ`IC z^u{@ELc?JK&1`vb)m)<26)yP(jz9{Ex)pimVItGu?0UYb;xj-Fx%7;=xH-1~JDZG( z^g9W&jbpt0bu_fe*u<&s9io1|Ra6&ca#@?-eAvIPP-oiAvfq5zH>IFZ*2?VltBZ=i zd1rZDK|v-vrohoCX6GeB=8B}p^IS)O<#3Z*FS2_4#q&*xjT76HhTepzn>9%=E*X=8 zEQE~J=;zDGQoat{Lj}8qzQJ^j36{gj%q4gcAzHK?MTUiys|pnKrN1~&M;M9qH1OJysWxhjerHM$*+sreuMn9DOnQso~nR zxH#l2U$|giYqhRz%np9aVE*RPSbx<VBXSF}2yp{+= zQFRmgK7lH%dV|b<4DBYIxu+%E3Uy0|(hX{c3U!WEf}b<52AjW0G#Mz=`FBRq?aFQ$ z>OGZvS*A*eUau>o+wE!1sV6~=BSe1xviux!-Q(!PcYeYd-+g|5eUT90x#AyQUG_P_ z8KiS#d_X6*x3E?wlLCSv#bmnyg}gmW0*ft$wdFS2%la)mqy{)j{={7wtTtNVFXw(l z2(oPO`dOkwT;i*iZxbXiGWs-&sz3w)9u=fIw;T#mTwjA8J-p3uP}Qi%BV5#eud;(_1C z@o_l&J^U?K+m1e6q#T0Xcj6n-l+MW3Pwm$UpCIirEx#067uQTjL}cz_P)Ead9}xNU zN5F4;&hU(r1#w>4A=L?}?(uw<&*V2Hws?)ye{t>{|Lz`Sxukk8wpK$Wr@Q)FwmDF0 z@qh`)YNDKdyJG0Nfm3%$F8s?9yqAIZ|<@3bkR? zj9ncPFSpz8{Pgg46s|haMSv9jA!93NaEY?K-+7oQ|9Wk?#hH&K^6DjMN$y3LKYtgW zAC2>ygG36p>F?Kvq7cSw%Gs3_Ws_^3R|WpdW5B}tUcGgB$}~)<<)Qwi5ssD|3Cc0| z^0p9=GvaE6kJb_T?9cp=;Vjm&-ddq|KJyt_<9oQW%v~prvlGKaR^4(+K`)hC4m~Xh zbaq^>E6{}>@)}Qs#i*~R1j)D#cS7_^eZm==SlC4AmE?05$Wkt1XhxKNoaGNHW$f2# z$j<$`@-oz8zmmn{Vtj?Xl^ATbt8JH1`YS5YFPwGln_u#x-%9PGFz-`v)Cu)spA1Ek zcf`fxJ!7mahO06oHMb-&9pkIVsDsu#hrN%c2*tvpLNchOkGevu>mkM84kB=*Dba69 zP#A9}Lb4;&<1P{JUEJgufv|D@>C@+BhGTAy>etRNi0M@tdrgt~AVP~*8i7rKJe6+oP%akU}%N6jym?~=|X9D?7u_GY8#3!6& zKa6F;{>7zqV&9lJ5dOP3BkTy%ei8^lhTkTUM1s#Z8TA zInyACA0ijnbF6`n9$g<&i7V9vC;QI+mecHjCJ}oHtG2+L>MF#7P&H^hF}f>#sH>Dd+76)8SOM|L ztOy#L*%Ga_o#@9JrAgZbX1G?MAT}lWDs@n^`p7Z&Sc|K54G|#VT?&q{WyRzADFRG9 z#=@QX%{2kUw>7@zuPe%caN9ccBho?_=diEPMy;yxWDb(^!ZhL@N@X$CF)RiF0!Xgb zZ+c$7%=5F@>+lAZ1OMNtxr`|+gk;A}axf^7~ywPTi+U(;bK zAAy}k8hlpr-^`(`o`9Xj46ARRit_C)=1{}9l8EocKnUO7*UXTZ_ew5eyuUOD| zJI%LeSq>==V@Iy9y0!uXq*^S479PWTHwB!?Oxj;r%`b+hoec*oDq zVvL@0eYpU}Yqoot!gNz>)HSz9Ij5eE9@v-gP=NrQ+3GxS@r{=0k$INi!49yOO!ksN z%;&O`o`v#VNsN>=4m4~(NyueFNZDSud4DHQ8v4jekvZHJo)3UvLn=HMcF^>y!;h2iem&on@~9%g9LcA_n#!wx#Bnd{~`dS-(L?-<&5^ z#^o%b3^sidTUDG7xL3J(iWxUkm=TyVwmMdGY>#^2x}kLnm_T__!bb<|1FaWme$xy6 zc+x|p_J5j+N;*|O2Gq6Eopx3&duYhX9^*Z}3mWBJM|#M1a+El_8RdWw9S$@_qoYpT zSE;!GXwM0ZL>1?Z%TyUWJme89P1}FzL)7dYeB>3caMIObRl|w!rUX-kPs_!OP=yk5&HhUBg0Cm`boKm0v4eH(^#tL`V{OqKa5q>@6^ z{I3pkfJ4JvOWW~N7}93W_wZk#cmwEz{)u z41GEYoDIub3tQPtVrc-A?S&?1PNU1WuH;;sz!5T=#O?}Yrq`wV(&4bTwBz^+qX?d|=G*3HN0EUE{SBN#m zYSQ1R7o__0?Df#d{LEOe*Xd9z0LB^Kz!3&A_E>(XD*)n(+H(fY@Yf&j;%Lfzx!?e3 z8KMUWCZLN2mW2!Lqhd0;JZ!|?jgvM7$*>e77$5imhli=BEnuTd$C;mGgr~0DkIMc& zU3w(k0qc^Ok%hSs&iy+|&-H};AERYv1N7RBe@0#zcL~r4=g|fOPA^zWJqSM?{5)i-(|ERS%ZR)2 ze8JF_W=im+K60qJ;o7+h(p~k`w24jQu>+mK*h~2|x9?sRuA6M>!b_n-0#MCwz z-&q#r{AaYx1K=vRDOMu65$yb7@w=|-`B_1}ey>}3juomq>RkOwT|yZ9E#zAWWCkdf zx~e|GP0S_u6Dt!($#FAZ=(3wwepLpbrN5k8?<;fIfPpAOfGOs)NV3#f}q1?lf?-YOt_4lAuWaS&}5a?Q?p- zJpDZQNpBxyYK3XI;U~8aPq_13d2y1wXGdcD%9X7H2RRGTUwXf%TnW^tKXzko&w(rA80@+2hO;8$;j_^>ID&Dsq5vAppaId#J=GUuP;Az{NehhV;X1q6_*n)j zn{lS+yrORDqpcYexV@ZBW z9#WdD4dV)W{M`i?M~D9uSLdT(0tqUNV;%~iB~4M+KY4Gl^j)wC8=BckBz@z5I#O#W zM7#`*+(ex@YdAiso#8_?{|?x!UrFR#|IEmj(_VRZ+U}GlL6gXOVP(8~Z@1iFLVs@P zrEKS?Okr&Hb#VXqD6Njhw=|GaUL#`x<5c1Urr0 z4LFA*+KidOQ3}t1T5}vnmHE#_Ah(mmkEw~vcu1vn994s4Ek&>jzvhQSBE~`jk*9!L z!qg>I{k-`TqLFsL-0ekskoq#2H+@S%UAu|ew1%k_uWFJep@r?_m7ot|zr8Oe>I`Sp z>|56z4Gu_mkJ;%mr)>qs4+9i&+>k?{tQsY9%*lNocZn6`ySWL zPJij6A=ghV7hE^CH9X`4EpRf<=$+=lME4FJfimml%O#sAf52IU$a2hGzlZqg!%jh0 z*Bs_5ff~ExnyV*N43<>2QmeL>PSU5Zzb6GPV*IWhxB=PaiurRxh~W`cV}Gy1dj`YL9wHh@!R$N;Rg*IM52FRLs$u__!)yKB)h_MYOX?}F?E^rm#Lw%jKuALa z40emL1(t+Ldq1U+bu&|wdNw+xAKa{L>hh)AGf zk4ESu+`Ze3Ke}3X#+Q^)Z+xfjQUPl|{#Y$+hO4|Fd}rdWj1E&?%(qYRMv3-~z5aVW zs~6fUHjwp~Z{ECC@r=KxL%o1<9`VY16)EZO5IuSm*3C8PDi^)c4d^QAcQ8@%y0*0k zHQ%+AI{ZCII8tdKGEr5ubQ%{^n{wFGqK>UMrEeMHDa|q#UFV2;4KDiazo<5cowr(s zpp`Jk?frQ^u-kETk4Rl~u7D3*27<6_wSQE2unX&7~!Doqo zsB&h|r1T8TvFJ$Dn>QnH4&~R!EH0*7YBE#1xn^0G22*oQcoO^5Sn?8+A9gg`=?|#= zI4~(Jhu0!H2PJgA+A(BiNOxMaboeDI6KiP9aZ7nUHQhVI5uT+&1^b^AOusxET@Ic+ zqN${#6d&|GrK65oU>2hnSTcMjlKfyV-L6L>d@W^*=r`SS`R(@zap-ixLKJbLXIkYi zS|*w=5x4dtinZi?kx_Ht+b6-&34upaAo}vyTzD{gVt$k`B5=uXqbogyLgZj{l3t6O zYx+RSFLCjNq0(znSkvLFsJm{rl_4JToP6)LMNdq{Md`si!(a!Ay>(6E3X&!heTwfH zexTgABFskp=bcFZq7m}Wba}A+b1sDgWZ%l6^>nuLadi#<@-U=6+1tM~@tS#Uvgh7nvl<~6zM5&z-Op8zsHv?EVV?}hHs&2i?r9A7 z1^@USl~X+HdUEg}ab|7RvxDI$bB@vd%NZW~*YO>_p-{`5JlDRRh7)`L*3WJCn5Ab3 z>n>+bp6(fJ#7g#yN?n-2+4v&fx@xOsbKzKPuQ@!^(Ib9o@N$OlifvT%DfsZITH5^2 z2}$iXcQYyu8*|;Bnp3q-5JAd9BZ*ZUZ(xSN_WZz59I6s~>rA-i?0!T}N$8ZvUc;>B zT(H#Qg7Mni9aUrc#0plncc(2+(+AdRD);CG;@`E11h$AEWr4!!Yp+}9w+I_(%)&90 z=0em*?7rxG%DHE|3hm&lA6w_c+6YUB)IrUIR7a0OPC{%lWs*6E@h_@BJx7H@F_c z?McXU53z1ut+p$S;=csBLA+Fs!`%FH?6cr0{D?J1Rj%z%oQt3IQXKMn`LZ65M-vh* z+EMd5bfG9b#`6(3bqXJlY2UA^T^ym`>llxp>x<5$I3XAvk6sEvjgJvXp5OF-(s1w9 z++pxueYhhi!W47d{Zfia40s~UjUFn*Yvm1P&t1>i2rYee^d75StcdMjxGviSTGh%)jq*B*aR>?v7wZNYSp!9j?g#oX zG=M=@rQeJ)zh$*c$V8#39Af%DJ4PA2uVr@njJvzEhh2YRlq~t=XK>b?#<1T6&fpTT ziRE#yX={6039NzMB>NnPUVMfUP%B0wtcM_`bo>eme|C;cH+;0v)upvA1*)bD!T6Sz z>%-S7e^T4Y;iT&|xyjr|P$N)!&juSc86-z+5nH{B!-l5+QGxR*j_}}=wAS7?Hw`x` z6(Oh#y9?#V`QiI2;8ej{L}U83364&Z)ELTADn4uIrI{KE&_`nnOf9)VAOBrC-3+HD zr>jREpQ|rAj1=O&W)yM!$-4cCM!FSQP-e-nqyk}N9(fe(^rd0&3vz77X91sgYdv^Y zN{GIcP3SIAFEa1@2I1-?6B$olPEPJQ+00LTNWS{IUOy+=%J6qXyz9Q(!FB_*`F{2m z*7Dv5iWKq7kB)Qx7YC28SPH4spzd)8gh;;Z@W{dY=^AtH1P_xmz}VUOZJH|%;;e{3 zKxOhZkZbmfk1mf5?d>J6?fYJyD506?lKakEy}gZk8M?ubu62*zi5B_yQ$bRg$axVo z+X4ex_zZe;~ZlDhg19uo9Ovut#PZNmLVs+w$(gelZsBa`q)m z?g(nDIzZD~w$O+SM)BGymF=)m2#!v7I4vlTVL=Gj%>hSv|-FAa#@MFTXH@{zm!*3-eH_+DiqHY#TzzpE^ zm*3qIKcAOx?|2`a7K{|+4XA6Jo{Wr)u%}W$%1md%lSCG))4ota)^c-|-U%SKrsnj} z^ZZ3Mk=0+s5x$<(^~l0yd1l`QZ82zxzZhOMTfu2v{>_pts-Z+j@m3iaR|txzX@|9$ z7}BJqTsAtr^Lm8djMm2;=_95U@P4wWc{vg^IxT=`al5odGkm7FT@VADfIs4mEq&zf zxF)B7bbe~>K+%i$CbflqYA^HVSNy~f7=}si-4L-QK|LWn%%BjTBI`*P_X|N6w z&zDa>M1|6X;~1y7D>N%XA5^Ky%VImm#2{)jAoI+nT>PkYDB(FQwXHMM;%`HWzc=c+QLeJ-iv z&M6zG^oXhC&sF^k^U^YAjIfPa(vavea^&=<`y%;)ZpjP-g`_#JRwmP2cimdE2#}7G zo!7rm_647eS~@nWvyS5pnVeB2AmdsaDnQ( z$t7dsE<~oZDB{K|cyYpQhgIh>9zUG~wxE!u)ZCxKG-4%l(JBN_s=+qyRBzXzX7rE< zkDPEC&L%hT+Koqnc2oxXZ65;HW6`hZQ83c^kKpTH%(uhJC4v+HI7#OZ3!94^ToBbweK1yYa%JK=Z{+5= zABBoZL0?5y3z%NNJxI;%d`XeoJ|Hn*t4(+a7%!Q`BX7xwuACQ;Z1%9`z+5sPdmy8E zO^T2a7M|KP>j?#PzNK*VZ`+^d89$Q_4+uH5r9CWfJY+;7KkHjl`NW~Xgsn=@UyY*y za3nj5A-WNmg-74`IMGOr3vHtkhLxei~IS+BD zwjoD`V|F7RgyO{D#nO)bkm1Vht+wo&I(0AKS0#9UnxhvM-&fPBwCek!Z8z+!zSWrX zR?0U0SJh}rJRm3e$xEyVbZAElefv7Hn`+0~veg|jZ}$3xp`N!T0uF~xFmcyq)p@>| zxF#R219hOV=Pj!($s=kvO{qSXcC>VSRWvEq+~l*#-Ruu+4ICX_NeS*MMmbyQD(>@2}tW`MEB2J)h2bUXRD~vG2EWCkITXJ^XiuDfQdP8@up=2tWJ6xu)vR zmEsuyA`Be0;;R5%pZjSuaLAVnxMg_~5cB=bEEPbddfG)h!5aQLU;AbPV>J3F>}mp; zn#aUYw{CuqR%NFyivpBv1}(CQzW`0~lRU_)H2fT+#+d+Urf30k3?TK*EiAT^?B`Mj zwM8_7c*>|*isX@Vg5%k=#N%)UTfzK{Q#l1)-C84IUeXd0OjB#u1u&^fa>2e|s^1V# zFjrlWo=BL+1~(aG7!}p8UVu(MaU6~)Rpx>~<17Q1U#_epl)ei#0X=zZq5BhbH{9%6GT8VwBPc=ME7Q7i65+yI2lM{~wI1{1?I} zq?dcy0|1+vx*nWqD%h?g+EPlA3s@tFgr$M#%41=T8{dRdI1=qU06JChE5~qPL$EvU zo~^0S<-01*Ne4@`h@X!}OZwk8Xdo(>R>#N?v2*0VxQNj0anXI_v}Au;;KZ$EyaEc? z)lwp`&P?4U`7v)F*J{z~GPKN9kH_ohQ@S|n$p-|R*ak*Xy*%SlnFxaC>9LJ2H}yoG zcS*vf8XdC6p6HST-5nq_*qHc?Hz5d-LBki3=5J|6$S zaj2Mh`3aIaqEO`#DQ--qsaO4g{Q)ON7plS$LglDy+i1D4Kb<&n#+M3O zuR$S38@~ov5aA+XFYu_njJOq;nAl5Rz z38I7buIHT)AzD*)W7I;?JnfzyXDXc^cgsZ~2~SfHnZf}rjGP)FVj<2loHr}{hEHCH z+#X{#0^N`2CI!!qI=M*Lvp+Y+*A#9pd{srEdHz_!6Lr6@y@)R}1XZsBT*HKg&MAc1>1ijJ8N&iU`tXzVnckiRAV z+fY%(^w`{0tOQaaS1(HndZ+3x$<8&=Cbj9Eth1hALtt1$D8AzPlM4CX6+)yr?DAj9 zkPTq#N&eT`a}$tX&;h^i7O!#u5o#Gg078hwb6q4@$a|hey=zfExsS4QZfLtC4<-|Jg1`KPz)!rtasp8QIPS#s;ww2Qla#5DsHyD z4mm}mDIPdAlk+IL7{B%_uR%IfSWkF8 zbDFn)jK{QW4@W%*%Jz5tr;+A&s5<<^Z&y(TJppEBNAJcH`WM5i5{fwq0De97u^IsM zhLzbv(!M}2b@pP*psmW3o6SZ_;jZWXhl=8@DFg~@zfRQ_N1*lbxki^|fRn7jO9)}E z5d=mH*LiUA4h5*!sNgN8adA_vO`<;To_o1<>ivV1q#!6|?wwqlfPU+CqxD~ove#j7 z6hgCLrN2BDhkE@@R!$rK8opKJ9+E^?iAx{D1s8?-Og!0JEx1^x>yv|L#NJubO)!I` zNt@Ovf3PYv0b$3E^*h`|40}yWm%CYtn0zm8W0akbZzrTkeF8T37s3R(7W@tR3V+*1 z_iUv2t00Oo#v(<(10dAj3Juyyyo>I@E50^1lks*hKmjY<>${?Sv4S zXW>zB5bj#1r7lm)Y zS3eq*hHFCi_5*2dY!^C@Fu+`)U!N6rDuDbq7#m1v?EM6pfJ)p+{Kl#)`t7y-t7a=jhGqL9 zTsFMY=YF_QwN*pi%o@czJj2sc98^56i`4TY^ozq|2)~6gIXObVH!jxfsJU>N;Uw92 z=B@$M2AyFvfn<@kIb9w347{@6P1L^koZ&Ngjuio?27r}*VX%Z}; z)cntVb^G0E%>`8!^|frQJ3x&-6$P+;!BdCNjXt2Vu_RW_T<=K%H`#M^tv?>gVV-xg ziu_c(G6b$S^43UOLeS(azz>L1OPB=YZeVIyf~03Js6xRiU;or+h`2I85BbhM6Y!eC z6Y6uK@`$8hl;MF>BIJtjX;>ynf`81bYD18tPH=z@mNPfRa;^jE zB3!OLA+^SHdRo7L4a`y8zYpzv)1I1656bJ7Tc!BG-h>8hG0<+@`B8jLS&1-Ud!I%E zV=)xEWnaK6q%QvA+fIBFHI^^%TGFit44xgqmw|S&_6YImTYGBRouBta%&eFA4zb`H zhG=%f(*DX@+?bt3)*mQ!F~fcs(K^s`GjPZ{kv*g2=W&^GM))xYrS{GJh`WJ?TZ#M7LS~Bp&V57a>mJ=W^dgOd&l^ z?%Jg%v(!mXwz;HEt=q-aprk!Y%^6bA!}Bc#n9oX~aLQI7LO_SsI94BK!>4w@MEbk;y`WZ%lhL(^27Z`X=}VW7^zj#DU-?wo2)Q6hx|91=3F&8$(hX|+G>Nj?F=;vAPx7uV~?V`ECDe0d%ck5<`3tv$004a$^+_+ z`6PuMAl@Vd%JTBsjw$ZHNHoxPqfXkN86RnyX`Oo>zBL;VsJF+W2bCpFK!F-+mnrZC zrMH0Pv8)YPr8$6&=u_jL0Nnr;fke>6mD#9Z;vc29ykE@E`!xl&g?U;Q20(C?Z_Scs z6qlvKDnE(o!PV0ko^Nwte1~YU=_VZkFcR>SX8^6#(Q`8E0194$ti&*`wE?*bEw(E- zF;~cCW6X}G3)j6Pj9-$#hc*KXUN(Ko_ocpw(2;9Xg{-(%mW%5I`@L3WTkZo4N5eOXb>b zL_D4I(I`RFU^RssAPe)jtHVW$l3rM$C9w2L?@Z|pse_%Iwa91S^Ej*uo5xFq8gn4( z)lRJn!Q}_bZeVgtwY#-z^A@laWV|!=d{iDrRY67;Xs=T06XvM-_RH8J>B7uLfu0#M zqUV!GYH*WQB~!q}o#s?5Hl2W-17^`$qT7`ze4p z0x&49684B(BW)tzx!|l?pdm@+;q3ECm7RH{qHRUv!1I_Hv3DCL{6|aPfV3D|W#1#B z7iG7^zVlhM!pOgN&y>_dZ z7S4+8{lPx|gXy+MX*nslzfI?~(eBccObB7pe+iH|=x&~aB$ypZESQ!tNp~`E>uOy8 zAU;>LZQjbouTq@Lb-v>JN7jY~;mT}3i6fBjgH%5iKlq456WYf*LiCMgxq*Sh2W_@B zjM?;vmd8P+q?y(S4xFQ6g957S#*1sOSY3(J(VI$DHRuba<_S8dO^STABI*88B8?Fb zai1;F*BeKET*cO)Qsbt)SqYm!CSn4UgoG5{y%rNbEHlK5YhdDE^*%nm;>b*_(5ZMEDUL+XYpv+V>923R3Lx@N)$ z&OZG7D&Z9p5nT>!>;%klz;|XBsh3|Ly9;XQ;3*!){iRY|o8ZGJ>lzTT0?!0K{5)q= z8KMNpzWN=BN*?>WUaXnSZZ&3lx&*QDrhG zqlfB>W=kClbvqQXu+B51&r4uQx>S73)G=T{#E<&x%-^X@x)arAzv+JrU18gt1Xyi6 z(W@cURXkCKB3li0-()VDXU0{h0h1-64v98FfRjC5@@;qG4#z5^i}7!%x2`X_^=?V9 zV^)$q9Na#=H&1J{i!oY`+z{=)W#!BH^TVX^PXGAGX9J>qz*h};#;5m=P-rouznrZR zmK1ox-LH2lio?#Uo2W5j4x1f^S9=wv zQ(D|~x=*}C%CM|?)@Bs`{;0#nIK58Z=yy?MJLPLkSnXRVpWfxssShCF-2SoMc7N*H z=4^1Sj&I*Q@V=J*zAv{T9jLtQ&W61rkX4~tsL%*%g+fVt)}KZIDi>jUSVL!aC8(lO z;EHl_0y}1c^sm3&H3t(+#vi$L-RZ;N%nLgY^tvHouv__w0^`-0jZMfKIE*=zWJXk){in2cNUJG;r50d!qr zIng2}j2TNXfY}wFOR^Jex>8ANPPU^|&VyWvs}ATKM)Sh2u6R+eS5fa*mk`cKxD<^&Mxn zEwLz#%h;XG{q1cC&sM|G(0aoE0Qqh}YT$n&Ou$o#dBkm3>4y$6Y+aoPe${&HDE#$_ zzF^^<*t^R#))FOM4eI5DvR%@-G1xK7s|nT7$Sh&3_CDQ+q=~6JUv@~Qp z&+}&lgaHuO8OWNVM;o9)ra5In)}si>scEAQldI(e^tmnaT?pHNAWbC?l1BGnP47r0;QeRj6J_VeK>qv{!3>mgKZg5 zL8ta293JVa{${#N7>p2rl~#2T@gya`KBikU@M#-oV@KM zeZh%Q=t)1GnBqqIdfF_r>8F_M^UgB_oEx|4yQY}URN~RA-oUem$hF^{y)xqESYL$~ zv2OO1Ak0|LBYFTrMth<5!sCe;gMO1LPhZ3S$rm%D)ee3FebpDT-rY7D&|Sb?o5&{( zr{Z4f;oE#u*6=hmm3n78Cb_LIPl-Y5eGk?axwoP0etou%-v~lp&(N@GF+Eo;c<_8( zt0x&XKG*BA)J3&&)Bobx{3a~9ObcVlc;<8X<3v1qP5wz7*yauIMvfTQK8vn3r6GK(~bKaJwpl}e;rtA7pX04nk<3 zOaECfgamx^r!o{!G*lonI0}+ZKgIiR6;g4ordSud?B>Sl^ngxR1~U`wX(9~)o*Cz) z5g1kQCtl~||1Thc8R9hVsp14QO&@?Hlwp5pOIJ?BmmFNH#Oha%U&%kyXJ2S0U0yLE_}&?NB{*t5WQ@SN}a>riYc|)st(OYZoG=nemmq10HnP z%$Ac?t9{BoJsky;;deT`RmrrrN?|LbHFug&z?=JY@W=^e44Op#+Gq*6A)hRYY#PmM zJh+(<`)KzzOIcI0(ihl`!Kn|X(5IkR#+*Y~Cmj)u95Ju2JbiQ`w8Uee?ITWey0k=Re-YmoP8EA+G>0&*2yv_*t4@31 zfUYoO)^^(W-AQSyn{Xz=QFquh>?0I)$!W2zhWgM`7DjASu$Wd)7cAT<)pH61??Jqx zIZdw}>GI}rm^1Mml081eIH!sgvFJd<(CG1?kKe82({j^UL(_eS6J>l7t4=b%OmA12 znuO-bsD+zKn$R1O2o|@Cg^N*vl5BsVy1uSmEl?y(1HAn^fKE3;Ag@^^)@IbA=jE9~ zegBB}(6@H+51djGuT@f7?Nl~&;a$;l^zcWv7HTwOLY0lFf21mi0NEG;y~YsQ;(sK2 zM-%Jth3BQr0ZCr4WRjmX$e=}HD{zLd$j8j%w}=psE)l)9CpgvJ&2AU5jEX}SPtTJ7+m55=-VS>>xhY{Ml_Bq$^j`g)z%wqktpVr zo)LS>)CEgA*XQ#R<6x}v66U-116%P0u6YXMnkbq(-$5;Bj9y(8;>H{uef$L*F6+j| zOzAUI#^2Z6Mr@sU>p%|`<=A);RO(hS{X8i&%TeO9kp_T|0MYlV=jan1 zzQS;{ep`*6t|qf?Ri0(xfK{PfjI3l~W-VMf&o$HjiQvBxK_mCEcc`C=k7dam@pK2)I(gRc`se zl)u+bz3({s6`CDCVHHVI#dE$?4sbZ8+)7fn>@xun5;0K{j|=5>`^T109XqiAoyePP z!OD4G?G8P1GRD)5qT{Vqs1+gwFm-^eq(@>)@`f)v39fwJ@%IH%IavshrRHVwAt{{c z=@A>KxQ_XhPcMQ}&w^i?OLY3Wc+Fte z$WjIB0SH=@3>sJ=^K#6gXqqfi+%ALlh_t`ag&P9s4I1QG0-Y5QAofssW(SC?w5|(z z)bHD8>W#5xWVMK)@S&oZcQhbW!ND>1#=V`y6c+DpO(m|IsMM<(l!zkhT=${XOxs;U zLx7p4Yz#8su`WQ6b;;d{smNkv#EzLA=?aJJ8d?i#dTo5LRe`eA-#0HLdspUt2e1qE zKJF#_KQP+3(~5 zbn+VEy%?koZezYf`-Vq(FYygZW9Dm?(R2v~x$7ypwRsM>A7$`FpTPkNdcCmaa9Y^Z zRhThZmHpK2avf-7AL9OK4A8raJB{CF9|Bp3zHrutfys`fP==H$FVB6 zQ3Z^67mVmTEt9at=N$Q*0hKLC8>KqHpFo4<$rsT{B7{k(vyo{u?|Y6-b2{=;_0K>G z14CnIRUazg(8!7>%UOM{GE3<@SKHznUrw8ftO6tERnlX<_0Cw_w|r|7c=X%08$NMY z`iy7Ij_x#$yI3(H&EK}BdRj1P984VbGCqp~u&(E3XMkUMlWLluoFL|Vp*CP=>4YN_ z$*vGDOF?-q9^ZM-cN0@h+3>K7Ef6nkq9h_%YOQ!j+eW&RmVk%0ZK4VqruT-VZ01?? z1#EJh>nW3?mKnC{(r`1P67@DO$%F+Q9YBYMg?a-e?11bLoQ2C}*14-|A_5jFbTS7ZHD^3lwS@x04viGOjJ}G;qGVya+wW zg6^xEg`V!NaIAXA(8Ai~k9J&6w@y!XN06Z-Q6h|_=X#f>wi(QTD^vCazcNixAe@QGMWW{*PjX#-Ei}Ezs<* zq53=^1 z5S0FRJmBxg8G-D^GT4Xzx{@zy>v7OEBs)7YR{&*DOuhlZO`mQ0LxCsK-2^hz{W(t1 zE&`AcmFS-f_Vd6dobDeGxZeXP$8+G&_UFmTfRlSs+U;)5M?`0_02>0L`cL@@k&u6i zIaZ1OKKb4qE1$XTtp$qv>l#%ZJ641K9y$KaP8#17=JVA-x2^IG5Rm~Qu?de;&5jnh z@u7zzF>)W&awfOWPTs{m7BqZZ7%zh(O;#*Y9H)4L(6~g295$DlE@P9u&xIX) zD7n;3lYm-Q1!)7cTp*TuxgtouT=+zbYWSm!z_}frqLX?%%rhY22D{L&1)aJd2QKNO z0d`z2u7@(ObLdla<3Is2(lyZL3- z!O4|x1PM1h*$6ibpa17l?0@$%lsxfF5q59$wFeMIHT~24s@G!Aitz{PS3N7#|5h2I zw%9RQ1BitY#4_fk{zSKkpKg2pFxb<%0sl6HS2pQCHFbuH7Fod|rU~J-gG;`-wI3c+ zhZo0{bn=NjGk>F9Fz3c_=*WBcTJrv1>%n>&W|eCYaMG1l$A&@KM5Zrq-34}5+~x&+ z{hP0MPJ@C|rwVmxZK@FIg3l0bMZ^1@IAvT>ksFf<8VnrDY zB0vt>8_J;u%V18UrrOl1W@K#9NThFlYybZ9){NCqPlZE@jdMz@{$*H5Cx2QW70zg-=aB))bFwjAwe|91 z;z8V_qw~_f4JA`wQquQ!r8TBHC0~06oy|LI_rJ!xMbq?x{9&3l&#Y?LRGrx-E4@eqZgugjxS7kDKaUhwD`cJ+S|Vd2HCZxs3Mgol3Ep_o~z#TJNzQhInXJD36p1-R-o;9m?2nmAgQ;e|lN5 z<1V*6D72}n`L9P;+#yZ8`J~6eT|sLFhpBN8 zcoFy^=s(?FEDFeV>^0e+NyV*8RbKyu2^(!22};>9a3cnZco8 zW=LA-B>5ytXsXa7;9stxqvOISQ39d9=0}yz?dD70Wu^>HNxxdap@n|ThErFuH1bV# zbC%yI{^e^Ldj+cxzI_*6_r*8f4`o=GUrb6e;B{hGIeu_`qt7MG1D0pI@n(bC`?qTS zpXK!Yrv(1sWI=w6Wn7>s?z!rEU?UQR zcgoYU@)b5@_6?A6xGtB^(qYoaR^_}1BPF+-Vo9nVanCbT$Q&G6NK#*12~*F(AayM@kh!n@C9-q>CJKC)UI z!yxup2uo#g^~@cLh~1muYRt|PAtH{uYk)F#tKS?$9t4U%8IKy;$b$1SIrtGouyLm?}{(vHg*SaK>-t6A|jGAmv0#iB>Sz4qoc(>w;x7Yt@FICyAwlvMP!nVemkm>i6!v^z{8Am%6&k z*D@p?PjMDKj12)&6;LofG>;S0&gQ$`pH7F371Gm@NC0hWwHjG^&>5&#$4t)FYFNiO z=~zv~jj_C6iF;=dt^?H|!y}lztM0CT%gOj0$XasyH(zXE_^DV*Kwg%u7_VyZx8j3Me11#8j@g=|6YYzf)iTwjE%+*YND{BcsQRf@|;j-&{FQzhTuL zWUrNLjW3jp&8RGf+^^r}?;efI+i3JHjgJzXNc>$CPcDxX>)hX3?ewFU=AihAvueBk z008goghF?&d_;bgLYDInKliZZXy z{sHIvwjQ&fqpcDF%$;4=*R_R_W40O~%Rp`&#S*!S4pD*HW>5P}zW1FEMDN$~TCO^W z_>X$EJ+jGVw#kW$70g*ma5D&BHVaynX`iIp!iMo|h0&5(mgE_SWi; zk1+a$OU$$i^Y(vS)9FH95u@t39g{+Z`7v#NL1Z#A^w+xu%P#R?EH_;D)|#ybFX#J_ zU|J;AkIEKXrrv0dd+}eD&fK?&@zzfQYtrseWj5XqZhgTwP2?KC4ggk@}Tj?7+)Ob=|?$p;pebD~+ z&*JEzfbsh<-yM$BYw-g@gq)G>n@CG<(*3O|fUugtBT0B`5Ee67~*HZho4o0saRDZsDH;4bk&r z4#I%{|Lcb@&x|m-fgjT@k0BuYXJz4^Yu$%#?}HU8eQ<`BX|KYKCJR#&JQ8cUD@zPC zE2PKz(>!`T$E66tKcdSK*W$7Rze&_@58Tpv`cm7&v#z53aHogmw~@GS^IVBYCStgc z?}ybPEMRxPCnBn+>n)sg8_fSfQXTau4dP<_!(Dr|ILncM+d%w8K<)2jah; z6pMT-4F&hk4%8dx`FQRyWg^znHrOpM;>DZ1Pro`+wf@Vo#hq}mq~)G?_n#WP0_6DA{Ln=^|1PqWgzC9m%T!wic9I=-b__0(9KRQE z&~10$x+*cc!P(Q20h}Sui4i5)vDMUBA3!$Y?DjIgpN?q$%ve8uTm1+29=(!lFaDGn z!zMH&+8a|1Mx}Ny2;lBMD4yH8bDCK&@zf%6v1N~z8^aobK0(UwknAL797e-5!9S!N zQa{GYe=q>ocL;gwWGOT}_Ed6AV15>P+>!~paWA8NZg^{Rd%P;#C^N7~R`l`nLFo>& ziov)blkRwbx~zx{wZ-43MR9Qy{PTaY%tv*-uLLk!ZZ$UGH-N9)z_OTBY(swqygcst#|I85WS|W&|#p^EWryhG1y6$GAu!e=b{gfx0B>?4a7nR@z25} zo6dR1fsL9erW_)8$LTuuIrW&mwc0|dhKjbu>7}z2aELG0zWGu=fciJ0Pr4iS+R%D7 zm?r(OKi(3KSMgnN|0Z86%lONev8HXjGUv8Z_KO~{u`kid6F0Vzz`xkINN*W!_nJ@W z^lei+TPMs(f>g2!#Fq3wqol8_4L{)3fFyYJ&$ErIhZLrQZt^bs3bH|&l4eImNfV}- z@xI>!80v;%iL8g zkA}F|JgBL)ekChr;M&kzUQ7=J$s>hyUC$Wp+gm@@Z0b1hP=zMMos{}EU6yI`IsM*; ze5nQ}bacZ@Ab_)(^9eARwG}HBxbcP|Qss*5R%hx@^F(a)$2^6BAqCvW#26IIw$ScC zuXU=&kf5fuT*OhlZdLC`mr~({HG|GV>No&Ybj_rsL~LJ$8LX8=Y=Xc-%fHb4BAKSN z22XmXz?GU6pSzjg&mL<=rC45r_$SWf#$-W#0}riK6JHWV(B{KMg{!y)u~%Bo@f@x3 zY&O;(>0Qu$!&AJ<4e-1S^P6p(sI9+wxRjuQN~{-Zt@384a$)3=z~IU_J+cLo*MIQ) zh5s!gIJ`}$77>t=5&sno2y>O%?>Sj^`Hd?K7-J(WB(;pyrWPZ`b)i9x9vo2)5_90> z13|z{oRK#==n6&}(j^PBp{ zcOgKhI_HNn&bWTN(B}vB3D|w!?}*|OJ!N-ZI3oUmpawXX*W@DXK1IPHhp&5N*vHp$ z22doVWNE=a9CIRF7lAZ}t{*?;*{aC+-ClAQTPS+IGIOJr0x){0bBP&velVw*A)@c+ zkl_XaaNjV&-_jp))O%j)_Az2#g-))K{`}dN_ayd&E@WauHWT(|Ei;o>6A)99~JJ63l=uhs&r&kupdfIU; zu)C%ekoYYtdM-q)*&}~eKvBl&p?FR5g0k-=1)j@4SRy%OLnqtTyVCBndYrPnD|%6I zv40bihcDT)dmZ#e)ckQQ*2og-;c;g=8wZ@Le)B{w&16tCPQ0^yuq6hAQAyGRCc69> zd3w4-1yv|gIP3~+;KONz@9DLL{IiDcflweuU_ZIsV}; zq*%vVj*=VmvM#h;n;cZSpI#q+4`}64m3Y*S1*Ek9gf0ga zE+SgC+CpC*R`}5ZeM?o_@2AuZ^LgOa0|G%Qf980r6qx%@ZP2 zUw-3#D7Ok?N@roVD-7rn-%61SG&&lY6AZ{eUl7Y^y9==%c2s3*5Z1@8Z;4_w!#gSs zcF2LuFpNm;-kAm5^fauzC*E`xF8s>{7Tt|-+~L`-rMM)xI@~%dcZ!R9{T3I-S$RVwgnmy; z_pW>YJ$HuR0` z{Yl)CvL07!<^P#samMTD{m`$~t7iGiR*~w}tp#bcF0q?l8^6Ufl)irJ{(a|G-uEbV z@vK%E!-~(JLGR*BD8~DFF+Od*Z(NDvc~7L&>@%g*tW6sd=K&UE989Ph{WaGkZTNmN z=8n#ZS4%oKCPl8BZ>lqucKuPYred(Nfndt`-HnFy>W?GV%wk)l2~=^VACT)7`}EVO z)Xbf|7MIwrBV#Loez^|#;1OPXQCx|k=afk)-|Y)kEmnDMW?fNJey#P((QWqzm7|?T zYVWD1aYW*GU$|&RLUyd>;=-A&#>tJ8a6Q6l3`Y5^ZeOK^=%MR#040JGy{4esb&6P_ zDUlJY>{?Ut&PcU^g9VI?rqs=XU08S2bBy_lAkYt&j-}*n6AvP$LS+HR%ipWN_W}UK zxTn-;7*h(aY2i4a913>jgh?95=s8p>{LB}2wCBSjR-k`P9=nGuQ*LYwRq zGuawbLK$0@u?#cwy?cM2@ALaTzvFn0vf)YGTg>g zNKjG`1Of?}n_aR4fdug&5Z~*)JiwKqd5k0QOV-QG&Kd;5D1tz^J0K7fxPz2R8mp#l#0sPvuDrA$tj47OB_0MNJvOx|NbK) z2aX*Ql{>1Sd`MICyt?$cW7_f(ik6nPV#nm9q)r6~hhZ==85vn|aq)I`NN?`|dDlE? zEtsrjbV|wtGc#)!7dIoL%K-twSFd`drascwH#&DtD={%SEDSAuTowY+2ZMEyNax$P z?_R!a?&fw)N?AwVDGQB`Ibn1AIMhc`&tBd>SxMRWu*D@=lbeU7jwxCMNE#VPD4dj$ zIVU6}bnZ@^xV&6w=q=leasiH~?)oa9S3a73Lsi+|TOETw?S1taSW7i5wWymX zt!+DaW$US1os0G>ex0w-D$EfkZ{=VYC9e?GD1BLD|1&o&j3;}DZsH;QN)z|iYHTn+?f|G#Z? zAYk=>4f5Un?rq>~fXpu$A#RN?u#&xgj$WHx%m5ihx7pC{Yws~P-}^}JbV?S%$@#)C z?H1_@H6nWPmZ#E!c+l%bH5ifP~DV@}w#07c_As)1O$!il#p>;2-*SK@uA8FgV8vy)Z zsIClMZqRihj%bCehGr!9y`V?^2|kPtZSHS-D==t;u-bI6ygt>>3W?G-eAkF94UXzX$$X_0kFNB_Aj z1^?y$(}U3jtOvN$XRqNQ%dL<TDS_JLv4K8EqnE#>hWWf!ywWI}i_vKKxyDiq&U(f}l5ayG4z$@o(LpEr1KHL-!jYN< z>#9LLcN1jKzqxVah7s`IGg_*6;mZ()@pcg-HxVUa=N2P18!#mUmZYwSHGbLa$O^rd zQm~LquPcz(c3V8<8n7_=hJ2D2k!g{01fDOwVkkC4zMq<(b0)E@3pZQ(=b;{@@S6!{UbOT&f7Cfg1Fu$ErQ+65=~ zzhAPh;F7RbYdTPPe(ZM260^#+_3#G6!s`z`YAl~_3I z6LgnE8w=5Di2S%rH5#KUh<5BDCzwErAzt-8YpxfetVNoA4KUUEs{-{gj!o`Rs+0#X zy>a2&@tAXhx91yIX`|}sR~z*uD;G8sBoQDV4PAW0?qeb8`p-pImVbBqPpQE>F@aq5 zeqmQeWW>YoFa!wIEI1|U_$U59-j3-4T*#23BJ;>4BfH|oA?L3>_GPbe$|*L7NE80Y zzP-l#@RHRkM|y<`1u8sr1_og&V}4Hv+(V`v;c)m@(aL!C51C{#vjt0V$2vcc zgSBnBtxTz)jL^_IFa7#`aDc*(x9Eyq7{p-&B*7;Skbr?+>Z zPn1$Prz4$mbN-{!uVXq1_<$s>RPBx-_a~62yWcyS2WH(pcJx7bLsLab zbxvk7`v{y38he7)Yy)LQYPiJ_GXx19%o3V5xKiT@yKw)@+4*G$MP4%PoNHy3-4IuO zXp$1~qXG}z=)fqBPKbnPz)!E{pOS`)Fi^2MX+h?s`}84h)JPWQ{LPHS0Vl>x^%#tQ zAyl+bZv--+4SNW&KNP5e-@S;(__zn-+pb4ie@;%+EHt^R`cP z?xp9s1)Qx}B4VtlM-h`o&dA{9>gHyL4nFdv|3Yb~$))W_aVOK<7RQbso8rE(!tvrl zMj(%;=4kjqt?L((dt@!Es@#gtFlK^XfYkw>Y7Kn`=8h%@Yxp2?4nZQFvC?pcL$OZrlSeLN z8kSd_HxqeS-N98Ey8vj^R)u_+?lxY*DtB z0IB=@O%|Ok>iDUVRK9!_OaYTM9d*cD==bKd&!?8)&9r&S5Et^e{1JFdSnT;9__aM` zR7t^^SG^DcRM~GF=@9+dcK8{5+#lC=Mre%~9NV~RiqSww!8sn=NQGfT^+#(wV{tWf zbB_AtWeqL{t-IfVusfL(e7`-LjPD_R(KqchKbnGoQZ`>!RZ5^$=S*PzSYM&zBL8kH9a3p63qF4V?1yC^V^)_Cq`hM7RDf7Q9v5O-w>n9gX%Fu6yz+ zN$;^Mf-%=itU+LL2`VIbqxE9h`R|XKn)sOqNL^#cV(a&69 zS771M9h>p&5)ddw`!qh{<_C8^(IlgKM1e<;2W2D5m}+tXVM%#xVGVW@{XN&s^~ORi z#85a8^&F`@_tm?;(oad`cgg{W6>p!QlXV~9GVdcV3{tXhJ^THF9#tM#T6p*E@hl+5+-zWJvGl!Cy}Tf@ya}{ z-J=$cHOENy0t~m6q{3q_X6G-dR1ercLi$~f9l9T>xmWXaqg>Crbj7nn*GK1Pcu<2y z-}JXhxN++EILW~zwZxlZ>xCeuP^cE$hmXO1F1o!IT{?0T*a^-A4YQ1ig(IbZ=VaoT zrV(VaOxgG!?5|#pvOG5pE=mYva+&?kQNu@xt3KE@$>(MSOsz4q(VK4Dzq=zw6q#lk(N0hOWkk6z)%-0F(!8xn6z!}2Li>Vl52lKKf14&3hDe+MGw?_T z&Dj3OZ$!q2;8E!q={@-ox=-}?At`tI8>#{*T=ibRQmF7wI>B`;i>gGT5BUzLJ#Ch; z3x4zFJU(x5<)JbH)HpTEMX`TbHvi<3*TkT!vKF$%ICF`l>^`SFRy_BOzIE+##~HCu zjsB1FqL27A1SwYOT`6gmW73J`4KSJO{`6H0iKG#Y4}x0LZ+&>F(Sng9u|mQem5r%S zX|??5j09n;Rzki2!Pn@{^t)2aKjG)x-1_ch)3x*)XP!JmL#3ZoRuUx^^E6o9O0o8F z)jFN0s-H>c@|5RqvAV?wj(mutT@lLsbLQC%moC~H1;>ZZ{D_GDc0Hau6~IqszviCD z$?r>_;>6kuNb)1}{4Ms_gL$&&GMR-`pErDrRsp}S=6j^3T$rsrRmk{~E?k>I!pRxz0#csrWP=q9|>dzSyO7<7Eq}{FY{b$bp>4n1v?> zS2U{=$jD;r=-0Di!)G{MV^8E`EAm8pXuZ$G$C^BC)%I%TrSmCb-VkgW#`# zkdPBN6>5F_lLu`ixVPDqSY=gN7D%}uX8jYX>Z%#g>`uQrZViK>l&V(4z4t91ol_sv z(0dZ@Uy`A8e`!p9W0k*$mmKSAPVn%Cy)V%4icd@T;!jPAD&s-LfYfx?c26R#a7&~T z*p&b>g0DJPVS(r`NfVdOq`ubq^L~{J{D6n#HJ~mA4<1ZDr$RE41}jRavSFpaarCaW zD=(5hOyii7p#sbOQgDVDOC)@Kx0PmNZ@fmc+*FsIPas8Cve_~*4^{MtM*Y0SZAUw5 zI@IzNKQ?^gCWX>Qxau2k20f$H^N@mvmgYK2liMFb+AIAW!Xom0v+N;1yNA4KL#+)* zHyksUkB8`O{Fci0{u6u1$@;zi8##$(0>oWu(xX*khP%A^3A~itcBVnM#c=VC0U>vl zG-qUosVpww69)-`Pve&cHO(yi)E$u}NIN&h;9oX_(p^!z*3kMrWfcmwD`)k^_4O){ zPc;K`FZ>>d3^xk|i-}e3Drj!!Ho0nYfo&c@_WAW@m5hEcigOjiuYIb3bgAntCb4%3rIN541~gA?62^ z_w6X-ZSr{Q4MJrQNd|;j11;eQace>*54{~}@I(jCPGgcvel2@*ld3IQC1g?CPLq{( zKYkX6oulE?$%Qr)h3kk55-6#qrGflpVk}%_Sm`{>*)^W{+=>uLMSf`NE44-HN4`rG zl?fpv!OSso6a=)uEy2Y;c(*&eOc}pao^A*aY`jETsC?*>uis5BwcXubox-8`ue*J* z7Y9A|stzdkwdOw=eYGJ&66^s(OwaXD)J5T~K~HlJ!wak=goBq6u!1iSaP%@^T93>r zQFtcP1q^doQb1ts#^zoX8l1d&T2vS%oOxU!n|PowOu&&B(Nrrw{^9HDLBq4dh3nS= z2yrTR*dfM?TrEgQv+5F+-2Qc}gJxs0va`H&Do)=3_2o|U1I0LLZLAv6h!ti-f9JhhQD+{zB3=9_7|HO|f*c#`56;+tO?2vU_tP4q#WBl(RO#TaCr5i1@*+4Zk_cyc{p%a8%saR|fY5~jeyjDh zkRjrSp4A>Qvox7$JZt3kK)SvC>P0sIuV&=Fq^%qE_cw8T^ykUTV{WxxgjckklHE9? zW)YO{)@DBs{j)H~&73bUsq(?&Yr3J>toKFJ1X9pRH*0(m~gnap;$Vl z!o=GwnCSUI(blK41-QT?{C|-Q0`n}+`BlJb(Q){u5_r{P)7;K9wJc57r9qAdrO{6I z6hP~8)vqSCOOIr*AGvK#%FxY}lZ?6x%ON@A+i;(9fB=icZKjjg?ZY4xj*eS|?_sQ; zheu$ggR=s@T*t!`ieC7*o&Du`x*Yf@!lR(m=o?d-RKEG;ZHUrs_x=%|V1o6{k}YGs z6zqUvoS3;i*O#=Z(D{yd;m7(nqD8k^-W*kT#p8 z8NTOlHdmkyXfc$)L$KPgKAx)S?N{EdKBhBd<0$K$+0{U1V9e{b)5OfDmwXCw%}0Ym z6r_B98KD=dmU#2&+BO%a!J+8LKJO~!&)VrOdb1}i<=%lCT_=1UR9~)MH`O;sDH%M)WROE{eM?pv z$!kpZI8MsE5=b|&aa8 z+LK*|FsP8!*yxCAC>V8v3$3ST$g%O-DL<*If+wZeOT*hyNi&r(O#az}cFSYD%$C_! zLoHyX3aHHV+PY#zFoAdnb1I*CL_)!bFE5g>SBOB$aLi?m*u_XoYd%JGo~^tRSRrvf zE1(9YRE$zcveZ6`918qJFskzQl~7N9<(vNHYo&2Z|E!ouaFvxXVQe+S$j&!7O6eQu zg+n!K%TS+ST*woc~6kvw-{Fz<)0WpP2r_I`LB#vR->kvC*vOMa-8 zOkZOk|F$K3Mio!Wv(vQWL?ZOq%e{ibn{g8c7wIBFO7;u{(U35TbJ^C{P2}{pbCQ~B zhq>y9s1GTHNzB?KfbE8l3giWMx`jcj*2x0LJ;dH&q^2(ANlipdoxEWX<{lqR?P#d2 zSE?BbXMdwPOe7LN2uB?3?TRMQUuw{1^&(2qiDQ=_R z>~OmT-E)2ZTUlSBfYSiF8Si(tyS;NO40e!jo z5JRo+E?*qb<&W3GXo8a-WXbIQIQQTG+-fwps*7oLaQgKFV?p$1ZU^H$%U$nmG5V?Uy(I z<4w-oFUQ>F7i)!|=_9la#<%*l8FtM^OcSS4-(!h`8qC>gAOGK%M(VCZ|4#F9G7d;S z;_Ob~ryznTC*X?N0r(A}%ZwPVfBppihbsdXuwXFzf({b^5(Npkl)F_BCRM1Codp8D z*4s;P8YPv`sQ>A0pwClc+{)Km-XM#BzkN*q4h$v!9j0$s1keQj{ldQu20WD2^NcI| zU^noWlQIC={xykjDI$iu;};BeA3~x)pvfp+#JAhqL&R=ErGYaDbiMu^{_XoCLWFD= zN{0*a>tmMV{m9f?%C^aZAm|)eQfItMM_lvG`-4#m_19;DOpy)L`^ z4l!AW?}zmukV4UjzRt4UE%{Wv3!A7MiJlOVoM^>R120HW=3BNObM|nf&{5ezr}BG* znnPoL&KI5X;F$QLl_TQTTsN1o^Aq%$$hMuBQC=y2(YCtE z&VE_{JomHi(Q11kiz%n^L!o>Hsm`2Z7v02M*3fXV#9=nqts57`wzIG&LAd(xgebU= zWt-CGP03X1kL!&m^zhn}?yVWHmVJB3)Ab|c!PEtj{!UeQu4zY(w6XRmAW560gjOS;Q=X-CN2>7cjjOa}Uh8e4 zJYm9Q;jK!Y!%_<~gD*tGBS=+0Wq%W^5*FQIK@s>#0m!wEIsSK_*7xoIP&x83{Nl+X zZqQe&sxR==H6aGFUEJfBRMRuMxoN7jZV*<%kzhbHT}8)bk+X0R#0R5kY*8jFMj3c|1Z9yJDxDg#-BlL3h-2P>;NzZRB{{sV%z;@xeDLj-+SWexYjROJ9&+fdBI31zHRU_svs_Y zM3A6doB`tk&6f{_c zP-Xi3KUvB30n@Zq!?d*QQ)s2^K|E-RfU+l2eW2r>z07V6P=F`l92r7W-rwEN8I?TYea1SX2r>Dh4%{!_4unGn?%2B^;?;Ck-L}tJtFSz0qf)9}nul6Y}p$)lIAR;YV@Qh@j*} z+0lFLR7kT2iYmliVbBleA?-GfRPlgbREHdfOG}&01IOLeRLD7(egU3P9=2GR$xZs1 zWZn(OVj$g{0PuM zRTo}F-KMNP`NfO$=1YPAlGdQd>N{8;s@Y99Q*V;f*>bN;y@ta>v^1Q>EP-03WiIg3id=g!VBB-$kI`i8+9_r zEEcMGn z`!8GcU3dsSJWrWyexZK$IVln{D&4eVQ+&$H@B6EdUv21C!hHkGd#bS?Q4D{b=ME}2 zp#5U4@+p&&&7M_|(1S6NCQJUNYE>;WFinOMD(*DcfgXqr`ea6J=akG_liR%{mo%{l z%S=}~t#^M+bHQ?pnc0i76w|A@T*&lvxnjU;j0ojzwI=R%j0Tq8o{YaT9Q(OTXyVDk z`YVP~o~G>S;^XFbZ0oERA51+^_QVU;PsR)0Oq-*R4I>G1_@&>6E`PPpb}mj!Z&5^~ zyvRjO%jETZ?s+t>T7PT0EaK3Krm49Hr;h}hD&L$PP{3ZA;79V4W4=t05D1yy%4B%a zhVD394_zdII#kA_V-!?&)W@Ja`D)R2*J^Su6109QBV`RIGB0O zcFn-tqV+Qu+Pl*#c%ggwmHDuh(5Ld;@x^emQsgNmZhv(6VX8IS)6E@fJ)85-E>MJy zqm}bFm8|sJ^&H}xOL5A6<#sHqbj134F#l&vdBaPLw~8`4@vxdkh1V~_YQ%8#;)1Uy zwDwkMgT*2l$wj#mNth~?8C1g$D8ycJGX+9Ovp-CXitH`tV?tZ?4<%uaNN(0k@scsD zlJs^qBi*p4$|`k=QyuepE`9R8=?a#~Q{Y-N@zEo+D_}#VM5y3mrxHC!^Uv~3+laBQ zB1wZ@8LMpc`5Q^7FNWPL7!k!)U(lyEQ$K+QfxdpW=0>$b+?`!Er9?iJQ;QzH#&}up zW_mdryhpg``Nn*C7jn@AIVhFLuUDHv;YGMzeIn&52Gc}m2@)o4eOFJb<=(fb=vlXM zC-ptDAT?=r%bXkjSOgi07kKU(VXKH7d-UTs9!#q{wDd-54>{RjcbpOXM5QE;mz>6I zzLsc4EN6!c(I1!=Z1fxCuNJ7*eGaDsC%irXCu|=fo45}IdVU{h$i$x$hntFga+Olr z-T2nNAxoUbn<{f1FZ28n?_Pxg9rFRSy9jmiG*B%NYn6F9dQ`POS`m-g#^vc{?j8j6 zYqWw}_}RZ8K{;f;;2xn;h_Ic)eu;At+3CmERkVXOx$50@M-RVaTzwze&^{snNt%`N zesNlSW4!R})ifM^%jf}XAV%&>)$EsNUAZ*}wgjL5hI1nCL zLBSt?FoMURjw3GflYy#|zG@@NK8Wj z!C~El4v3K4D4R7d^cxZS9UKNNL$(ra7bXBz5P#Qo#s8uVx%Br6j&yd#|51ex^=R)t zf)3##>-tIpH$XJzdrS)w*{^K7ZrnMlP@ji+F!a_&j4s4*iduVGeg)7!tyo3`{%M#X zVP%y0&S=C^btC+KOo$RqI78AJlIcAOiv3v9U9uG&aTp7TE7xgHKi^sL3-kkIk+?-% zGyOh42&4i&hj+M0i}Z1*-0ux*IURfU=U#bFTG~K}KDGCiVg?}vQ;ZTe{5+h3ac#mu z3`FRqIEWT3p7cAfm;5Z3A6#c&Ekb%K3`Q%?$qUoXd4VKd|KS3l%;V8qGA3)jtGV#; zQFzTn7mUXc1X^x5-M?vk)XEqvT&km54202FEzyUC3#!fVrW@PVdTxF%$||Opd-%1Q zR1XT@=}FtYueZM0p%5rj=ucE)OF+7n|K$kHLg^KQU6!`?4UyQM+^)WE4G=@e=Hy>+7Cmr?!%bR(z=Ib3chUs8Rf!o@4*FwpR1?Oxz9i@X2M zxlNX+_dTqyV3#Ku4IYeD^kAg(#IF&U-r$5wJIbd;=G(I8NDXt#$G&0b9qViai`F@0hq>LQvM%i^M80NCGd44UG(p3gZ}S2`ZodM--dsk7+q-qF#bQn zvj2mV{eO?~6@mwJBVY*7m5B>n3^jJxvvwy&6Hsz}Rmklqyp`#P)$Q>6>8g{^Dph20 zAn4@264|{C_I@YCYtA;wgb&@{>6d+z8VzhE?qpsGAH$F}gDrf;G~~=+B|Vy)p!#Ow z-q2vh`>r|y1b(CIuNQUzW5wOhR>sew;0f2>+%gja7XGc?Gcr*AlCzP3zsdS%cl3T^ zxWM{n+WsiAQ)ORtP6*xp(8P^e>j?&C3Jf(GYyG@o687T9|^)_|MP!wYptz?nkl`e+~3{ES&E0H3&*TVPU2)w=e1 zV&Cl#JRW=>Nl}f4;|EowR;e+f*v0dE*XO{u-)eFpAHSHO-#@fb$Nzi82P$?gGEmky z&@zV*2p366eKGN3=bqD>8=tdtDb5TkFvi;_f$K)Bmu3W_6(HPJ@fq2lbUCOJjTV!Q zZ93&SbHnYccLa**MnP+NB)CZPzDslY%`4?8?;JLQ_>>mDhK<3Mr@V>PtjY2s!Gobf z5i>*aHgAgs-h&kZ3<^3`63B~y4U85&FO?oR+h ztd9I9f31-RKe>5iXgTtB1QRsy$>H)IGRfuJt4EGHEBP;3@#B6U3O^cY>;>ElsO&}o zj_HWq&cSB??O3sdF5(k8Wby5$RZbvl9Lm+h=N%_tR~(1Z zTbsB^A@(^D-k#s;#hz}ACP5UnEA}8BSzMl4r%WU@H_kntEGywbg@Jr7;OUC&aOh$g z4}FOg3IsOL>Z~vU%gEtux8GC461yI=I|AXC)v19y`Zan$7Ujz>fjWR&*fNQ=6dpYiT0GKp%`>-T@cnvQ+ubfOu%4r~9AcPz^)?H?#u8$W^ zxN2_*qkksjBfR4$6PFV544~*X146B3CFFSw>g_Q{K)A$B)UV^lj~&&xnet>TsFGiw zxKySg8WOTCrT8e~Hr{Y`r~4H9r2&CdL|Qt6=b_skICdNzx!HG$P3B@UbOYft`T6`j z&YxCD%YUosKlK6C)x91zD;}R3gB9hcDL*YE``$O`x^$iqnI7u0+0bg`L#FMH7<152 zhBo7DXA&gn6aQ$I7;$Y6Lbrw#W+wX2TlEchu^cnk<~*!NwPfJ)s=C=^cYj(7|0i;2{!co5V2J-G6#f4;sQeFvRG)a9%ZYD*8@f6tzBe~vk_0;r ztj)Y%JWeugHSib>)!p&<){h>qdo_iV&lKHzYLMH7hM!HB>HWN{>eKSa@-$o-q@pRx zhpl>fow@G!F2LcO!Hgc!(OdxHozUNq+7bn=`bgA#EJSdBiuiCoK7x@~O|3>b*a#Bj zPDS11Mb!AXXObS{AAbWBb{yq(6jYiS*OuyWr~6R}nXX-p!3;m1ltbi(%l`BZJ%7k! zI?)^|6bwZla}3c^=b~i#q{xnEdmy2dq#Mc&>PMrJD%)}cq2GEfTE&@EWDyWDZ$(ls z;8SkphWL9Z2 zi1;;9N98xmzj#bXm>}k=pO-4WgeQR;ObaabO=5V zh~Y=Qa&4}5E6@Hlf2a5WF7W!#J1)JAn+|o`uO!hC{&#{q{;9WZ7jm!W-RzoydM7c`4{(!9&=*G76PD zW||pL9`00sjuK%YO{9Ny(P-|m&$scP(ovp^cTDK!LLNRpwC@gShVUf-FsJ83>+lLu zRItTTV431{dKnMt>ERwURXyaVi1o34@Jf35U;;_;=hH)QH&^>gptABP8qs+O5u=z3 zG_KSt`D&|34%jAJX85OM2E1uRgdFCowF7r68Ja1Z7shFb&_4%d^OBdQ4G1Rve$(4_ zw~7s?OaKNamPa{r_7MG5#7&*G=e8 z8=_IFJVI?hTAy_9?M`J|e&AcF6$v0ly>QXo33hlNz=F*1OsMQ!t4=i(ga14LNtxI( zEhb*C8ZYJ;508cxp7QD3oyWOQkk1@bry4)J*oZARaY+>CDFt>tg?i?}tv16y>sf@k z@dgDeOk2NhYiX*X+mXCDGSNL^=0$9MW^!I}&ue>=mCkfXwy9uC>sO~Fr*9u|7=K_6 zD4l(T>VjQz_p249YeUKW7o*Ijuo^z73nT5$=U#nyMxJ{Tix#72Ns_$}#ouypz}TF% ziJ9!yD_zTV&t!Ak!=KbA)i&$fzmn^}*Yz9S67kK!3zW9DZ|5dem zGF?l0<>cN5@e3-UfiiOb8jL8K#9ys;NG{Wlluv=R9m19??4dwmk8k>#`%4~6$J$Y*iTrQ<@-5EWKpU$ z)#kuL?x-Yh$>a?s|KuF-OXtr4h~di^qw5lCT8QM@D@~Sl*F^naEA88lzR_b-AP8(L z4h@GxgUdgik7C3NJW8Vb8-4FJf~(fIKDHs0gzpJj{EjCz++2T6ELVP~r?pc*|JN39 z;5wknB!A>5Zd^dS!K718yLq*WHte+M}bZItf-i!TGJVGunE3 z3036*eMj=OCaNOrPkwiYWlhYV69KI4Eyl?mT2;YK8hBLQ^#~*Hw$dcc%fo80!MBhk zk@Y_-Y>cNz*AH4XNjKdE6ect{#RAPu_?Vny3nWQFfhA8A*-)JZ8RE3F5V!u1wyqo@ zMg%RW-4O0^MHEqQT!5M%3yUP*ce7uGj^u>UOGN)#)mO7~T=g|Mfg~XV1f_5&k+j_h z%cKX7?Gl0N5zHG(m#~5GL`!}n5*+ziS;(Iqwog}RQVf39r zWGAq%bqe3m2(#WLhRuh)Lc&@lLPCGvaAKbNXSwQcT!yk%I*X)aIJbs&hJoWB8rr4& zB@4*wM1Tvt|S-~{x+v}0i`v_@W<@qi0rhHdo4|9_?#*~Vxn1CDr5(jJINiobX zJ4LKrR(iPt8Q>O`y>D+l&k01W4?9%%8wlg2HP4Z91PHmw%!!ptER-!jSz2ETXYslj z3iR|vNrwoOMFDAhxN;pgJ>6b)hg5}qlZ_oe73a3lAl(tlH`hqMv#X6?nmBSz5#KW! zz~jfZt$^S|Mz0~XR({2>GHV9wsW3xM@^1S2Nc&ckAc3~!bcS>1EMVSO8NL7kFgGl0 z@V44Vh;16Z|I28xD+Q^`hixfDO^MvL-Y$E*n4nytD-^>ZjgvoYBAYZ!=5QGAM2p2I zMt0KKp0=9?+tZm%Bx8FnUA5GOq9F`ef(3AZM`i^v~9_h?(nFIoVm_13A9{#;kK#BFbZyP<(G;$%qhP2 zrgfaO>@Bcs#Y-;guPYWHOc@%oYv{|uE6$wtPEy`(s6#B^3ceTU$m8SIw|~j>yFAY8 z-r6-$#wSK&sAi$yR}+Mp^LU7K>xS$R`22@7>WSyDy@ar~IPg8%{tQW8uBzQ3S_%&0 zD1`+80;8S1Tftz$3^;*EG&6J1utM;!&NTV}$W&H;T4xCa@7i%*nmQ><#UkyZ{u` zHle0CD|aTPfCp7H02{r~dWM@0bBt|%GPNA{sHdWRt3{<@KsbZ>a-sDdF9{HX&WHz1 znFj2h8K`hf&(yHq?SUCq+5=@mpdRmcMs}ytIlilN*cQxi(MRjua+xHSKX&(vDWdFe ze0RN`cRDL6c$(8e$ybbpw-;A-0Hu{cORsmyOxxoTPswUElUm^Ztfhdy3jlGzUl5NW z2T)gHJt$4c=Hlgfzuc4~iMjmsOD97=C3;f2tg9rp;$G7yZX~s;DH*XojdWmZs(;z! zZOtT|sl2k7S!z(u{dcLy`<&IVit@dt%h-*zO~Z^D#r54*PFR|D z9N>6Yo2QzLNIo8oEE|Ar?Zj18bCZ-j1XT0Q=am!a8&k2FR<3co{-*~K6&pc{=v~{6 zllaMRJJ`S^8g`@cfe#=*MUG{QG{kU9(y^<<>4tevqnh2__{lD#b}D?i`%&*vBO`z% z)7s|pa*nw*Ely|j zKQaBqH=Ocnz%dJZF@h!!9ruq`^~#5j?0`B>yhifOR%F-T2&E_iy-adh?k7+cu)fOh zqY8iH>$a*IUO2`u>Ty6bHyS8EU$^+n9N+4N0#yr71}bs|5xNbWo@D09e+cDc0G!8> zstk_5U#`FCY?K#8o>R|~5*|v2P%8i7S55$$X12*FmtaWT$mfr<%dgD#SYOgc%TF}w@4uZ|bzOdMgJbL$pubZ!9l)oW zrPc>88?SV9Zx~Vc-_E--zAtapOs=Y@jEes-3~VY$z5llJm%zCiqtDgpd&tc{No57H zf9MSN16!ys&I~Z{8u*Wo?Q>7R|7D^t3b!A&LNZAnB&V@s&zWI=jUvL4kG#D5XQC*M zxhNCo*E2Cmh$ZkD27m1ggwIn-Qd`rGo<|x!Kf47XTE%hY#4T{K*V5oKtxH2+>f_M zs^!VyF-?+)PMcHU6duxx_F_fr%LfgZcYrUtK-Yiu10TnV6|YZ}`*130;`_X+b_>2c z(> zeBWp7FA!+YZ|WFnt7r*~nK%AhzxCHOL5Y2P3AFC7xjjtst`oaJ_WT9$>}$&60`RD&AU_8JYiEA5_7p zVVlKu5pjUv-~kYYTfh4E5)2Pt^Zi=;J(zxj*_njOJMF9|$xB@?u_;rfer)#=*K0+3 ztT!`V55oN#SFrQ!iq+mC>?S{Y8K~qsqGq`JC6bc`d}R)TD{6An8*hTkY2B~3q-r-_ zMZtBrC=-jk?`$sEVngcy@q( zPxbJVu8eFTf=0f5@aok2KK{8Gk)?UMPS&7KTyAnj`|hW#MMV3sOsq#M`{v4OG>vWZ z#-$_~P)eVV12%IF!7jZ08==}vfYbabrUm2UShEur!AdR^Zs>CaY)x! z$ud4Gb?1u)jw>-JGBFt1h9Y~Q2)i5?^6>CK>9KLogi^R1r+<2w6EXAH!y1_t#{rhK z3&*TLCI7|oR~h2@zJez`rM9u>Vl!)q+hueH=N2cLVc3H8NPD!pMB^L;|Bdce;`s!b zlb)UCtguhowL8SkA0jiRr`g}PzDCkLSmg^3E&e*uleMEIHZ`+5qWlXBA}%GiBh+-q zTdK5WlH*d>=7;!-$cBZwiVfFdnFbA|R<(`3=i?%=G&e?UYwU8bIlO%Zy+Yfp2=z8K zj$N)q4aMn&7*amM{&rlceba7exp#NOb58Gj-S z3$IyI0Hg@GVx3<FQxde`@Whu;Yub!7s%v4lesAXa1z=zbh`cc<|CJPlwBU7?rCkb0OrNrNg)5L0 zks$#ByoF<5k%npB%x)X!`Fh8#qmC|V<1TBGd`1(^q11zkU?;nv&B_&N_z7SWkhVLc zy}(xWXzXu{Xnd3jo>*)R{LoBQ1T&NIp9&c;DPKbn*s5We*XMl8gh+*$yceRc&zYgxZsS!_Ww4+Mp-iNSeM z1Z;Wb47KX!b`b*F0q2j~vpVFAmy)n!-uVZ?I2Jr_tI5P*p3UvyZTGi^h5m|+<*$lsu?#`Oz7JWsOz=!1#^4AmrwlT{Ldiv?J}j`W*p6$IbSd>L_<#Wf$BbZ1Fp*DU~n?kIA*roxlV1D#^ z?9#S!ZnaD}r}y)tj!>bb%U~uMA=5C~4Doco=g$?(tLTC1j3r+rBN^t@pO9*8Kc`Fj ztO4iP*V^g6nb($Z6eDH33eF-CuhQVml7{q1c;$IB1Mc^IWin?tSPonPS(p(gp0BAz zQbI7p`wJ%e&tUhC}O9`p0>Vaw}6-xP}<(TB{mtO1)6(lU{yi z2iqlo#0UF-o2W^+%0<3|YirmoO6sNOmQMTLhLCO>x=X6OuE3EScw>PBdZ^Un0(!Y4 z`ViJ}Y)kIw{>D@~_%f6##SA}wvnGiV@lq&bJnnXer;=CilbJ|!>9U*^-$h`zpIw30 z;s*8V(t}Sq_O0(fBF%w&9=BEG!qlw{fK>CnQ+n~#r%=ik0>VV}TA!ZNrN4knPqIRl zAAL+S_`u5x6EaK?U0-N$ZzC=YN#y{A20h#%UYM&FzSy^@kScbjnOo#uB7OXu&*?%C z=Be#hmEknXWaY~`k#+{hmd9~Y@!0~Q>=?@LOYdKbt80b-d+aO(g&Cd&*=QysTLCg= zxbF^>H|3>WYN)lB-QeE1g&y^OC#qucQ}xRQsavo`P}V~qf_ox zo+sE4@MjY?b!QLT44ns8xmze@7v_IjLJeO#(R*76|A7O;yKZ+?Sl(tfwa$82{&w)@ z;YP&7C2;8I^B0Ur*@u;1yUzTGiyBWmob(HG*1V(rd*_*pJLA8;B>X5((!hw9Ej-j? z>V0O(|0;0A!yTv*#R@t!3etl-SC3zzI2p$vt-+{GLp5i`!xf8F{KPu!33ui%PDMPQ zbR_D9Sp@vGtju$`f6t#kYNiKHYDl)Zj`qM5dmQ_AB}>_C!13dIR9-0yY25(2^y7{s z!^5KYV`ngJ#k=JCaO8wP!FpBIswx?XvH^5@^eY5q6U3`zs@)&SmQ0~OW6uEG;9G)M z12lgU7x8HzF{`_8D1POM4G+$!Bd+xC^{QH!^zu$L*3N`;CnaS$mmwdcP zpSG(9g+G`fBXhcJo%{a2q~!#P=tW);-+lm+EQR^Mm;3GeT=xpvUr**eOSp3806+N+ zhu$dz`e1*Wc!v*GPi^!Jp%!}5{^Z9^2<2-oHP;TwWBkJ0l~RZmLl1~=Bk%}uFi{TG zx`L|wiF(h#kjasuZOc#n8T&d2!#Q@fa(&#l9Ty*h2V1gS1fsG!k=@Pg zttYFWxXUS%j{-9fqWPjV9C+#n``mX~C9BPEsHGEM4oCA7bJptiL9tIgg^}XhU=bo} z7eduqB^!gP7cKbPp5_j&kc}~o9D1bozO6Hqy5t5;{eA>>S$(-9 zxu-jK*({j@LrE^MTG|j-{X`rY=3WsLPI@xT>nNxAwN0;qL7fmnn;01c^;0XxogG!K zu$4k8cx??TcR{b@1YjD3Hi+tqSU|>U+{8jmU9ox`2v6%loZgf#pS(?HE_NNoOjzqe zZ7ICxmW=CCjy~P3qcUonXPux2+7Xw3G{vaQo48MWn0KR}j)k zLD3gA^ZD4wzLkl38lx@%dO~Ha0LD9d(cWzRHffQzdayNZFXHw3y)W$`rY~!o4jqE? zK4R)&-uAZ2JJG%p7iY&UadiDYy|n@eevbLA7WO55Qf9WfrEeT(fc-D zh&)z{;IO~Y6l%@k{^w}dsv`ynDq$v0w~gI6&xt`)MQzuM{upr0ML&(dbczjQ27K3v z-P!OpJbyF5GRUfAka>CEj)33~NFu?z{l@kCjVYTNsXVao(#Uda>}N)s!G&-BCcH2m zrX@d_FFtWgIxM8s{`LI?i!I&Y)AfoK$TQ~u{Eb6Y7TZIs(QH7`rTj-n0r{1*ZT%t& zA+|(4kFu^y|9usd?exGBW?vxlIwdEHVt>8=>zMmWorV!IkSU2d-wZD1Yu6~hTlUq` z5X1n2-TUEq{U;N~lR3C+yekwzUeg(|j9^(_w<&h`an%6wA~f&EZ%OWZHM=PK+Swpf zVF{%eIGb8UU1T6br7`~r%tR@H^~kV?o}!bBX9|cm70UP&57LA&30>i~`P$4?%4KS6 z|5h+B1b_X%=w^u}lsBTB+$Pzb7M)!*m!!K3dlM4Nm{9w9Y&;&@Z}zU#nMI$}gqc&uH<%^u(fa8E|pQ;*wbCg}(;v2uK3sih$ui}f$D4s7Hf7H5sNG3G~tE{Ie8l*$yG33A(+ngQTe26Q|v@oqM3Yu zUVA*F8ZqRnf3R2b+uKcbuR!l5x`XJd`8q-vEr!PZI@UJMU(J12XUm?lDH!6|YFs?%=`>MA-p603U5a*|C>+?FssD+fora9_?YTC} zfB#y4D9I?F6Lv1H?-y#^9zUW^lROSCv1h~R@nXB#{K@RLd)2%rF_XI)$9<&L-ovF* z2-XpNV6g-{pTSW1PYAL>>W^f6kaJp~@}R=)Q5v`ad-gRV&|2!iS1B~JtzIS{ zd+LQ$p{G8mYrCQkqTjRJmSZ>hhAwp=@I zdT8L);`QCm7wmK;0UkhH<0%P7GQrV6Y5)!M)?^+ymI{0JeAW1<1dlE3847sskvLqKgoKN86ungLf&tZ@RcXt$=(heeYF8T1xB*n6Y-vRynS7244FH=-LinAt%pC-Qu780Z8ln}JLScpb+;|Bn3$a5f5n zTY&|~ctOeQ0o(T=?;Z5$-XPKnt|W62;z6u94rEt zK|RP#%4Fe{_$C9gAT2$(JFywWE$?6I5K1&QnJe<2Y9{amgFt!6JJ6-eXVIM6X$zY# z@99h7LUEO+MIoqcn!bfa5aC1D7VBh6X0!c?BW7CJAVvH77wZ6Lj52d19^fxSxc+BF zXAt0Mg~M^7!_}LQOBfKT2A8Ti!Sr1ibyPr)$3fy>u@?vcVlGMJVJHQh<-T(OlMa`} zM$)3a>yG#0b)W<@N%?(_U%P9UAe7n8dA1QcT03wwEIafbJ)*W=s~)&rAIF7x9MfOD zxC@HX!|SAd`Cdsg5o>M+#;zR!XDv6+hp`uAJxIQIP7?Odf9ZHWH+cXaNeg-@gZ6Lx zmY3{*alm}C<$xc^SzwP&-g zQ28u6x>H3Tia&u1WN=QA3Q0wD3llM$lKSnqIcuG73cKY?*_hh!{wZTV1` z!b}XmSL~c5&jc^{%|pre~cg0v7KN{!Eu+?Nk$O((lxL_#_44{cs!f zMuYF!ObWt8>;(C#da83W);|dIa?OGb<7?n(Py6(%WnbaZ`KnNVNA0m!cG1OwO4s}< z9(?;flGDx0o4Io$Ykmje3?{>DgG4aeQq~4k1V2>=WEs+G1hq@cXkQr&=Y?c0&XTnXKb8!j3KlXXo(uBGDn(z%QB(_c$pIU zW0VPIP4)t6#ddsD^{niZrVpTo7Rj7(XU%@)_e)dt_i9B%01s!*B{wMh)EF7zF;PjC(y>-FNBB?!eII-gyM-<@mNB)ER>_Ilr zhN{U3usAv1+19ryKHRg!Z{?|c2`$```ar-Z-;B?K%k-Mfa>!@$XUH9{bzsmy7*J?o zBQtQ++X|4GzU+eu)$$HCA%(0yRSt!|Ik$_v1x2Na#m7jyhwY6#_)V+IkBTrd29i7; zGV5?zo1lw~=k)ibi3&wsyGgDMOzhbmo- z6p=Vt#s+Er+%0DYitJM!qZivOYNFeo8K>+kR+#qpqT+jY1N0Qo!fu`&u+N_egq*mg zG^Ja)R4=WiZR(>b{g2lXzw(?@kX;c4==cl;|I)3!8^&M#OQZyTr@MLPQ7g&!qmA62 z3xAwn_X{pk7MUM9yy0!tcIoFbP|7?ZS>g?B_31%2Oe@gbTWxw{%Xfri6Hsy1R;*cZ zP3N|rt`JN*pyFmf*BL%h)WiO{s}@061{^t-de!zbE&;)fYj0G=lQN~7RBF13CVN^* zy3+6k&)=6UidsIimqYFVcR}JL_=LJlr27gJ!{$fp0D|V{ebsOARB5zX z-CIqQ?pckSEuHlWjp+{(R9{5_7qf_?wCfIKMpv~9=bGuNcoBC$`g}~yMT?_dXLDaq zCgRKTw4PiuA(|F5^lSaP!HS@49Aeph#PdtpxTCJ1xvMRG4 z6t~G^oGa?FfQ`oIB1%DZ#`ga&`ToMSwD< z+)C|=H*vzP!JVMFhF=M>jDNjFmt_j!3bHG&7OdqaN#y-&zZFmH&CVf z7QhqTSlrPi(6||dR736}wB3?&<^3`!|F#0lB0*lXiW8W~);YcToBQchnl zXpm%05*g<}VwG2FbPwRoBY(`@z%$=^9YK9|54Z+7vRVOi=@|QgL_1uuz--m+bDz~x z>+l9IdsKXGNXS>g6`aK~h)JellBbLmTX52VeqffhostXRLSyHCtbAZJ78V7B7j|tN zcf-Ozf>eV#K2X51oK2JnmXU;k;=B@X{f>!I2&JL(GFc%7*GPV1lcW5O{CP*tC9VEd z*lbFFl!cd5-=&mo#OFYnlLD=b!G7j_V9!2+zk2W>NP zUE7im*lO~l`5P!Xx~trX00w=Bi+I1RRvw?+CQB6_Z1t0ArHLKy9+>xsQ45t z{9J||Mr`+8pnSkjn1wh&v-TX{w_>rh1@_%HF1HuGEt^YpyvWO03^ ziiea3{bp4ryG_gMM7xxtCP(_vapKVcHsUZCIm+uBjf>3iJaL;&kzLl(<9ieYWawLDF|6BnCEh&}G%KhdgfjjOcUG;r9-J(nmVXCRc;9;|oqd z_9a=rr#p$YGc3Qojviq9+bJky*^x!RkUF^VIXCSb@`bZP-*LZkvI=C>AU7g}r12@t|+p#g*Gth^0Eu}nk+tsF7{ zEHSVq;8qB$q8I;tjmwSjDPJk)t7^7B;OiDuVo##RHt8L1B5=d zVrT|SW%_^g_Jp`8jQ_dOF0!zCKv@kr?oa|q0sn3IY+9oYSVNjUtJfu#UQD@ZoA|oa z>tz9Km>{ex-D+GDqpWrDozRhh zC&zW9MVyTKf&`@=&Dm*-$C;;h7-t{biLsLA>NZwrRY={^QjHit>0Np=u4nwFA<_62 zGkeKDp~K3*nZ7s#+&vldTuYkbasKe+AOYkhU=7pw2HONSjD%ig7VIkraBs@(**^tCs^iuarDV4jq1-8TH9Y>itCkmkAw+w`7dSiS&ocWRk|L7VKoVANn!^G+Be9+_vaA#qm#mU<95fgl|fT-3j#K9d7@|U>&`HlD`haokUH<{~|gx^=jEJ_i(ZrLX%&!EoAjJ#a&wv72^Y>S{yZx+#SDLLrM zs6GT?+v}R5G8JX~W-a8jj=&~Gf1|zDKNUH(qTPE{y__u?C!(W{u2awpQD1$Ts@B(` zd`vy@tlvFoP|l!Q|E21@IQ64`1MNxH^f!eB#M19mvBBT^GTQw4&+AtFFJv6LxnU{(N^BTI!u~^2C8Om_FgyF> z=0*bM#CWO_O*Wop(G0S`!9BYZUPp8_=H>c2iJ>{Uv~MV@UY~Dsd%~~g>RDTndR^yY zamrG-xH9#XmhA^ELVP3sN@hn`n28`v=VG4oo65;Ci)ejVn}Vl1e;~I`;dJfSxcTYk z)%7Fs-7i^LjWgpIQDPTCD0)n%t?AIRKDjG9L1E17r~8^RAurBz^3Ibrd5!jJRX3fDSu;uKQ@+5+pQ{lGIt(+Sp^>9|=yTiJYFKls#ktg1d ziIp#`s`g)`j1o1}WrtR+iYnmk%*2j)QT=}mZKUThzf65@?ygHjD*DC#4!(CuA>NIH z{4yyRpR;@PmB7Ha(W`3TaY;)*mBv}+tLGO~vhQtAbKQTyIRlmeEnW+fU;S&lyhy>1 z^8)g(*XxQ7c%A?JXaoITygfRg7uBiRs?l^Lh=d#8JaY~`E=*sP!RQd4znCLquxCKg zT(bIA4lpScqYXYXcCz))-ZEej4|TE6fZf+a1gFW zl_re}X}}fGEm@2Er@|XQg(AbShjFRX1=bbXgS9~l4h(y4(4drvSLfne5pjC@)~W`M zJyD{^om;O0Rh=U#0gCqWVq;gI*bTi_Ig4KY@kHq()xq1%AtM>Sn4uJ-sc17@Q8W>R z^Wm-R->413dg$e{P_!M*P0~^hj&X?vzcTJhKfdP4z(95f7$W_KJVqXy$frV6so9~) z3K!61)9B}+;x-OaqK)Dnr(q~Da(??ahtcQb2BdS=%Na)h^fTg?MaX1ox0#LFkSS{f zYwH%u^|1n4y_@NtP4g*MICtZCliH1(uBYZ$J>_z>8cDQ=?!sA9fd$=NICSizAk26j zB2Q`x;rG3p8raxrBJDnE2Bd>s?u-r*xuzbEMr^_dI+k^N`yV0SW!*;IR+uFJ%H6+z zmJM_29H+SLVK0$MzfCPwc_Dj8Wij-40>J+A51ug=WL#h-)s<-_Du!ldMcQ2LkEC z*;{gW6H|xbL91n4sN~mC7s;Pa$-AeJlfJED&dENFeRCSZFn9mpi^T!$;g6H7cePU; zcx+WigfFelFZDlBx(Zw?*=ZL&RO2_f)s=5=?7UxYmZhwo-HcT~&O{uEE7D{x2?PY~ z;yv<8$o^tIjbtHK#|N|*LWi-fT4*N&S$XxF$U(Pb8qw@CO)--@4_Ni3u9^V(A+CNu zQF-q@gJPw6BcRohNSVh0rEZ=yE=;wUtN}-LPptrXbN*8Dc4HjzSO^I(t9HJD!m57( zLOGLUM2QptrC-Bui-9FUW~ zo>s=OeGrPJXvX$MwEog$zJ#(LX;esgut3wj#Sv8&k6$OEoszd5Pknpl_RH9Q!s?^i z9N5Rt~)t3o>w~27i=C#^e<*jeIJdT9%57>BNWieM!qW%-w5JKs5tc|yHVC3IV=>P z2MlC@p~L0~~4VLr|Aq<|m@%(OCa9JpF(GaWIZ2dXWeX?F3YN)u%Rm#PHH_A+u zJVS9C_eUN{J`QWHSzvF!goKGWi~khK*A!}h=$)3Q* z5m(dkozh*>bIQDMUgaivbhGQDSL5tajJ^ob*-YcL_<~3Ir?4mS+3xRp+S zw=@lniN!_Kj5?9~B9o094wH2iHej^8QX;5@%6Q~Qc(4y3$4(N;I0hJ$GG15u%zQj@`!6pqx&wB)Nw zubo(kPk;O6|5CPKCg!TwO>Lj@wB}~OJ>4lyIpQs4ldd=@aWI(FmP};YNOF$%NY*cn zbxuo8iBG-R&>l0X$ff)vfba8tMYrWpWcOX0t?<-shi}TZL3e$%%nxl1`-=oIy1$eD zjYuB}^>^s`Iod`7kn=GtH_EF_qbYAw+5R}p^9Tqgh6_TeX@_${XczXWw?m{)w%F`6 zp7~K0qs;4ow@#P4DLFpOL=;l7tmliF9PAi*4Hyty2MdGsJ6**zyI^ISs75vI4;Sr^ zCgC7{Vo%ps{D9(Fnn6hq(RyWD+AK!yVIsDQoqv<*E!f|yqxR$GY@u_m_~d;dn2kdJ zbZJ+{0%^ex$ELDwxTm1iypB#A@q_8Dwn)cvW}nkgSl?M6g(K_a^^$pE={imd7aC*S z9DjE2RMmQKX;(f5D|(1X8pis2L#Km(?r#02UE2I7_)uuI%dJzr8*->dr!7tQ8D*AN zh0>0*f@$-@N8&VIUyV$wo_#%x%pQcElFHY0f5&CG}tvFb|`Vdp>I4{*|Pz-p0+b>c6Y~#`D@6r9XYELY^WRmD7_B<>6ttmY!)_r?F|hG zD0ci~@y^2?x|C80qp!bR#O^%o42eoB{FF>t3@=9CAS*K zMuO+n6bJG%pm5?`$KLGiw#mfc`(=jK`16x9E#^NwBfYWgfGa6F7-r{w4@zlLZ_P5% zxLyvy^SO}$B9dPxvUp>*-`-DTFYYGB$xssQe?uL8kQ`d=7G+4^CJu2v;=T8irw`2) z3{k^2gs0oUXHoRZP)l>4oa?XutuzHeotYW|`-wUBncC%18GpEEpL(~%{Pu_Z?wxsE zav9b*mOoc7tAkW1vD|}X#2H}PUsm0wKFea16}D5G26?rbp{a*G;-e)l5YqT{Ev84Z z)CsvI zjNE)9p%Q&d>FHUo!5uBLBT9sbkhfeI=h=(cXtzlX`7f2LlXA*InY-S~0o4)q|BO_g z^14~u-9Shbr+TeHNF3cWK_f}tjk(P5fT^QaT7s`se1sMExWs}^PpUTCMi_}KC>PQ8=2>THFQ#v%c9fRB(s>2jdnR}`__w*39%V7ejDXNT+4#STT0%GhOnpEcjt9!$KJKGjL5zwksdc!m9*qgY6=jd|9$!=pso*w37?^GG7%d; z_e=aSqPuSA&_y|1-iUdP#WTvz(87F)-I#p!E&Qqx6Y*DD(~ib7yz^gl^LeMoWY0hG z#Re{udTa|lK8@%=sp-V5uHcPRwMCh>gXaDZOTkGWYN#1;qscj9I*ZancS2-RO7yKyC$bE zHF7RUhazAOs?a9(X1Rb#p5#7=|9A;#orbKP7R8IU9mC=|F%2rW4pNN%1@9_>Ql2o8Rf5l}kO~Oqjn-b4wGQNeaa&X9ol7rvJD1E|Va;H}ei;*Z@ie7lZ++0klLJ z1MaB8xlemjtRj0;#j|Z!&exk4tAMf_ZkWZ=ZJOwbIk3Q(zaocH<3o=9lH0=c+qYHi z&;V*k4G<&SUs)eKTtDv>lG03p$E4}w2;&naiQP1`Ebp;Rm@*JaXzlqNN?1-8hNuFTJE-HUpWVNv`V zeobq_SGDSvnB{R_p`cLYnCn96C#Fs@L^&PRG`!Zu1FLNY<*ZzWTD2~d#;ouiBOOHK zEF%sljv}gCcdX#z*lKvKUgN|Icj~O?NmG0U$ZFVSh+?Vb7M=Kjif6-YZD4L5>cCrq z%)Ax_@0_x27MAVp=={){LV6C~turrlspCN;Eix!XGz^L9FOXC&I|9461%OB84(7jU zwJ2tFZV|_Tz%if~pt~9a_SAegHnb^Gm@R4M&Rg(1a#-H6g)Jep`raN-nz7GY9)N2t zwswYD=YfPzOC|)-tj7)8zrYM%4_2TfOf^$>PQq{iUj}<~AjeeYJV+W(LMderx$C<> z#3gT=B@w$Swv3UJ#@**9nv6Llmp=x80vB++k7-yopmab^3W+-2#~7f^Qno2i^Q!0a zSoN!>!m?|7f1CmI_T@9+&}BOP*g-lnuh2GC%w~9?|RSOPK$hz z<}9AWAIY>xa?MC`iWCbTH3Tl=0Nf+_;@U-6C^KXF&4l3j-LY&goVYW&7;Fb*P~z2? zz=u|N)h$^WPj5`rmByg)2Sn}T#^es65l|zO$Dh#9aCK|ap;1I3GH9(TL{M2 zz{TU(D|V0;iD^XGv%ti_u16O8Q9KQ6AX5MgjXH}E&1u59C~xBSv;k4!%p6 z{$EL*a}O8*d&yF~$`1y>dDsIWIbG=ZgGII#3O=o0kDZZ|MuH3Y{8snm+hv za+A)bGcW_(_Qh4+OW>QOX+{9;4d~jTLlEMJ2%7G33xrh;aWC?s*D+!nWB7EYex?Gac8_ z>d~3btc6|u^VxWEN2|(Z`N(u|Y5Fr9BLJY5YLV(Pxssl$CeXdVy9yeEt}LX@U-rK@ zypNyjbwujBQCs|g&m^EDw1YaP^JGwg@pimGGvMT~gBtgQfeBDo=E|z!iq)l^05%e|Zxkuv_fNxR)bTOHxW)T6D&# z^1-<5b;XOtEu4JPKkDX5yV(!@ii?|f>}Mj1_hPMgM3vv!8)a_%C;>Q4@HwAXj7Xc6 zpVytScGw2@_AUUB|J#{vcz&mv2~aOQ=aS|=B+rF9P<2(o3ggDg%DNt$;)bO<4^`?w zIVb@=)l-gR+7EZb$iHlgJNx{4Oh_M6si|B07m&JYaTRPA!6Cd(^lPQq z#$?XFv#SoAtxVVLjbp2C?#YUKv)sKXVQRJEz(bf9QB#VMDcq6H%3F(yE}ip4ag+*+ zqSn?VhOTg8!hNQRR$|Qzl^0LJ^g(J5*bEN3k;T0T8TW(DFqu83%vU5GMLiO8)w8KM zPIJ#4#4d#;2iW`$$n~u(qYSG}_PCT%POk(X1uW!y(81{Y8;`B34f4<-(OjHI5So-Xz zCtwA$wzs-XWeu2}dni!{LrDUxX79B7yMC^o!Tuf@!HwB_{xOfIT(uqGs2woV;5%oM z>tDv{t%F+^OZy~u;ng`*Q>%~Q`|1WCWps6-SgfQ?@$@4J*QbH8%I1Qw)Y~eN8j@eM zOP)G|`UL;|w4jn}p*i$)p0mgkWt#SW&x6Lm(I(o!<|(0%sXNhSkA1{gw>;BQDW)pT z5c%CrpKj_^P|_EANMBnMe#f9OdnCIc#Za*Nl#cXspV8J0;21x7dbc}UKu2ZGb%cqi zmpfZ_@syp8G=+`a4XKbu0(D7ka$XF|K@X~wON)2jBqy-N*MhOPyYAxh? zya~aY=B7F!1HeX3os}AA(dq_@lec>ebMhabNF7SlhoY>V6MNgto*cU?PhZEt1E(i- ziw_Xu3}mgrA^e+l2W5DBG&aV-g1B*K6YB?C8maMeuo=u_S4+Ct+Fl)zWg%@Gr>8_o ztFL>17P09qG|hl)i3jnSj%QiT>r#1KG3Bu2f{Tyo#VsP|EUSm@zpLI~s(=EzTO-n* zWaMKYlxL8Tap4j|)nTc;CmdC319~uX_FN9L}-W4$iUz10(n0Ws_l>d$kz>E=$1?K^R^x72we4Y9V zYm3L%oCT7$!b^|S3i;os-()~a?{E63{#sz{XGJwyMaN0lGVa1qkW@a{6YlurFMAvU zFac~8HaU02#YS;8Jvm(4)(A}1#K2g;(~^(!y^QH;Y5L01d^M=Q=r}AjRd)Z}tGV%s z-uPUu9%0Vz?ouvZup-OtZ*C^p(&m2`ZtEzU58zYh-%)JG>^J;)uyvT2VouDx9P;~P zwXnQL8SA)IC$dCjT8ncz_ky@S;rmD}G0tMgq};jk8w*j<$pi8CUG>t}6?(4QmeO%0 z?Pqz`oZOg#SxS4G38Jj_W9Ph$W6TkhzZl%f7y??dDT9k+|A77*`^ zP@S=}c*>`?#92Md&T>3PsX6k0EON`Xt~|8*AECnmK0ab9{>!PUYHosV>dLZ9o6AE( z^&giUeTP+5Mvm%jE>&(Wx$jid1(-Nfq!)*Me%}-ybJ)P!zJH8zg1*_Rg;({dk~d(i zMCZ1{l1e6|XGz$J4Qu>VJ{(V8e_$5(Rb(-XF+Yn@l5zW4CZK~bjMfaOV%pAURu~i< z?fkr}m)j1zGIwqWMo6!9*S#7MT1_!n88sX?NJP1-NIUwguFU6*Yw^h$+vR6imNDY` zgS;et0zEiY0|QUkGId)*5pO7rRSS--%Tf6ZEj%h-N3ZTm9Skq_p z+IKXjswZO0!eY7s7EcQr0DN2?a(gO{;*k$A|E^tR-e@Y<)HN9QMDTlm z;41M*|GR`p$)njzTj!q}?!_A&`-hCc^D#A2yYFJ;7)-{nBs`|`k?PcaFgkg&&d(+uQ$$MGni&G~04DFOYiXhwLZ$!@7hiqwgoAUGJ5l`!Tsu8_$1UwR?y;;aKIB z=+rpyHhPo6F5o7C{0@ACi0ZvXFQpF-B1&KRRt5tTt#t5pzmXzcS%;+$%3(b4=w>Qx` zZt!(&M02E1$HZZDq4ind2NpFaz>5&v$Bc!L{W zHOo8e<#r&EIT}6s)s-c+dJdsn%?-U^RJ)<6gZ&X&4Yuk%bnAi$%wnIL)l!d{89rvQ zaq~hC4tujOAQhc4zMQhO#Y&EHeU9_Nn7OOjEJsVh;#I%r5wJ1f_V|E9Egv5c3kWC@ z(ElZ#_nLc-uAR4aic%rtD#l98V9!0Df{G{Gea8Zpq=b0CBJh+8M1UzSzr;x16$#qw zPY>Md-Hn?_JpuFWVwZP-tK5`ZOmZ>3#Jb;Tu zC!0q295>g;yKi|)azPp?+YEgMs3(eq>{YF<#)uO%+>^1}OB_ST(qsK<986us&;*3%Q0UsCGO)-*d>;cemSCkpAiijz_Cn$zdr%W6R$_Wk+FBW#fB_rECo_nGJ1!Ss zKC6#87wJQ_35#^u*qXc%%WAUZ>5gb+z@68ltwal2DdxxAfRg^Bl#{(DL5~_e`XKS| z*)LCYhT_&1^j2tlWm_7_v9LTQ;(fv$v#j(AFZa#Ihd7>V@!ldBaFE|eP$@qpcwxR9 zZim*A%)liP(qDg9zCR=yVHaIuDoR?11KG35s+V9;dq_(uu_Sbtt zZq(WG?`E3;0K!!qFwwz;y=xBTP1=}px!qC@sK8MKAm43d7%a3N7Tv|0F9&bvT$VU(O5EuC#rg@O+0;XOjQ6BOr5D=uJyE|eNAx=xkG>F zg_733L?D86Isz3gX)d;V$!y4~1 zvzfUr;4 z-No@QcH;AH9xDEBv>&RNUl|4WDHUCR@=au)R@&^)X5(sU%1;7-djque7Ac_H#bhJe zc2>;EKyOA-VH5~%Wx0^4V!`OsvXkG8Q7cV9dNc+jSZ3DW*gH)wNY`HrSkGpm%;6$v zt=#^ecOjt0o_XM7k!E53S>Q|F-2qOD#(8wY;9ohPV!?%txQ#Q|`U9ZRm!W}-VL8Y_ zvw-mUKaS@Sh1D#%;FELx`K|h3MZ6sy-=@I#Dz( zw-83MIz z@+b`NapM{$BJ#?XGpP*4L4J!Vq|3etCgLn(tOht2ndMPh@F)tL7AOLf%tM{}M4RuS35@1LtVMQdGWL#l{cOostaqL8ID1K=N6sdqWolwN5 z6NX4<%>EZbs_YCMzSVVJ-k_A+DRy8k!wo2#i&OsGlzO$O{V2<$-JDT7Io6h695=5j zk6BZcE_dl{c#-lBHU8IIIl+ijY)k*Y_7 zcT*|8Kwzt6Jr2N!ioIX#@p^o2Z(~&tOG*5xq>xxJ+I_$7_k(rVwm4LGsBqaBt{CK` zA)zTBTL6t@w9nTN$dC?Yo&mD6 z7QXa_Cki3)uy^tKrFEi!_ z#^1Juytv~Q%)ha7SlE>3HI~HFlj7|@&_w2QFs$Ut%pTTu*N%+4K(we`9GJH~a#22P z4JRtT9r`5#xo&VcLpnt8=^y*kVjC8>JRbEm5!taXT@zh4_w-)=tIK*G08V1G?gfs@@IxgR zKK&a-5}&!<-G;T!d;M#~v1Q(|iBH0O`Fj*;jC(yP@{!|fgE!IsZ2^}AurG(v+TP=T zp`ryNZUEJ?PjP2`T&M}!0mzY)bdg9$5#&au-^khDNwKy6bhn0xux0JN(cBZX`aIKW zs=}4K{Cp?Rzc^#U;rZXOf#TUByvV?u02Rd;>Z3(peq?ONb-SLrxz!zX@zhR8O1sO} zew@OhWq_56ij*j@?;oxJ8}j9f0KiB<7rv9vvJlHE!dCRaL}=EsA}^jpQT)u*M1YW) znObFOw`Ns<6gF4|A8X)=Pp=h~#&hi&7HTc_^T8gh3+g0pg|OOAj_o&W|1Dpo#~mip z(a0eNTPW>+Kw525da?21&^Y(oZ$8Mx^2RGD|L34h1<1GwW-uh}4GAd3*wCU>GVIkUwSl3YDSgQ#WW7Un7FIQ}L-CnDG3g1wM z;9xPrJB$fKMB9w~Z+-VE1KQ06!x8Mc2c&;)`oi-N%gRSb|L}c^9?Lt}`5bu^*d5pc z02y$TuTh~tfkkz)wx1~LcDOWE_A9&#u`|J1?9I%+Bs>wzab)~rZ8Sv}$o0MyxsBW# zIh3qaAzHmH|9e1z5O57JTd0zZN<+4{z&G525dD#CvZ=c=`<$yBl5oXqx+s z2~`;%DS(MAcVj2zWAweKy|e4COY5)ehf0)r^frA%AA`VT&O*$DM9^)MkPY-}hntd$ z85tve3|JbIurP$X>1>S`b_jMAk2{<&)nVFYytIV5dQ@@_XVY9sx}ZN3A-6aDS{^sv znwz@A2kBuPwrZs}qrg(a%W^~mW~ z4a}ZxS}ts!Gsbpy&sJcALV7})$kiaMX|S=?P$gYOfUfmPwZ6^}PE`%n_SztCZFv<8w=VTW@I%}=Y`7Qv-uUmF7xO;~A2RzPg9v*BTkc7*_enaWz| zP>p8|d(8SIgnd7n6>jsrb~FwDlCg7< zWBlPx_0n6)-?E7$htegZj>i9@ZjFJ4MRh3QhH>8Y51TM`C9jn`m_T>(&#(KMZM&dq zeN0VU&AmN}BxXWQ_<`V-EjGa?B_t=Z^CP_xzGjKoDOcAiKMyXbJSq1+vCLFN`O5jQ z?c9VyYQodVj`@BTsF=ihGZ^lClN0ao|H$l_^44mx8|H3B5%TzQjr6bA$IR0I4 ztu8c#GC_Qj3GG2j(U9w9=z?hQ((6lBt)l{{pYkqUx@Vr;PZcy%0=}rgrzt z_n7sphlZN)y0l~$HvGhm_4qbZSC1;6&Gf3lrDCQ0hWT-+8e)G&^XAgQUqS+BQO%>j zZ?jzRVz#t>xkm8*$lU0v6@Q=UpJ?{;SE|qdAmQ&Q zW2X@vrxi|N1$E4z2IO6^3f&vWyvX1kK&cOUr_i;VVZ8zJ7{ZGXOk__SoFRZ>0Qs=M z##*lB96(qBg80#emF#}7z4{J+!EJYIwtc;|;CE?QWbC|DeS&((&424YX}bD+dMAIf zmRCF#3mm;yPX?j;OnS>v59zf#Y1Vi&G7Rr<`{=RfjSiH2EsC+%s9G9c0GM%p(ITM2`#PzqUw2KwNhVs6798g{?`|+;uyUy1bq4HcH$%1|(K6QOl5eR7fJC=MZ)oqf z-u|jH4Q6N1R^3UIO#g~vPtjrw6;WaGLP5#F{f8k`QXnk|VgR(@?*(~4YDv;0#RHn( z1DNvU!%D{U(5b`sQq*IvU}{DeClKW?u6Sq$2HBz~S8_+>`E@aFqx?-j5;m>p>z0W=Beaij$FH)M$y}IP|Z0%ZVhs0%wKi@5*0`s$c9FfZVoyeMq!kb-Kfk4KK+)& zhhY`-pC~o0_JU+t(Ni=2i|sg?aVFw&?eBFC*ytdUiH+nK=0Jm~tZaJ~_Eh0gNN}F2 zG3Lv`=K$Df`+>_}T|)vqP-g4ln1eu}hgm|TeCp=_57OtV@q0*~nb5(vgT&bHOCt%3 z-{U1F?5jN3UWS%c_LfzidsovrX65)Sf zJK+lP$!Aew&akQZ{vw@6klx)uF}*v%g65m=vjkk)F|VuHP2~qtUwevYwzCTL(3&QB z*`XCp?J@uX-9#nGF>A#eXT*P%ezV{Km6@BNZAQ;tn0C+VI?#pLMgtAVex<<`Np86r z1AKld1Wz>UXIVljwX&5|n%I6+5m88(7DzhMkUGrI>ZuAK12~8&tMjIlc*jVZ6e`SJ zbMyw%caLzh*Hp4y99E6kNg7WhIRQk_>o>h!_AGzdull^0-u-LQ=NjI~e2kJ6g=vN@ zC$NiW)_0ZxJe92O*q8f5gIL_2#F9cqOove6I`U0a@0rL`vV?~#8;B@C9qG52kDA4% zfqOXu5>1g;;!3=ZfwaJstMqwV;fIsQSxiNc=61y}?hck$@20Dx%l85S!N#xL zy@#Bv>{ueb`D{_T$j`Um8N;BhzIb2JS8sMSd#}DGp z|2NY+0upCZ`~;pAbp-U+-$KNVbobWTIGUBW=Q6{zE6PFzyt~<2rY;!*x!v$=j5RBO zdwow1P~D+5ebgOb+Ap=mvoaWsHb8BdupBxVn(4huOS;J#Z#W@|W+uE{_d`Eh3BfYy zy_il*%dH=g1|r9;hiYHS5HY{)%Hx}={TIu_hbNs~Gvh0IgeZ|Zb=fKn!M%2yV zCTpJoKGMzWxs&X@;eRW{H!tX>j80_)Q=K0CS#)9t=mA`uTKk zDY8K&#?#KD0-MW#jx%#IyWQkNxqHaGOdvClsw!~(X`_F9KO}PNNxQD^R-2&whPMt2 zy!)Oj^xJ>pPp7&5aqiA4{s-wo231zsEGUxAwBst@5Z_Y-=OD;1ut?`1Kgj58w- z+$-sc`WXEO(n?bg9@G3R18>b$x_{ic5EIcg@^4g@uwhQ0cQ|Z9CHVTfhM~G1)1cYx z5YUH_VP*8W2+i+g?UwKHKP4@<8DBjB-=D}AD%gDowghmIIUUJxtDFJL=+G;<3BYZ* z>C>*yt^BvVvnL@YXlKbk3(&C1E?zjt{@>L4Ip32I=={pA|7n6faPCD|?(OWJc_Xtn z4+wJdgPRAmlpY(Bbr8lYaX@(Pu0KGBjXO?J97>MC$_#K;G>@$5Uxuz9?4AYmLK;1i zCepe9R)4cU55ILYbgULuKjBuwS$ju^-T@3TH%!ag)N(+dch-6|BrHp4U2A9$atq0{ zcGgbK&~x@*o4>c8ARuOiudrX33>4%tzWysP zTr4Wc15M+Kux)ZHF__y6Gz#4x;5flUs$(dhcF6$D-kU-kxbUuJhZdVIyLWe`@$Ysz zrhEcT_tK7o-~ab(pV^W`od*z50Ky-fO9&>IDcE)-F`*8XP`P>^eX+S>$PIq|9q|Sy z8ccTfiHVY^Ql)OhBNBsBEptfCg2LElX&#@HFMw>j4dD(q#38lUSM>vi>wRSqDCNow ze9VY{gDZ{ul!2vK%!R8%x^;@aKp#X1HkVSQH=I%uj54d?ISC!U9l&rvcB? z8qx55z*ZGl5Lt(!aYw!U)pw5w^h*+E!Zpp3fRy4yI^K86vN2|siJNB`2_Wk&C011) zlhM&mMNPezx3M-S{dzL74B7~dIc!SNTA{(L3pdPtJB10g{j?M~zJ={|F}`AvST_Z$ z#hW`HFaTSTyJG+3Ql|j#l4`^%L#Y6$%me772=88*312^zI;om%awnqKV6~{${-SN~ z%tZkg>TrnxY<)YDi)CVflS+J`!Ne7*^O^_7j$lR=4w>beYbxV+WA4;BZ;*dTDh`0I zS>pxHwsC2lS2l^uKW+O(?(#*>EM?6+=Msc?zRA8C+Y_9m$@ZjyF%>ah(R4%GSRS+a z>L6!L^9I8ggrS67euCWC-6r1BJ)aWiJ=KB#K+aA#lI_BSD)Dt~;}s>8AAcn4gZ;T4 z)b951#6(HTc~s)BJU?#iQ-2r>{K;NFVgYtV5x=%)D8QH;>L^i|K>gC_vY4s6;(Abn zSik=d9+2HB`W!W_7tEv8R z^t^uxU#EK8vU+)XcF+ zyQX#`5Lys<$s+%K(4gmUX%=FBHfI%6sFTR7cJ1O(dg+jCK*S2dl$s-sLjup4 z$S7uEe6Q!Weg~$p%bLaZ#X(ns1EX0`HZ#hIq?6Air8f260x1y zZ_Fn@&RcCd_A3 z-<~o+y_<6gce&khq^0&crL=iQm<5V&d~TB%(gRpJet@{6-=)%LgR3daa|6Fb)|3=7 zU>=Z^siy z{9lu&Fuwe&E|1Q`K}9iwli{a}u`bAuEU!sI#;Y6H&{o41CAFo3uKPXO0d7ZM>+N2x zHcRpjPBt~Q)QcGujAk(Gov8^t+)9Yp`EA^K_=W%A#bm(vRWQfUJ3Y1;2d#sK1;-|y z>jK=b!oo-pvItMC@l2qYzB0A%^`)V3dEXCE5j$G2A+>2{0O&l4_Y*Dl?pf4e7bt}S z>kVIq^gQ#rx{Z|R&=OyS5(umcUKce7tD;NRX|muC;so%4_)Bfc4;n13PfWQC(VMzr zLjnW$$_r`K0<+NM!rEF;jFCgI;Br+!LU^0DF2nt2xqL>~7bjQ#WeB|k7Q4&}j?hal zcbr3$^eQ0g*VJA8S&MBnBtqkgBiZj#!qfaP%cGnmmaI+Vew~{VpBnZmcS0LsUbbgD@fh3W<(9FIU01oPjM9&vHoAM z4D_|BimicKoqm1pZT>MsQjSBt*HO9fg7e6cFM=OCVmJFJ)Mb1D!Hcvj`E6RuMq5RDn zAOOQYxAK3seWkpSa#RaH7gQVl{|0gX7l3%>rxn2dHtPOQE&+7)rPHMQ{{i|=v7rA= z0RGtzPw5P-?jV!@;6dlu4}=u**Cb8)La{Js4<*7M z;4f(S-ZehDa`d#M$uE;rq!#jGKOhh*3kSTYh8%ym<*2`F_5+LxG37s5^V9p=e^E(* zjfWBRXu&B$HIBjHE+BI&0VyPx@GAfV+YCU=KDcqJ)W1%XK6ik2i3pbz0A~THs^4ku zO`9>9`adK3v^9A!yaAy^pHT!Zpu@Y^wb9xJfE%LZ zuq&K{wVxk73C8iCAWeRYQhNhHXSpc&iB@$Q_qywk2QXIN`B+XXrtwHIwDot zwhumtf~CkqSjI@1MW2ceNE1MAX6>D*h8c#Noi$q;x_+)w_0Lsb!IQlME|b+M8JOi= zq&LGyXxlZqkM+l(>>+p53@Eo!)rHK@J#}y@MOYEVMzfDew#4fXi3j|dVD<0#LxXWD z(nHt1h$~@=1>NMCg_d73ujvu}@=$z?x-p~{STGp)+?mzKAEVf58F$=gVZRD$OsZs} z8QUUIFj+(;{vtu`+y-pkg0)7omk8JYvhj;Kzl!Uzt z`vK8B8N1vUz`^{YGiTl1XYv5nv~cE2Fg|2 z9a#Up-EOL$3O4Fl58ym5XnRAQZS?~^o(`qERm@(m@gsd<3cKf?gBUtqNol4z*S`Fx zFPtEBf%CdqLSiD%uUQ2pgXd}Ft*y(3%OCYCTj4|l`8h}eerC(pF-BmOhezONxOr4o zXm&=~?K@lS*@kUih}EoS_xj@TnAvy`sN|9PkOlfTmTzM1R$N=&tN6AVaC^jAScuMP*pChtBX=|^gW|lKRwYll{s8H%S?6Pa`5sg~ zGw|cMGuzJqxH79y&B{vrGt++9fV*YmwA_VfG)?U#Z0Z;@TWTiU4O8v=)ta}0!dYFM z>+wd`bX;|1%kZv(!szAkFuoWi8#aj#L85j*=jI}ysv84g5nPD%%?1fmq*DYxnO-f72 ztrvQ%qk@;zHl^?4Lvnw&u^N!v=0Q9WT2ogt#Hb0C?K1`n<>v(r#KRp1qb6l%+-z#CMh3_lcCGZd?@BXyP){Qe^vmX; z!}w5Ylk-i6ms%su%<|$c>jXMN2C`O_{;z&k~RNQxL4=qAf|%a zznBA~g@o>dJwm?lHF@@XnCRM-sX6V zRn+-|Cpa#daGMv>56*Urc$_65lQxE}ZuD zassJ)%C=OXeOLbmZ}qJ2{RC4&*+_uqvwGQleBN2q-dgf%-T5hp{K)B_A5y;sZOgB0 z8=tlJ5iFC2rLxN1k%RjZ3Z}{Fj_wt>6KS@a(pQ!ui}o)TXIf`k;Z0;z@(_?8=}N}e zW)0h%Hq_x$v`52%?yB%*M4WD$Sm1h0ViU(?$GI1fqZR!Z;*VlkmZeRlrKqIfh2s0R zwguDU>pVQm%0_G5yB{L;7Mla-S7RxHN&D*$@+=99M-Ij1(%fHto-#!4)ut^S{&@VR z&1bJ*p>*mRH&+ABa&vE>`>Orw-w_pv$f3n->+#>E9c}CRrY*YbUg2p0jDQ=cMI0G9 zZ%^r_9OqN!?K|#yWD_d;QKE-_KwmG}pah_q6XL%eq+G^T!SGCy8EwT@qS9@q)gfUk? zs|oomnSB1cVnUGT_ixwiU&@}nl$EeMqDl%ii(1LbK^v#HQu&@`P6#rVFW!hd^!Sik zvmQIwU^G`XliEa-GYO~V!l@aH`PWEK+EMDGfCZazPx`7l$C zeN7EccZdagp(N@of{MsFT0S=oRN4bPRR&TQWLJ(V%@IR&Z%0U9618nIR!ge-%Kh}^ znEMAd%lhF(%5-5m3@bxpm7Go+*~8MaHQin^hx}>zb+LmiRc7!#hDAeRiwHy4_BLWe zp~C2%7oUj$<^L+|$;a>$pLzI6_~Hv|)j)2nr^-5=>bPfSrK&!^C|RlaukA>%|?>Y$C39uj#wc+_{|C2F=HSA=b;q4TD zY*_1fZkE8Peg)5ZelBj--%41ev1S{+a3L`ur(b$Y!1*;+j4~fMltz47S-F^6z5bOp zBmE@TgS~j6>Zpic+-;6cuCbwT^=rV*<-Z zR~<}Mu8dbpqzde+*mnnp2YSypD*NF@BaEi}RId~f(rpFI{vjk!AGGf}|6NDK3Gc3+ zvyy?4#s6XKbbg)lwm9~FRmJ5-=^uf{MCKZI}m30yd%=0ZQPGvYHKy@ z##~D4*=>!6Uv%=H0YyT{-kkVmpKYR=^p%&xbY;xCUUN*l{8I#j(msfRcj}2q^3fe* z_-qiK=A^W{D6xlra~B4JbsM;0Xt+;i=;KkUgT{N8W$q{3KW}UD?1V-)GAIxa<@C*L z_t{(IF_%>ZS+H9?(L*Hjbg`Tn=J_=Mh zQKrZ*Q2M9eS8xn>Qxo2zX%)Z4*+G!ffbXEcYidkz3LDZ?s%n>3Yf9 z54C%j%l`f*(*hef*4#*l_3>>pb$>8auNUI03buXe1;CR*NC^y`(sqSLoI%{sW6XKQ zT(NG;Vr}Ca8A{{JC0}VZv5EejD%X;yZG-t&^txU{P_oseEE1Ee;eIc|e$J#J0_vqQ zkkg^`l?HaU#`%F};R|7V3mf_FP`GdQR1bmgE8_r67SL+==)yXsFwNiWLKpc>4BJ{p zM+;Zy_wOHBd{*HpDf0_(Nm;kae`sP(&?w$)IaX_4+-9Cj6w-t1`Tmw3hdI?Y9x`3` zdbyHaB^DfveL`I16-cwgt*@;}9|o+WC%081tKv;7&|$wntoasI*16u97_cjQM2V!0ajkPen(h=e*%3Lvq)h3cK;+&sB_yo9FW^=D;UpL&|~aYOILvVSwrp8=v0sUPZSFoTknZu-)XG`a_Z|7~On;6F=Dj6ghK z-)o38G<=2lrDLsOxWF=X7U~S#iZF45MhHiyeG_>t3L+k-nEh8*Y0riEcfOvcZ?Rh)pNDT6zwOmeeHcdD1Tak%!xk@!_8Pi9mW1Rmzl-s{NxWukeD zsU%#cz4dMzSfWT#5XpE$iQ>)Y&GGUzn$I1>NF|y`M4Uu%$qe&+@9#+AGp^DTgU;12 z+{N?nlC|+cMxW|!A(_ALt?M=3;eax%R2yy$%=T~=`RFVYyk0CWDrwEi+#>%tTIn^2 zxMxy@`;D_~*)GDbHS7byXrD5AGjiBzl`-HxfakeSSa_WP-9-lNSz{ATHyTXHHoBaQ zEj*CV!;73njcRVS5IVIsIbD0*b+69V(eM4JJlk~iRMd?mNcIX-+9U{)DUT>$F6OUN zFcibu-Jm(ci-$xCnYnrqPR^(6)f@zrzFFrAy{rq!N`bVGSdVW1CcpR_lc72L8r>tlVW%w|=&ky_Ax!$%A`scIZb zt9*FnLAxat5#IB+gtIn_+&gslP$IQ0!{j}wQm@g~_dMT2phtebvM_G!BV$Z118lZG zn!xZe$wur`NjU@lJcLlrtj~xMal4qAws$bUgQVXiDWH@ac#v8c3Sgt5oLDL{q3R{N zDWn#&{6*3?*4zGcSk>*L?{#(mm@6v|90%6?tS{FPzo$y~QBOs`7m7CYSs_TeV)jgR zw8u}1kC6P^qk3p)Aq}SNMNIe3d0BoWIvJIv_-_-Ft>~EH9iu_HyU=L& z!1*`xF4PzgYWKxO_%1a7O_u-NwJDYuX_hyTV~h0et|j2%1dre ziHI6a3U^8kO~$G=n;!C^*T;-B*UAY7>b=tn*VeMS|5*tNVutZa<7or+WlY1uH908{ z6upIseCV+a`4c{&ByGhVvlGn)GcIVSE7(AMPAIX;|B$;eLw*tc6JItVJ;AH<{aRN< zO`@|-7^m|~>FeOr#xxULTV4bVBNLOABIh=> zNq8t%OK;{npb=Rx_;YN}mo$Jv#|1rdq{3C3T>H+B1$jf4F(kl+m|5I1 z^qgy;s#A_gp8RQUZEJJfo;_oRPvMoI7i{TypfWHk2%Bx_j|iHR3p^qjrbdz+L+_&L zVe;*)bJB?kq^*XMY|shSeFki7o`}@4 z^eC0ti-8Xt_v{Fjp3@l!_s^bzC~EW8_x6UQ>MU=Rt@Y&JwQ=8j$NR$UPHc%)&1RG( z;pgNbN(ILuOhw;$JZPURG9^VqcGW^w?nL=A$Xi-EhcQDG1#r)Y{$)FvY8TM7UJ{2k zr{u}ceA&c2{n7F{AY6;6s?RDZk1P>*7m)0frlVBZBs~GMY#m<{qlXWoqAt+WVUQr z?sfTRw5f_dQI)IX_dzdqnwg>GCia4Na<2K&gvY>T_$vf6E-$K5M^U_3+8h90)BqVg zFA|gfg8~oRCog=pojtT~z}SaJZ5vrML_%R`Yl1ntB}-zdk9@Z$=W4FcJH`3y7q}hs zKQkX+W@pS2IPxk$( zaPbR|kE4gpg9IAuHhI>TXG-UuENLxH(TvXGCN7oED4P$Edk{`C*w8%^u_D6PQGm79 zVu~?Q!mzbDrXA@~0xvx`e-nw3B)PFd*EhNIPo_GG80J&Ly3^m-@Y9uwGCl`11>4)FTt{hG=MKfv(RW|{8u;U7o( zV0c_G@63m$%`9N@io-nNc)fGTnbnHWjkdtKetYh%K3x;a*h9*W_k^|y1L+s2l*)G0 zu{-Mhib4%V63gr@iR*S|5c?1Md;1tA5-ZW`3LImM!632-t{h?cW{^XAcTO6 z6f`svv<7amg#p#6>J$fstZ|EraP)I^3n%rl^J1QU91l3FWfrU$qJf zlTE9Dc)`rnmvb1j8~;yZP8QU^Q~ePKgpN|)I(_{qOlS!v-klq3HT^z*tI6uIt^wZ- zS46Uam4m9Kt1!fFYedoKQ9*p4k@j~_w~bw_HJ#d17}dz*#~Q*!daRf z-J>!i+1D7^H~cQk6!!Rqc}D8s@BbE6Fv3sPrFK`8uuZA_%NEcrHFGijLNf|XB(1tO z)ZkV96DO}Tqr~%brhE5a=g4aH<^?Js$Fe7Mord0+Lt$qhjK~?2#qw`NG-r46kTk>agZ}>*2xp7_Up%_xhngu=-socEs z(uWepo~de}PvlVV~jo!(z-L*6H+Z<8*HcV+92%`M{f{pOer#ye6 z+W)7@iSK##!BQ>eUmcY+&WSEc1LSpY6zMtT1Xl%BwT*S!Ls(K{GOiaDPPGzie-7!v zOq8={UZ+)sZ6j6*)mwhcCKlwc(p(S12G)ucnqH=@HW4K5JFPJv+Vmngp$uHG$+J{u z_`XNA@_{eHw^yWbpYbpi5MrK*8<)c}2UjItxlcT5E1n{(ZsoPA)hD9l?knM9cW&j! zNfpP~56X)vo3s=u`RrVBO+kf2(+>YCmD(Er)o>%;I8;bR3&Srk*kD*;_-iI-hCB2A z2*rhIlnITn?V{I4yN)j@p}cp@7yT_La%(GD$Gfe6*}}9VoJVQ zOdV(OBnu5wRWWla#yT10no#T#7Ay)6C^CfyY$ydPn0q+nI@eKqCJ&y?t8rxFDm+>T zsq6Fcz2QSEmAXy8VJeuLZ&23`|K{qa(JQT=XDV|ygd0B%yN;W3ww=;Ww>z*9s~D*< zdmYYyn@JgSL6s}GUHTMIap_D+R;DVsnde-}kah>W(*4BmHa`qEws(Fl=U4^!-IiJK z*z>}=oJZ-tPPH&yiyZmURx~Bw3cEVCYPVBhyqvl^w(zP-RZhw0FisngyMVS@9TFih zeAZG(C_CEwN66dI^3>o*UtGil@R`?MnfWT#-rR1@CcP>3*a@<)uD_jA(LwIR;*c0*bMRCLhi$~pAQl;5Nk1cwp#P>qNqlLRJ`C`Ifhh*e%l*0Rs1ZL@eu$Q$}$T_ z)qMusY75Q3KI&kc?Hf)EvIIxZvjgFSo3AmN+Rvp^nuVhYdvopDC67w%+w|nd^9rs-SulKs%{{a)gkl2rFpj}pXX?*h7*0l4R#zhQk z(s7N2eg@%b_$!en043 z?t4ycjoR|2SdMz#p<>N;K8!9pQ+_JevtZ?^;uUN++p2dS<^98Ky^^sO<6(UyaatLq z@5cx-Vh^qZG=a@|MwG#u`rn}5PUbo%E8oJ%_mXkFe30jr)`Wxuat+U$i>h?#9!XSY zzef2|UCx0*#A(@5kWi{M`p1^@ zaFvKG_{A{0={}$Y=a9m8T8{N$5lHerY>H(tJ;RGNybXHyqc~NXH|oRXi_m)vWY#?W z?fY?nYUf3-IrOmf(gOu>#S=X&vuCr73f;RWpih5hqIkXsfI}i|;JswQd5{4A;0Lms z&JPPBA3FBh0Dse;>pM9?>UjPKkGCB}>N=6f>0<6~ zlG=EZfxbpO{|xP2I`E`-K&!MR>pt~SlZ@m?I62?v_H>;ssF4cdqH z=Yghi%VLz(pX&fsH6X!B2W5OBHj8H^Bxtd+jXk2rv!pSZ=-^}CrkShc$c}rFEFaXj zsnWm@5C`AydA1mm{v(EWE|%`&fu`TU;rm%0(53?z2NS1X-H96pInm^zN{fmm76LlV zOUHl?^Yo$pmOsTZp-gy%|D5`>I)Os@z9h-5aj_7r(4V6z*ExCFJg)#8jeS=fyKHEO{vG|MwkQKckq z*RdES%w^WaONd)qlFwND)*Q? zKk6saVl%iENl6E@#?*u$p0=ChEQ88aq*wiRVHr~YF4$9QhMTiA~w@N zS0CNS^Zr=S^od+Os!|NyB_z%m>soD0`@|~4WhbM5=+{~0o(L!#W!r_$x6}x%1qEPE zo=%Wz+AF4|;ux^&J3CL0Cy#2ya`P!Y_Nz+;WZYrK?${z6zIEK$p`cT+l;18GxV>Od z!mz4D#rhl!uv2o4^BKUPc&HMg{pDK^)n;WP@%PGi^BFUl?+?1>~u7@%Hi!V1Rg>n^`_w6~qEV;~f#znZH%tJ$tTgBvCT(TX%05Nxd z_BE1g=qzvTfdCD)@KrSft_-eIYP72$7a)>z0Tl;`k$jcXM2{({6>gE>^UA;vPBPZ9 zCWoCFGvT|-3!8BJ^6&-gdKc2?@!4P#sRhcY;TKZ=F&~HGf?#Bl#w=&kUV|osW(-T~ z-izj5Ys^c`#-%d**EY4mDknaen2s&BB{4GY(UOnafoBl_L`0HjDaXHlhz$<;^mJ~- zfs=0j6tPYN@#kR3Sg1k;kOgl*=@t0imZ#GQ*b?WnP;ecklQECP_A=dEic@XjnzZms zu{QDcgPQ9byUkyV`H|#8p_s@cYZeeUe=StA^gzbMGCg8pVSHT?G1_ys%E>#T^ZnMU zG2yG!c?RrcockLr7$Ux!ck*)bDH(i7jin`cc_688hs|TIFFz;$rQg<2z{6r@;j4V( z0I31@iedy}Dp!YI|F8$_Xm+9NbK#`)gb1IB-F-r##ct(nABMA8tRaw+2!l{a^fqWx=4<`YlA!WQ@6734e+NztRLGgm+w^p(z%s)ce1|6pE6 zi@C}yvCZmq8J}pW4`-w}b8&;r4u|jygPczUO&^R`QDkry1RMHNKLq{jpOH1)^)@wv$k z`mYk7Nu+QYLd;o1H(o4~V$@|VjLZ^MHm%a2!F;Er!H>bLQcltiDvuyn@U-m-y46$2 z`*tRdZ?R7>GPrUJu`)Zl!qdTOwmC8wqJgK!>cyrR5r@{kf!gY;aRS;@VEO9gJSYuj zQfJU^OaG;sWC(xas}ylNg&c|U$eehYm>|-g#2BYseaVXe(3S~|C@~jvvU3MzVTWR% z(kXsgz<#=iU3p%LAaxf7En~y8;^0-wdI+yKJeJ4poW(wE0d90enBt;#c*wc{%jaUe zdIAAX?5^q{R>%P7t=6uslKFbuB8~>b&c~X$q$Q;eWh{5MQazb>a#o{b)jhRTPM20y zNcbFKRPRJhX0QXe{^7!xe+G3_wUY1WGKj54XT>2hc2y%j>pccE4;%wUOENR>{Ega* z&NWm`%F(uaxaggHEaZ8W0uLII0*drxih{E8NCdQP&4mj}k)>QQ1xB_u&_nq1Ho>KF zf1x06?a~b?Lb=T=qWFTm3saO~=E&ob?{l{Up`$_lYA1`odkPQi8+Nujjs<1ZvC72~ z`|#NSXap`#qvIgyRee<^`|qz(ar9UfshPfahccm`ja&-I(K|bNRui(RwR**c2OY<* zfTuI=KHYzseZm`EbjNo~FcJY3>nW{qRzg@%>ZZSx`Y2|-f4s!UdTB?BMFC!L9)-C_ z(X79mm8xl9cTduSKB6;6Ls3`&1FVi$Hv;bI9}B;=OE#q3lV?V| zl~_zbLwVz4SLUp%oL(IFfpFmQiAVjpqpw%)*KH!&I*#Twn+D63bu?UngqIj1uJt&p zZGK#0v~;n`QWn{J8B?Ax*6#FTmD2D&reoG8JI$Uz*n_vA99Ck&4`o2#B>xdz5&~=M zCt%mx5neW>?~eyCk7Pt1<24iQLU7*KEW=k{ir*pskT3a%)Z1Xk_OfmJzJxiUB14mX zr{iS%b`1m*i=G`Shp&eD#D0$aeZQruJg@z5-Unc3_o%g=d2eKO(zrjVwBI9JsbSeV zTBnBSv5~8NVtE}00+eExAhJo*vIWt2No-J{ewzh)j~RR>Pq5<4i6=sG$%U0r6;Z;1 z9`jQ(gcQwb?4C)<##rJE~BdZU1)F?C2?Q^z<@xu_Ce(#jr;fxVPfH% zt~pjgbfbdg^)~9`*Pv?_YAS6rQKVanhrrDqe+DRU7lb*XTnMfECzJH1LviLO?N6Lp z6QitB$!W(;{3l^-Gq35l! zpx_2O(H0y@cJSIL{0r5H@uW+O0haA;prDcyvzC>GE)EoWTUmu+`D9csbpc)gvraZ--M~^ zP=_UmbJ0HK5Z+3x%(ExQyh6qo+F~aU^v{gmH3HqjMu)X4iEmHh*hytZ-HZ#go$B(T zao1y^OtQc7xp8_wsK7ZOVI<~sA{1X~C?^Xp+xn}l$IsH}IVrGfR-^QZ*jyfJ+axOi;7TgjN2puFovDBGvk8*Lys*!)qs z9CEAjn=&n4n@2l|sdy)jA5Z&)3JivDwmsFlvoOj0MH+W~b(8M*Tcx9Vor9R!Yn26y zbWBt*RTuBMD%`0*;?79Srd<5O!vB-dsTvR=h1HPU;*UE26+I99H1J;k6@aCZ+HI{o z$Yx(IKyO47cBv;1##2~=r81(L|E^`gT3unVKO)I^+i9FE%CwW~{^qv%o%E$`66S<` zq4)V7<8kKw%TNUP!7L#Vls}byZHgiUyA5FVL8S_M#^R*w30=|##@AP6!TzVEGlRCR zv{A=#$T*`n#=DJ{M>~Tgw3y~GXHQg?{Xm~3J&>w}nm?3egRZ)%5QW`TU^I2FU@S$m zgC8-Q&I2nWz5P^m<{WB|S_$RB>vMP6%a3^xz%ilw;|I>1$$spfP)_^j2kvt?IpDhT zE07~+VT{k83cwxWeC$J(|W(U}lJ&53Vs0!n#W>!~C^(;LWsmqLoX{d+--dWF64K*7+?T0@{{;=>!EG(+p5|!mX&;#%1 zyZ{YB;WVfrGjUEhsuNhgajx`Qz_`#)1xvJ${_%fYE_p!a#_?J+_m2wRRX}s=Srh72 z^sJcx_xodpr2|@N&r?}Buw?W;^dKhk zkoVT8-o1a$Jvaw$4*nA#0gW$!CyVPAjC}Zcfg1)I@%@jj=Oc+UR;81EeE|hxHV=Rt zCb+}xHoVvvz@3fH#F^Jc7xmi*pE<7qw)9W*rllhfh>76%7)rJ@0315?)bERH^;Q7- z+rld!{=s?FMRp$F3ObAaU<}Ou;;$Fx?TLmaXW~i#Ym&rU>IXn<^POZ#e7*td!wa;F z)oG~W)B!EN=XIP4eadK}ZFO+{CLM4TSwZd19Abe#%l)p2@;hzzo@fJGvMudtuZVnJ zaTCX-io8t*cL0Guw;6_Xz@aPB=?3}tQxeUgHtGXEs_e#b!r@UH0qz<#aOx$_Ny=C% z`@f~mers1%%6QCY`p^w*4*w5xZypcj+xHJ!LYsU`l3fy+MA?#cRFZwl3?f^$?1drA zFr^3~YaxtmW9)?NZL(9$SYx7uu_eY}%y^E`^}X)rzJK@cdi|c?^IXsK{B_ox&huE# zV|gF%&-?TF{2o5vOuBc&tTcP@fZty)C>xcFElbPKsl;d0+M=TR2gGWt9y40s!SdAI zKhDIK7H1o6RG76mY*vc2M{9)SQR7N8{8Onj^yk4l7AJ|!$-#_cE{}PF`^lDTSlUX~ zlPY@qOC;*cce92N1>Ww4Zko?dS5s(7R#$<-Z`Ll5#W+P=00t)8@)H#wL+4K-x!Q3y zi}dIN$G>V)TAos$^CufBvLSnb!GAjWJ$>*ERbX`WiBHSBNe&SZ<2{vg4w+5jN zxDzyunWbat>EOFsqI(#FvWu?3TFz%m^V)y+Bp66;6{M^}repSmofM`Etl+5{6aLKy zF_Zl}N8cPeuopYNh$3n(T`3AV1HH+ZlD6Thj9cuFSzV`xFIaRn4fNUtTZ!y1xBXf8 z+J!z37d#j>lE<8KvD zr%BbKS_+L{Pg8@s8o{rO=gz11RD1ZdWF5U2$lNhFGNt+A5?3v zYTV7(&kjGVM_otGQXd3fCyr8R>yaAdY@(k(*EZ{Bz2pVKg&)1o#)Z4+#T;AWo@2y* z*+bj*e8$P+6$7tnPI)5|jkkrK8b#_^unj)mY-k{bUO@}wm_ti8=q0``GuL8%h`cyg zs9z+8Ug{W@vcBqfdHvOGYUD_Bx_}4L4%(A-bq}1+Exm{?`k-qh%d;b{5wX*4WiwCz za&O1Ye`loS9aBn~IWz{fodYai zMO#@&@yl_iE+0PU_S;QoNR=y5%x%o8sHobTV1HE+$IS3{4@D}F~Y$yl^}`z}kZ|FvqYNz7d} zdU}>IA#N{1u3y`4j88r_p}dF z1X90_teK>4ViOiQboJS)$&NEEYwAbUG%0ukB zL-}sNq~0s8uQ{$clG*E1UsNLrQX6ZH3tt2Uxj`uQO0;{*TY+_B##R0aw2K}o1I`Bw z8a^crJh% z31YogY!L{nR5wh^2zF5rfmIb0f_0?0x;xJN0=4SArHOizL(t@RNf0{v%8YJX&sJdg zY9FfiVqYIvE*~$8yd|ge!w?#@=W(d@DiP}Q14l>|%M_2XmX}!Kbrg)87O#O-9jSML z?8DZ6+QN9wxBAI|&l(Qs#&f^J)J%pi8rsi(T;v|?L2ln-wx_EQre|8b7u`Os`s{q& z;puI`v0_40NmP%hIT#ZJA? z_6!3RgXPb+QJN}In*RJOu2=J?Xy++2>v{80%Q++8XMR$i8W$mScQ`3K;vDo~f)@)D z(^uaC7LsN`-PMiM?(_CFi?6A@XIJ-PD*|ASy}tNxTKLz$#+q z-~=XigO}>D6<~3&hXc;11th22>5BxRa_QUuxROsGKD9>SX^d2}96YMsKlhbz< zk}uNLmU_iMh)Cg5iFaeysUx4Qc!hdpjCzr}_Bup=(2}dl3g$Crf02ivWy{MNkBak8 zh%B0_NtG7`9ZhqlokXw@M(W(0Na8u*g8KujV( zbH%ZjXRMEcHr$grgzdcsMefDA*-2yQ?&26+|Asbm@}vDsOs1ZCP=w*OuI;^-veutJ z#@(oU9Z96U-`Q;KYq_h%4Kgw_F%^G(%uM(amMhrc`$beLse2j(*lKZo(iHOZGY7bx z^aVti$WPT{#l3VQMHe+&-Es{_leVh_yFj3BISvRD)9DAVKomrGCy@8&3A{PdhO7>3 zX-rIdcFj5&u@qQwKxV<FOI>;iSj$s_E|LGqENJ=E(R={%>~-7WT@<()Y+Sd zznE5;??J=!$kC3IWIcx50_Jybf zY=_LgQNe`gd)P$ucwz*88&cqf^N&exin=-6e)H>AyAN^tyFd~7hC~g1*ns@Hyq!W# zojkXN_Z^;EBQ|HmvoHl8$G;nDkuh9LVNdq&3hVvF9g8i8h} zN}tA%A|MWxD|FRWD(@IT1rOg)QWp9amDJBI%#OHJv#nLDLih#BBpl73&=EJQoNZwRwD9CkcRmUjQDE z;F1TBEE5y7@Gr(IU=T)|nUePZ|N2(q z&jFutC|X?hTE5uzrr|TMXI)h$by(B-lNGrAjUNyigqpJIBKEkd;o9jzS z2~(x%hkH|FQ0n1&r|_LABe)Ol4k@vOtA}CJ@4)6$6VQ}XyXYt@76(_^9noYn;C6Ut zc*)W1qnO>2wvGpp>lTk3*SddBMor=Cnk&cEBU`l~*G#psU!#l<1& zu^9?13%08~861QWM9Dl=2=(gZ*bf^M`70w1Qe!_H!`En+n-DSzzA`*BcZ$9SV5giw zG&M_likp$FvAlOY_n5ut8qoTH<-chPs8=ikOG8FRm%i#nVW{-X{n)@`ICJB~zy+$c z+&%a+1F`CYBQti*nnj81_?pEah@6M<(u;=n5(hd{+6LpR6Z4qKVJ+K)F|QrcbCH)d z#zcipz4z?J75GU@ElvandIdeI z3&HAi+@#v`W-~Uba@~A(c(QTm=DU#Tu$GrEVL6H1V|fauW%}bbbu@&Y5vnMz_CM#0Bzf$@z3nkYlu#|I z$6%Ljo?mLc(UozIi=@p1@1q$1FpGC^)O;8p&5-eXK(Ho_9u2O+T4$S`^`>@kq09G; zL;W@H-!wi1mcqRBBQVVuWvQKkq+xS2t?D zLPD(~BTecJrd+N5YlT-?aEXUs_(oP~f^>?b ztCbtYZ34lT`iMK5s}JkSigCH8HuyiZq&|w2@ShAZbDjh7cK)_fbF(JPRUspxqm-$8d ziJvJGD4k$8#|PV&y;jfc1ublP6_#5r1K$X?zS!r2a3TjChn+T#juQy|? zgnB$Lo%XwZUo`l#mhoe=S_DpS*`Gm*zkAL%MQY|Y z=XoE3^BHK~f#AUXBE-V=f>O&>HRB#bi#hg8UR;}AB=1%Cb?^R|$Oq;;L-J?D?!Con z#&V`~ckRS36lZ7gk~0Ip&W)+O=)`oEiGpcD21ykuJDsDWvYO_F?I$Lc41^0XXg@AvpG&8-Twz#|dpHs)wi|B<9G|IbdJmpom#RJnmj)+Ux?;(LiuoIB= z|5TT$|E(@#?Dqi7anXO9d9~1rY>b3ki2tpWOk|)wK+3P*xCpl^CIej_0{~r9Qb{5t z`$D-ahqbNJ#~LBcaYeaQ#qpoJwfQCkJoenRfF=vcTITBS@d9BrHcB+)KD!Gn4jkH5 zjB)joJHBB>rJjFMYI0%(kOnm>A4WrdyF6<&I)HV7#_Vp!Z$65#y1q!&fh;~y0UuAk zG7D`HQc%;Yp*`Mhr>R(zCWurK*hapwTmH{vUxT_C zpG{u*^6)UQ6L`^~#z*O?tc3Hd8yThT1=&llTnm1R0G*_41K^;+6k*#f(e}YVAsKk= zzkry%*cTt>a9-fXR}jnsT>d|WWalFW5JXUkHy{}txG+#8xcm=Q{-53|J%v#L<`D)U z8)Vz_)u>(t1Mq)`#OH0#*ymXhAMGjss52~epNL*J7=zpeT`FEft zHK-D+rn^}uiJHf3>LYq|Eq6yqJHSG2Vrjzc*Jq%?P3N-H_LCx)jYol=3Lx97f8#Z3 z$b14SdJq26Dts5c4_Ie(&GMH!(K?316W*LJB{RtZJ;*~9QoI`LTmG@4 zWA-;)n)1pTfUC$A$=e=gHf@?ur+eemZ+%NKFI?)`1HbpUB&Sp@w}xcATNLCt?2LCx zq2HMftRzm?=CKi<_+_EPNQ-I|jFnriNF^vrexalFq{(%~Q3)L-kEeuap{D28YR9}Q zeir@Wo6WeQp`;(-T{ual#=5b(+UmXLRG(vmmE#wPsP#?cCU+-jZ=wzlxHn>Ff-Gt0nowusmTXtEt(}Z(5jA1xvY8;YSKE^PN{WxK;WxGFK4}3ml>^DcKPsn8;nuRQyEUI2)(^5 zbT!>3^v8#R^}?m<#V6ty8?CqR=}&9L-%ziU=6 zcL||xj1bo_IVu<@S=*esl%@PM+ZTAAjM6Qz$G+vfs|!Uf)GiCOpxjBZs!r!jq$8GV zwPlh|D1KJI0TR2a%O`ZC3Bqbug|w0S&G1#)eGnT-5?Aoxtg>IZo?W7^d9?fmCZ#jhJ$+r4F>6gstq zS{C!8O($9SlZo489z zBy$@I6=`73hN@;j^*#-slvhl8Ze)U+(aJaqtK!ZITzV7$^FhJ544jAT5n5!4-Z0Xf zH}2`1kd?XvdW$Q>Iq9wRu^eWE$3~L^28Ub+SvtXT{5}c0Zg<#vDL2}GBI_?Zz$P!> z|56%UgXK#V=UD49Kdo~rb+(X|1eMmQd^G~nY>(D{8CXERJb+c)JU;O4BS_nQ_GhMe zp8o(d(oEy5=vP%Gsk&4(9E2(^RWZK~(u^5bybD>TQ8cH%9t#b*#SW+HhsR=eB;-in zb)mFHYRh>dg0T#rjs_s`q}WdbOVdM(Z#QUx^;bE1Kwc=E%hYqSVTM(0_j%l_04+8| zuLsWfYx`MY)h)5j>)Y#3+X6QRYHq^iZBCgDcV^rUqy3~2+htqmi_0-UjO4L>K>+3z z?KQXTYxk0|Yjxh%gVM;?95(Nkwi0n()u@)H>Y?&8m$=m&g%zAHs3Xo)-hucOSDlF+ zA@&QwW%pyJx^GkbcRzgxJ`jSPMr3JPNhdaTF>LEaBu0cjPF+@2 z!q7pj=ictF^wSmnXp(!NB^4LEQQOg7t%L~~rukFl_{7O?iX&A6e~q;;PVi9Oqn=Yb zQtInLJCi7av+@v!Vq}R5hMt*raiJD?fiO0Wyz9Qtj_S=

4HRBOvJJY|9u?&KF9H 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 literal 0 HcmV?d00001 diff --git a/docs/guide/img/irc.png b/docs/guide/img/irc.png new file mode 100644 index 0000000000000000000000000000000000000000..6e4f6097b4256a7fc28211a33049c7beb32e3527 GIT binary patch literal 118232 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

V}5 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%0AUHVi9A(W-T@1CRjdW~M8q2GudFigMYjT@G!g zqr4^%n~o#8<7sa|LF@RA?qLCuo*2|Lli|+ts;8HR$=%gr;T-C{Omk`b&e36+alITS z%CsFk7k7V`81Gacn{tPt)kE_Q5KLiQ@Rx8U1)7CqYJXRl=`SMYH{(oduYH)DczLJa z1vil*9+DLT9^3z2)$GPJaRfpo(3B^M@Ys%hOF5+B_&k;mMmkKZJ^s<2&{ za~iA_kosaTXt2is;y_Gw?=TX)QZXZzmBxulOH7o}bm}(&qf(XEGyE=$xt02JxYp#T zy0eg~hVJjf@FU8embE=u7mPURe?ZMs4xQW89UrwK4VG%HT)`giQU^}L07X-_r0(7-YdO%n^`0rZ{ybHIn2GyveA-%UHvG>4n{BYGw$s2R2*5}ZqSeVp4 z%#`Qg(KEzH9DDX8r}rl9#|TO};~Du3;=?|&j+MM?S}Et5QvmLUa;)721*EpKP2SCw z^f6lq< z6o+A96tBFADK|tZhwjGl*(fSpb9pajPk8p}JRd%2`~wRnD=FOP5aH%7E~ko)OlQ)? zab-)yb}CH=EEdcP+w;l%g=OUio$hE{sE=)+@8@!Mk%r ziC36<*OzV#I0TF@51Ko~7(4;U{ZF&f+fVHb?b{!Y+Xc}X&Fk;8qIW4Ahn-GZKY*DI-<(eoW?_<(IXp01s zz#gwdGq9rL+89bSB{DMl+F0SxwW}4b!B{c<%ap~2t{CwEh^utBgYq@( z>x_A2zdUTl5BnwpDiXz=WG)|YSd)89#`So-&HIv30G}9C;o_4yS+E%#{<4ZOgeNIj zz1Rg)d5sukrG&l|v;JzlHna4e@2A`H?SKqVq$|s)HH2I%=d+N_Fzft5aO8W4dMo{E zJSWV=zzj30>AF$(?OlQUxv}@p*C!i-EL{W-vwNWYYzqLNd2_9u6-MEqJ<455y3_tS z9NS{p6QMc#&pgg_MPLQH=h$%7hc}j*)3~@yf4IS!JHK$I8EYrP@R=j3$%yeM+N}~f z6Q~upFm+B>r_atuyH@h7_@Lg^Bn=kQmyhp{jj89TEas&q(M3O1+*wx60Wo_$_K6u- z2}h{%L9@+w=G@CM#EU4E?%eEKHCfKVmy@oQNI$N2p_(>pOGu8P2`U9zR|7vp3$il_ zsF6>z}FHrzXo zvdU-gP`)Q{_w-JL<=*{RBjk@ki1{8SIDpYCq^cy4JSFc_j+KcHPt?-`m?X3?fE}!47TX*^NDnFlbdZ({9lUe<&MpD7o({y!o z7r@9A$)F`AAu84>a%XidKabdR@57A06>F|HrW*l41S}fYu(+|I3)i!xIzTp(7ZTx3 z&9~gYF1UQ$KA3m3&zOaT_+ir}_pv-NI74>eQ|R)L$gr;(L3g|=ylPsW2Wi0po7)=g zt3!U+40hk_pPae0a6guC+{RnZ&b#spZQa+H}$b-yND6$Ep}?N)-i5^NPI#) zj4s>8j>Dy4!V&MKTtNEQeBFA~9v%b8jI>$|W9On)W#>xxa6P;K4JW6DACML; zf}(Y%1CGfm6b<;H^2o$s*u%XT^#cak&-MWYT`b7|-smeF3Ulco+`S>y#JSzs#|f&= zD$NI_Sh2Uct=$;>z(o0>=5cdlUO@;SU}VY{G-V4Tx}@5?hIr=LxN z0-OWwZYNbD7`Jf6eX31;O51&>R608t-xY)xypjPonFuG$LOew*E^Ls2Z-Z-R1Tn38$?V^ z_0`fJ=z*I$MKoeSd{BQsT&qG%G)4WSD$QlZ<=jA;bQ$bIp^FRKH*M^0xgsjBl1ZhE z@PNtxL`;KYjrWvSch^8cI_W#@J~KAP$2zutz$hKQVRTD@nIweWnu+K1Iyrr)_=Yy7 zgo`WClyrT_JjgfL$9kskOG)UifM&yTnwB9-?HXLqtd(#ze)*)zt!FR98|y!Mrv{^( zf0-$VnWSHmydRV3T~9QrxAap$tCm~Z=ZXw1Fr8y6bE1j@bD}SF&c)*MM<{qqy;fpL zO$3@5Mj8@P%b?knF6WD=A-c~O$g}LV9<5ltru3Fn;`pxJkyGAj$?@3tA#>|YZY^h* z4YT+s7)rY_&8XwF3s7zZ=iyVEQ1@a{dP#~KQB<@;`GAMMHYzsk;&}z^#CG06M{;G5 z@9y04JNYv_VR<2r$Svzof;f6iotB~Mc_?gUX*!M8=zMSR8Z6U)6*dWVl;?*gbByb9 z;8GInkfBA4%;_$Nc1Jt+Ir3!8Rw_;&uSj?y1}?41xzOKhxShH3giMYUA@j!7kC3#B z9E&I{r0FA}9ak2j)hg5PCDwGOTU|3Mu|tj=KqG)k{9Nks@FA$>pS5zK$x~c1VM1RN zniie7K4iIL_BLmYXtEYk2lzFJZ~5l+8{u(_9?roY(izKSUZPIi+HA>$kpQ&*`anp3 z5tO&?j`jIWgpMebu{PvoTJiY_qJ)pHjBln;PvpB5bjXmSOD14-^fNPDdJF$DYcA@| zXBZgI0nDd^Vd%;XGgH}N} z2&zB3FudyX9#Lfj2eF@6X&K%aLp6BJpR0our zA94!_3<$VFaV_0kNGa=SwlMppdJs<5^ZK6%igAo8RYw;^Wf~*jCTissJCtjeDA78L zwg&4h8^iL>!j`|ZSyV)v%$cdRX_1jQhPs4mK$Ano5jA#ECBn8@3)%XTo zy*Q@we64EhBYYze*fZSR8riNnoWejr3<$8$K}xxjroNnA`j6}`!`z50r>Vzez^b5rUN)k01XVn3gP;6v@Q&dYM$yI@Nedo7nKZ`-7 zrY@LL?jQy)EY@P+yV@SF-ZQ#hXsQT7m3%GZ_F{6Werh{@Y6%&At`|g_yXLaa(_n-C zSdMLj;w`(1Cc&xNFgYNna97{TBzh_w&uWfX$d2ByIy%}58pxjiC={Tcu?oaM;@X6|WitXxON*v2y85uGZEnV^w%*#@{D@nNZ z-IwR_(`^sGSLOOkO4eOx=_028M|?F zh;VjU!K!{i|K8~}M(`6PHK#+-B52!U()8t|+L6t47N4iv>A{(L?!c^?0C1LzI~O=+ zmeL%#D9tQwP(4Sgwjg^olD&qkt=~<lWH= zrFnoavq~5YK$REkoF73t1dtCkwmm4Dm&ik5Be=A;v!Pu~LsR37gpHjmH!_1YBu3-H zW2U6@d`y*v6bP>`r%;_0c6#4nYZqM zt?Ijoj2f+HtrowqMM#g`Gg>63aXo*Y7Fw4NJIYRo`SMG^$#pd$YNr`@@|o zh+xB0oX%<#&y?4R<-^?ku9K!+uYS#GgOD(?FHZeR#*x7}yZkSeif{W4t55o1t!ovV zH0X*0>2MR50Y0cXu9Fo&Ca>=vBt2gKwqhk2zfta2=M1Qz^2ddGB8FQtesr?qE|f^} zV8$a4gj-3^ zGi-a+>KEGYoSLl#_5Xz1I;|*%f3y&m2Ly=Y!F=~+(5ZYDsC5o`Vd#lgW3!vB;lm5s zKbpEPq-7x5g0sfQfgP?FP>eKG@69s*D8kQG*0`u+rQz$}z2Q8IbecGtUs=Jg=pv4b zv$Qqdz3vWrErVVrd5nlr3YMjq;kWrIg4~#G5_j=7DbiPaTL$m1VE--ZXvP}0sQ3)( z2;ESh9d}>NFsutd8am60D_IUb+;sGesCCdxfd>9n4zK?c6_+2HmHRNKKlhyef6+xG zo?wQ;X?8R=HGb$)QHIg}AH`%WRl8Po`jV{P&7ciWcW@bDzLhU~b6~P|mKAn$@&iFa zj?9B8=#g275{4F6JC3jH<|4XN>tQ7XKPfs7&GB8^LrO_wL-6t$x*u4@^IYb=L;%fN zN2O1CZWi9lEOgi>7-cHDN7TEr%oTxERI!xGewyPPsxOv7{452CW58&CiesN|y*l!o za5+Bf125d^ap#f_O7wEYP`Hgq#nJ1y^O&K>sbVK7c8DKoJeX*67{z|>tR>~%4z=r< zD|8`B@v^k);zoXtvSn-!db6Zwkp@VVlZ`sssak04S^C2uh2Y0|n+Ajs9Mxay4eh-X z0im*dY8m7xpmgfy`oB6O$8bwpM=_%zDxe% z&lO0*utG%zox7lSs##GzPbaU^u7h6Wdio337QMU%slR()%W&wiFr_+|d{TglxUkEU zrf$rE%gaQNj5o~HW3YE8^Rulh#0l0vj6|V*5w|@&QuCiGq5F&N_GONVB+O-nBQ)C* zs(ZdrdqOOeiyv+g?HJ9_6D(Nmusks){~KRZwx&JonEa2Ch=rz**-Vc5<;>n5&mNGV z!SK@j)w3IFHlhLYSche0%heaq;MKIzY{Inowj`iy(JufWvpwzMA2S8Nx`j2fdzJ9i z>8p~z({ZG*!tWRjh&}^KjmZ{F{;G>xPvkA#A6Ay8RCwRLAFiq14Zm-TN_{N6q$UQ< zj;3Ws``pv-8@%`)dQ{FN3ee}B1C&`&Y z)}D$Qm9HIDG(_|UQ$82i#j?W0%zv<8Yw^T68ug6W5oqQ>mD_L-n$E zWvQkfSseb;7ks(LNq(_fsL6^1xkSwVKWfG4|5hvbO;MU|c2;ZJsvEMB`_yi@(kbH2!4fr!rxrrd zuB$s!X(&4B!(_NkEW#u$I7M5odHZqw4;$sqyL3qLqizo%xp|Vq?#bZn2Ry3=S_1S}&rWgyFp+k)c`)Q;*~5=I!;U zMWq2ScUpiBdQj+>mLEczD7D=aN$EqA4BXe*;P0&s_|H7{`}1KP8LOAnyUQ!FKgqRS)-P zt5rr@?$nN>h@l=MpvBLlzbu2`?N2M zUtq1_oYQH3j*Y>vS7CmTl__A_U;LbQ;M&OOj;*cL0}iJYWOg;YMALoNzl)1ezOXTW zT0QYw|K;>m<(xVK6=8+JW~;Vy3k`I~9BqV3_{G4HZ@5nV>>+dNUE(>KkE)Q|*z5Xq z2z2~D+)&WyXVcH*8xe8vH#|y;Rgj-;1TT(`t?k+K{iwaTTF{K6?qUQk=}(*_Rp_=$inNYj`svwr5YePl5FT z*0hOB%%tbupTeu=_fy+S4+wl0fmh$N2U3X# z_@ARs_yCGNyEh!sOzjR5V8t~jNDQmAOQRuLs`WKh!CZA`o&jfoOv#qtWBcs!t&Ddc z#-Le!1MPvmrHKp-MwZ=XA!EXa*W?KaWX_WSo2}knXs@Hd$0{;$Ndzjg1bJ8YyaPOT z9gY=wTVT*PGKMY@&1f{0&3Bg!hIT$a(zThO`>3dUq@322JJcmie?^C#qWo7A2@lH8f9bF#&JpI7CU0qFC;+q@#iI_e_g(`yE#NvhdF>W&O8}s|@_AJ|6bdTqx;0qqU9|^OL*R z*S;Ig+z_5R-klp};EBEIkadtqdu#22M9YIqy)0OJ<4?X!_Zp!WTa&-8AdMmzTN6r zrA4b)NVcg%o_SZT)MboJEN3IW1l%lYbT~&TD<+Y4wq_ru^If@5b5Tjv#BFSvlYAa$ zMnL|Mxu7}P7dUV$GmkZ%g|wgsogi=7jkZ-6Y_OYk3ubY3_Qn1bUVvw@Uu=fNf| zV{oA+nzTvY){1Ragp3CW9jBTws>mDz_=_WMo}~%<{Ku%|7(wTN0O!sCOWBf{gsqy+ z+Lg#;ZD8t7{bN%Tso5&SNJ)k%>;2FyF*8uk4=2BvF^3!*t_RKmXGWd-W?|K78`l$* zdrC);w;3sCN)BkN++~KV)o7w$CoV>Qa+t#S$O9I8#ELtGTrL`uk+y^o@g=fI;@TQf zsji-sl4nb=oS|Ivyg1WVx!dO(2A9ffu+E`s5j5+A25QKeoN6f`d|l10gI?J^Yt|y% z11ftkF@Ej9V!Y(B1Q2u{U0>4x_&blI>nm3flm?O?iI|;qNOIsT-?x3<9bAj(9Yl<88S>OvX*-`GMVmxL(5O z5o$uJGnL)0r?C;ND}5TMODf`>3v{a=>+p)*^^aSM0_;|<-MKdMwS?Fnp3ZLf5l2lI z0xBHgy8crXdH>S3uHk}7Xwu#9^E&qX)B-4I0BVnEV9&v&w!&DnG14g>5YvSM2Yyd; z-e4$y;_KcBVXbt_9Vcd#;@o!@4s|2WPV_X^qWVM}eR`s5(#+?_f2#&R-FOE#UK*kUPQarZncF8VN8I?iWZZYAS z=)e_w^#GndpElbH?R!qI05HRTFK9fk9+1%lSZy7|dB5_pU`Lqkg^NlLZ8975zcmUG){D#!+_L zDAf0~wk>uah5{53l&5u4R_;xZ0{E7|h?WX_@)~5d_@RiKB%%0a=!|~F^IGYnHHkPQ8pLAkjt@*b6`mHZ zIl0v$zE}I@_WCn%oJ>Gab4LJTxcTcf4BCdSE4|PJmU5jouFfArZskzyBcwin67ZvhB$IOr`_XnfaG9*-E2+JGkN18yc zH?aMxj7VK3f6dhA^@$nINPIkt%f^*FRdmJ^efv&+Xdit$nGyVYNj&Vk({7Z{5{pF0 z*SA$aF7Y~6Z+vdiuYp$0FzxxIQ^k{gZaJJb$g(eJ;`Y}wVOk)f(Rp4{g2tu#tc{G7Wn9I-U4 zf-?8NSZk@>%O&Ag@9^g_2eQv~0n;m4a()H8d16izr-uwvQ%(U^+Tk1UE~lu z&Jkw%B*e0{VVP70Dxh%XLB-_6_%&*lOe7XYIgR{eh4*BlMu@K7(W*!$sd zpg&QSoI$k>UVZj4Uq(7su5dW^#dh}kLIJ$Mc*H>!jp*Dp26CalTzV47H^#AW5WZz> z?Mi*vSR86)SaI3Bs#8Fu!vHReqey!P zedL7I8;+!hK!9`YEdvb8n_%>&&viijv@%%Kf_`*A2C0DPqo1Mk4%hW<3gZmKy~ksX z{&>Wp>~kk_Q;gVfptDt$mmfpckayN|cf%2LECUyAT?siFGRe}_&hiIl8Y@aqWndV* z2~1_+Zc)}IfIaX`6>4I9<|wM|?&xCx^Ncz%t8)ry3TRICJEMP_lR#Rxp$lLVbq>7R zR|%5_KD@ohP*(?bp?ug`ZGJE~B_QG}qQc4&RGF4xjJ!ms{`mar`@^ha#Nw&F)t0yW zct^LGD0B1ehWtzv^I-Q?2|Fd=ke)r?_gY38RRO2UpJ2w;?WYWX6G0)y0}im`5C%=g zlzT?n9)X4G=>DpW*48gFBdQHWdhMN*@NmQd3EEn+KY8wiUe~JBoX+m|GFRGgoEB(( zrHnVk3wOKuci8x8L}33#EhuF15-(`ybIV#W$m+FEBRkIH-GvE!{{yr*XlVa~q&p>$ zF;@?%OqgvKV*G_^X$E&i8Y_d2lmcGj0cT&GfG+bAB9O)JLB3Xo$EZZf*Ey9SCKg3%DVkugd`k9CK-&DQ`3Wz%L}p@pCr`3?Qzv`L-OY zSn#&I;{xu&we~lj!5GdKtho15e@mwn5;G~_fd2~wkq39N&*YT#4L8HTwNvhg@G3r5 z!Abvo9+>t=kd`@8>8lB@m=D^L@e6%L{H%?!oS3+yim&h1vy_eD#opxGqKf?24MT$q z`dUAwPm~wz#Y~y~J7xhuYd*TSs<|k69&A-W8QMii?Zq+N9&)?wcjst!z9GQmzxW>m z$N~oNr3QGz64&l+H8m|AmjQ>YDp9vrnIdCHuX>)e2F79={}Y$E_9h)n(zpk1T6*vc zru_U9=*s<{D!9ZyZQhWo2_VMl{a-03ox40lIe^~w{-51E%_)DL@Z|T%BL9b>yznal z@N1f1&aaMhonIE3xRTW9eBdqb+eKjco(p~Qb3@QCLpK&mM~UbtXuR{0-79x_fH?JU z-MVHp;Cw)NF&lDVkfX$%;uYE{$NK(@g<43jYf&MGPM-GjahICB;ao(8)@|{DA5~V~ zz*d&izn^!k*pHhXmvlNZ^}q`eC_d4&_A2>NY}~Y*OT_h|^O(TJ5)o%ZcjLEMPQppf zM2|qAz;2Uq0C;PQQ*9eD%QR)N44rQ&rIR`N?q#jH4zQGx>e>$lhh2KBOHsKbB1NF= zmf2=Yfwvc67|CCL|H8V4of)pR_X;jPwJoVCS^09DJ7=<_dlX#OZj|Q-{4_}##reR# z`h9ejAZ#T`4&MT_)ifFYir~}uqWj^)`=(|hfjt&#C($I!U3j*>kVcxTYjj@N58Fo9F4@x*o6((8-C#4K0E)C7}3^{(Vxy9XyMQ;@o(=h?>+Ss_AB)*Y&@2 z!ZYhCc4pEe@qV0#?)!6qgf^IIs=XImjXKf@5<#_D>-gKmXW0~wNL6iQF~P$B^D*B~ z(@nM3>)czko2E9@8c%f>6#F;q*K~>^2C60mr*oDU=Ep4>YpFsC(>$zU_y6Ok)?EOE zRLDvVq5xavUTHL9G;W`OuVsZN9aZRi1DTh)LP|7G#SgrJ+{3T}6x(ld19MRYTIIux zol}t-*-bHPyqlZ5Wa0A^mROaKLLGy9iB<%I9KFi?8@?8&l{i7~=Ke+s|M>_m}+k-=HQL@m^R-$xDb| z^+rsuX)p(-M#o}I0p}60#GR8$bQ6i2BtMME%KB_>qGTn;Ed7tK6gu>{qlZ5pMXCb3FY&16*{SN7`Su4QvAUr1PO&Y+PjQX@ES9;#PnJvQB z=daZMq7I*Ln9EYGi3b4|@4FVNLK=<CXFyR8+E_2Q1j zUDu#;y(ZJ+|M~A}0T;tdd2VjX=mMJT?>sgx8;eoD_1EA2f6!MC)(OFX!n(z;{UC>c z-adu#H!{B{GoAIH0Vmxr|3Bi0P8EW>Ewy|0z;q;iv(m?)1c(TsdPUB;TwKO~->c?u`cjGwpE+>8)%H365| zx=~q3v^R<}yW0)2dq3hqXNMcjBRFvT1TLB`+i^X3D#(uWFVV@9fR5x4 zIGCN?&lMVke*XtJ+&=U(FSv7jeq_I#nz%zUqkw7&N0N*I(#*&`%Oi|kN1Gb-Pu`4a z+s}oOiH<$sawVbr77IM2DStpg1D4ZQcJ%7L@{?$BJ&ZMM{GC`1wQTc z%Z|@5H|BC{(WM0}aM{O=9Z!z1=JZ2GdZ1#I^GuZal<}upO(lOopv-A$rJoJ9J>caO zbmH)7@e+ww=H^O0YD(PF#QH#dk1KT^QY+Ftcx8>l(6C$!;7bh$%wWyWh5Rpyb>sy_ylHR&`b}ozQyo&j z3*%1QRiYk;{ozB`qT4E}l&yYVrQ6Ig24<-Ykv3Ol(vB5)%25pP>#O&kvR>j zO}W-)$hhvV^A{vCE6gl&Q%$>o6_=eO<^ZiZ;x?--JN6sv=$oftJ^ZU~c=%%@fXbSt zf%2BDz!RUJ6JeDxE&j;2?sCNV`5&G)8)L%8R0b*{Jn1QCD&x}~D*)tjRIg{0E!F*0~xIn+FJ(5wTgWha@d+-b=_R( zVk;=2=ySQq-)Jg)t#x^GN=h~nY6T80x8L#IUT<0IZE@oz0l!tWr7zzDZvOnge{vzW zZ?2kQ7x|5_>Ib$uvOYzoTZ&x`#fwwj#$ckv&2XHoK3LDYmm8qeInCcC%Xv=#mv;k-28G3rbrOYO(b1EMeTJhA_?xzhy|7 z(cilifkQ{-7%8EXW=r{@T`OGw##rX0#WCdCd(gSYEX1rZY&!v@cpoM|96ES=ERmK6 z?`&C&+*1MYJAZ(U(=9QsC?a59GkVIfrWiDML$}_6XHI8{7;8p3LN~iSKp5fu8xo&L z-_*>cZ)MhP2iC+#=4~=CF3t<_2q;56y#{oiN>G~HWSqS4Bi=UZ@6h#=-h+bI;+ODm zkcZe{-RmjC;hJ9DaI?%QYp0FwoOZH1p#A?*7WDD&Kp(D^P&X5!!%2F8jCnUF%X~*O zpFTu>WoY3Y3p6_XMtKnh4~WM%Tt3FHuYv%23n|(afiT$g6M}+@&J`e@h9la6OJTp! zZg$&XW~zwcF0q9MG=N$n*QOHz0txV%cwkqAH-*{5R~kSVSwCsPRaI<)Hr*xQ5F--_ zbR0X#3qUFDjXHGX#_DNwYMN1sgB9_3NcGPxY`+s)&)g(q`eqe>87Ky1=PF5}uAj|E zu1~knr&q(kq1414)ry?IrhZLp0NyNpLQv=LqQDE*#rvjN9{HPrph087JMomU94`QY zm_y@rEPzp6@ty&e06j(Qc%&@=*M1N6iuXj{30fty6_BvldVZx4{NXyS^>E~Da>Bn) z_>-u9kC$WF5aIS0D*viU{*LbdnF;?STh1H627>DyhRMIArQZ*hv_JDxM&iuk0CMZ> z8{=QO^-mP|--to~XQjl?h*euQUA`e#92v_+cUT)LsFMJBY|@G8Vw?23^?~pk|B6w* z-@`RSTX|6>GA~0MUGVJ%)y|6iP;%Q48x!U0ziJn7+8gO@TXvxgYRp%bkXf*x!c|zC zOY2#aPvhQ;X^H-wsVWzaJ&lN9$H@}vN0#TGkTZ?W>wvYx5I?s%s3!s%UI!Bk{QGut z>V}hVkEhCZ_W@|U1klHS{p}2{Nb>2!MQ^R^;ISXJeLYhj$z+T~0X?BBy?JXi9RBf} z+_voQB{tkJsRo3EU(&q)`FG8HCNIE{L;g^rwg*r+`x8JzjyB}^he1kA{w_Tp^)h0g zzaJf21WMeOmNjo81Y=so%Oluu_5{bTMM|qBJ$JSW;+jk6#_Ug`<+gkSmmKbH(O>xO zjY$0UK9?Xf2|Ny`C4)zK4nqS&kXqGpPcnOeE+)>em913|=I)EzdXeiP2-34}F(0B*4by4Z)?xiE7L+_ZRwr+`elXOEqa(VwRP?bwo;b!$X^ zsSEBB+#cR%fKzBcdY;SILXY3RQnt880y-DniTeXY$EZ|7{vEU55LF||So7ZNSBu`h zY&a?wN(-b$9+@NK8opnnPZvU{A;^9ipBP4naw%L4>mF@iT78cWg>gienh7!vyMi z0aHc`7|gA<{IkGm0Yhf0@=Mp(VjX?uvVDf8Q~}Yw#;o9;uOkSZ0gcTRS6}BhmcE^# zF?P)glx#wE5rR^8`sr=D9$<55PK%zlFpE_pXEbKu^MtZNnTTAG1& zT9Ak(2?uZTm0twGF&CqzG$TN-u$2oQeZ&C^g@9DHiFuSGjC5rBSx=$^uJE*-{>}A3 z!OjDWnsAcJh7Emk##s~^m=@T*U07>Myc!s?Q`?nElZO}fi|)mw5_Oh1V3cnT{NIf* zKR51NPX`wK-$#f7$q+cT7;QhQ~tTzPO zcIE3fA;epOCb#0#ri5X^G4;rIAa}%!7wftkFP5&>d>y>|*f5oGhy&*!1ogjS^?5@g ziY)L`A+xMGr!n6rZzG?1CpDbLS9@K;M)EZlOiQref%|%G2~f^es|k3x_O>p+`YK+| z7xBifUFarKg1c>i!~G~4z#ce<^Os3wcWPcJp%J%$=y|n0-;OA>?vSKd9{_yM&*&5` z*EB~SxqE`_rb_Nxa16@hOh*=~1#dMaQ}`W2H5QNI+mT}O0}dgpr%K8nYgXLT5w_~CPRjjX#(Cg_<@!DxtUBC z=)P5FB8CS|HVM{y2-a;-woSlsa>uk+`4VA-nIeygT@ocj&F+`VrXasze+J*AR}?GIA`~&ASEWjxq^t zVaFjK=cXM0xi2m^y)DT|hiu}gMC)2f=)1avQvF;fD%qWiShu)Qc{jPya=UE)pp~fI zMn&b%vLVjsY~}^;$-d$zsmCpCc_q(#o|`ANG_sI50f$syjYl$uU6G7H+T)RM+H-Qv z#&DjcHinb}tg=jpEdvoK140OhiKa2_F|dZr80Dagd027$JyZO}HS5;$3rRPDX+?o;0eHK{{1N z-{uyPEK3*oEUMt8PVS{89!~Nft{4K*WE>ujCVY~p1z2VaEA4Q8&d)CnIG&i+r|-m| zWQ>GeQiZi;T$zp2e{+4OqvdR>p@a9C zm6jbm+0pkWWdo<;;X7D5BrWzWpn5+sIgP-c?>D4p+Nk1Lsxmb_S*MFZVRA`5C;R1P zg^-h5&_LFd(UmW0c@M_vd+EV9#c|x%Ewvx^^Dsd*l^6d_iWw4OVKtG73LP{ z9hdVu3u&6`KhM2BWVN2nHZQ%)cTT&qvJX8a1~~edSicvHO?s54;WwS<;jY&h==fqx z=6pql3}yirQwv9Beg5?xcNS<%5`RC4YI;sHL+|#ohiTQP z;*@F#d`ZASw2rO*VWS7nj7xbX^l{tWD5;y4O1ILTbx=F?#Nz{r<+gy<+Dwi0O6Ssw z-NtD0P?b=BB6_gq2=m1LWt9u?`mRq2^gy&%it4F6bX>lv?-QjQmBINu5OGKbiU3(* z+&Fw|M1Mz@ zn5z=s2;{GA`jgpzCKD zIcyh^smn=s!2Hs;W zbUE$_hsWb(wCIOPZuJIZnTmyXmD>a)pMzqY2i;y~M3Js}yQ1)M6B8Q!{_y&{x`}`e zWUd$aGNV)fbez~f>RjG$fq33L5zFD7X+H%^37^G9%Ava8k(t}U(zY0En+$Tl^)Y9e z$bD{Hwo=zS=4#ZqX#BjbD~0$fCGhii3csL^6;a^|?th_UIrnR|HDagDt+0a2I=z)f z@MnnH(t7zHG=hJM2G5a~L^La%U?}_ZQ~M`B^RM0HpBwqlQP%&CnbKXlSea1isN9Uh z02*JB6mG(u9CZxc54zibqpqOn=UN>`J65~obK0F=5DMMRK&Qj_@n65LS?cD?vzfx$ zuh=%_;q-<{msLbYQ}CTkmN&H%75cm!g?rIzLy-sY&dRk;%{%TlUz?A({AQ>nP%>l{ z-w=E!te~dwuJyzd$1T75@amDjJO^ss4oD_4tzoysU8R5BT_MA|q>5{?PSE7V*Ohz7 zW%IGXMY%{9#EHSpEY>xq3+rCE zZ{=OP4vQKm9!F{l`)ibLB3OZovwZa&&WiZd4MPwEwZm$x#mz~S#&F?eMz>e8kNlP- zrF;I9v39-J&WsBt-yJG5RA)?v3pay*YgVbzBXsJ5vPw0VK4n0G$_e`zMCJ@>NcHmFSAWbb|0l0?&6*s}g(!eV#0a27g~RZ*SL zLD996lRLUp28JpsL#Nmo)HRep*XpfvEUa?rX`oFH%k zG$Xg=OP@UCAcsjl!RI8)&FYhB=Tl!9s`7&=&CNZ<(~A0&1b8$T_A6-kD+XyIv-X}X z6ovAqs(@U!&|K;=uDS0s)&hgCcw$;B?JouolLrEXRN>|@Dt5ScpXEK6!i`-}(mcZ_ z#h_!R*?VaPW-D=zgZ)~jhxoz=@{^>Ha05h31YIJf*9h!v%A|Y~x}a>BQS(o!BrH2v zHe06>*BO-{Z7 zay`rGkBwixyzV5KG&q;sRD-$6`1aXE2EcWedFRK+ZcfXgljUr3FiHx@d9TIC6Am>w zMjq-oRKID&-0&p6BIV@Zb1T)ZnV4fT$!Y_%?4-M;gUJ?F$>c*=E;kx#n2y!gf0(CD z@~ZUkpUu5XfC#vrL}dBhtv~o(Gj%XeUa0Ymht%@=4E6?eYJGpB?2-zzF+Vy*=5Az8 zX!ub#8^qV3T)Uqf`&blRy%8;cUfv`E3mInIZS#Ofl3-mZm$j;6v<52bb9_kT?N&-Y zDP_K^)yMwWqBJ?R@&{=YM?OA%hv%mR7u*~2uttGMdh_`B=`S$TE;|>}vxi1;w`ij-vo|ESWVI*h6Pz$M-geL0~#z~pdNpSGb$u=oWi?Lg9 zXx_KM-EO>UQ!yWh4dVI?4DpXo8KByKaGNOiIx2iFt^JfT3Kd88?q>s2;FW0TPguNi zI*8Tj^$llDq{Pps>^2&3$aoLdt*ME@TWg7(Kvrk4%L7NUm@&$S;wWY^(l$Y-6YEuh z2vE1k4|7e_xm#8tFIaCI6ehmJUZ7giUlX=b8|^$)7~O$=#8=>gEGP;XCi@Y00~|D7 zG0h?4qrfavtGP>i9rhw|^myqPkk^Il)-amaH@c<{zO@fa&#E_hH_n#tM?hc^OL%#d z`)*Ae00ph+U0&5OkFz@WsRCR^K$@5WzAB@H4|GCb-(d@UvHEcM3!?EOx#7eJbqlm@qUmL zTa=f+*3mJAX$*>o`he1_Kh=AG$B1d^53u$cEH)~hSD58PrWvnT0atBKP$4p<#tCQdSJ zUv%#&dZCO|n^*tK%6qpwAF&61TRef=qu%=D;+>w38yU}3(H7LYMk6I;yG13;^*XWX z7`~5on~!LV4J=KGITjR{_F^tNcdC47v$g-L%QvfmU%Sjx&(0(f3R<*)%L_;vdHH)k z`1uehZ9sH~82;<_`VwBSNaK0*;w)nDKw%GyRn{tKOoIxV^q1Qy6!&*&rSu0V4bvn@ zc$~rmZ35c;awMX(%&)^ysyt54Efmq(;gx|`@E-NorZ`P+1HX1I$ng%!@~%pxfEvy8+;u;P;OlGWu_2$^6BrPEmgNVB{d; zUc{uodc`bWu7XkBp|(|Rnqzp7W2J0N-xYo2lp#y5u1WvIv3_HEs)C-bEV;_5qgSvg z5I)SEcY+Uu*o-`5ZlY--ZqI-W7yN`S`aC~TJk zinap`k3HACvYe^+lp#pw9zR6~u*kxj_H-)ii7giFJFmD*HGb%M6%oFTLvHd+#S4VZ zz>?;!PP6q0NVIaQ;V8%uB@RpQ=Upl6%BvB(sk#>_`YE`QZqq|syztbGULQh**u%DY z4hM@?&fnuJ^{G!^a+{<%0U;y+kUZ0uRbU;;}5Kmw_%2{PdRW$Ms&S_ z1>(5lBpDlVG7}|h|55=}eG|_sPs@8>?=C)qAFRJxA%9N2;;xs!xR5{VyIH7iDZ+Re zicVY6+quH!k^1gkwl(HoBa+6vkgh>ec7c20CqJhcz9HKxP^E73i|&~zHeb0vHM^n} zFR9}`Y8YKCLl}-~?osFn)bwmS{dX=mDb!jQgfy+z|Cu2CYh9K5!W*EY^SIwRqH*d8 zM#T9%U;kMhbrHmGzdHpOcSg1@O$2SFaym(>x5iZb;4 zI_yG<>007K)Gv3DZZtl`d* z*FhX6b_a(?BO8>yA&v5syM6Fxq=g5EhwLLI?Fx79EbZJ>RIGOHr^4qC}W9xp{)FE@^4PO7ugjb zV){!V_{6wmrH2azAjj6(?IP%=TvZaGdz>rL(kFy?2Y?Z-?txb;o?bck>z@3g50rr# z&*khgaQ%yb`9%u;+BWF>R(dFamMvcZ;a}V==wHi^n`Fy5wcgv}&^fT$jr`gR2%y}{ zpe(Di_o3jc?d3Ukc~X?nA045QOvSRYnRHk(^|8C?8O`}(Ktpa-1;z4-7~_i86Cl=( zrbf;*&>B8*^Je59J9pm#e2!Oa{_8)!;}95-@ag`=GlGkoeq$@gzxqGJw%S$%4#-{9Rzi$}TrW z9Kd>k^P45ag=Nu>n8yZCaxGkf* zo|kapa_L(*Os$sow_Jz=9!|RsImh;TE&TnDzi10IKY)w~{B1wM0L_ii@JwOeR0nQ= zy1)Lpsl+spX$$_B`ak{`Ncfb4L)joUxOvMW1!MgQ?Yg~OTH)DC`&jO%Q3-gkk3dzl z-df_NrGxq@N6WMv-F50UcfrLdUi+f5KmTL}$N-y8aIXf!Odb8v6^8Nmd;II+S}`B~ zr_b=Q<#!d_iWN=3FY@c(ALtb#{ujRT`x&nu)^eY_N)Y}NQYqr(#twb#eug>aRX7r1 z&a@#9|K*hTcdZ38-7x+&Rfn4)0DZDjzk=vvULmN&PCqCA_M_HTJsy}3Tv%~}wbUER z3`o?a4XjyN(Z>!-x_t;x>YQQVuFlSg6JW6q9A{Byx&!e?-_VwSx_7zuGe^p)jQGk-|Dw+c}Rytz6QOsp=mdedRzO=g50#X{}@AHW18R!wu z4U`Wz(Ih6QI?NY-%zlvSkLTsTxkt*2V(vd?B$3}`JFMMc-5tW|I7=@l$s{IhNUpqf zPWp??%dmARs+MyM+$Wlk)xb?1*iA&0DZ&tA>g0+!#t0X4Sp7mMURAf$rdadxp$qQP zu&zo-t1Z^I4d6v)^?Oo{?nfK0SKXQ`FY{ktV5wM~}`Ix@- zV9w0k*Tw+L%M3%7*07maAc!?;qgZ5@3Cb)oZOc)GdMOM_vddyl)YxXnRH=EltU8VdV zSoFvk9P2dh@Pm8}^I^u?8we8x3L0s&DC)2c0b^e{V_~~^bK!dTj^9qoJB1lGp>K%A54|6IqeT5-+mhsUqL8^CF_*pZri7Ax@$mkG%&I&DJ+6{yS? zm{W;x)^3o_m!}B(D`yjWvu#OlXt2ktG|}a95(paIrYsWMzetZh!oL%V0@hihQ!gD) z?uL@TiaSYtM&j$eJ%9sLoOP`1^tH7UainI;M+z5x;_M$ry_qLKjvHQ79xH0R=&xGl z_WUe+^;a0x4*3jR+XNbu&wrs>%$Mbp>vHG|QI_GtI1WL!Qw3u|EMAJDR-UCEZ(*o) zwtVr;T*ZktLsPz2rS6VhuJ4FNCh8F=jtvBsNjb){jZo@%JB44ot@Cq^0(gma5zQy1l_rnfy5)}&yORiF#WnwLJ z<8V6UIVT!FTA#{vy;F$&KtZKr=2CT5w+kr$iVkbU0FN+~;9cDrk|lEtx9_$+A2Gtx zIl~8PP@AVehn!cwsK=Kz>F;v(4vP}IrUW;M&`?CBaox3O!jTif7N{VF! z23M8!pn1Wd0!Jwr%GWdC>IlPrh+`)nKD0M)@lgJLWD`29 z<&ll)sB(ff5BV0?mX~~S$HDZxeQvJ-jOdwVBzZ|)W2WwBQqv};a#IYF)=cJ07$lIa zErhHFcdy4P`qWx`AzWi?#A*6ocAC>&S|;4!zqqoN!_4Ex`tLo6&d2g%(?#-QJKJ=n z;93hz{^`Ta8v7lX3c(L9DxoY|9>$dw@+xACuUxE>Nu@Li?qK@PaYK9u!o^DA^*&pk zt!3YGqp;7#Yl(dBPj(bV@$57I_kXY(gXahe(p4u4dwk# zAw%RqXFi}I!;21&DD+tN-Q>Kez+_7t{XIpOkZE`Rt%^JM3RJ(F5~PonB6n6EPyW{rtyjYLy&Z4o0e&3}O^%t(g#_6(T{ zLopPpFvJ(TC4|=UHKuJzN!1#?Gy3*AED6J`r5HEB(qh8PQv1l#KB0cb5VgVBT%C!z z1Rk*Tm6ex2--7O4oWfDkD2~BHG&_T!!0F}(@h-S$IzDY7suP`0gfT4h_l9kgw?^Mv zR{j{-uQK$}jb1204$fnJJ&F&A`<%9PR!mNl$RRuXwx}bb09grkt9Wxd&@9cWw6Q!hCt(I0hpz+FK@cg#E zd=$C$MTMNnd(`OEN$dhcSOOQf_|Uu@_YR(&-rzwh#Q9e5`5+b>7J^u)3F090 z`0UG~e5GJWTc%bP`{gSkRyj>QQ$HANgP`rSxek@1;Xh-_}d^3mp`& ze7Q>+lO%hA_-HRu2;$8MpW1@ih|bWF1L;+%1Qs)}+N$xe|C$dkm2bT|h>Y0WampC> zILVfX20y2ow;d0Q3&`Sknl_{_KaaWjXx)$yD#4racT%4OGN{+PXG;$y<&_a1ad8m? zJ4U!F3cxl)uMd)om13*~zt(7>_D2?pmAx3;sBlTg5q;46t~GGU7k0-F6&m&IeJARQ z!x~KLr&*P}lDB{zy!d*3)dV~WWueKCI*<^$Po<4j(LwWi1}2<#oM#9xHvoMBrZZoX z=|57z)?a|gqq&*drL`y0pB3Ig_*=OOBl3Wwpx1EPNVT29@AB{7vk_6as?g$m^=UNT zm`R{j@e#ZSf+w9$F9Y$LzO)f^ObTy?w*iSs&$|79lRsz@ZAkDwL&+U{ASfWI;0$t+ z$M~&Wq&HCc`xFANisN4!Q{uYn*}ZU*f(?I;9p@y#m&%vb&;ogOqufgP`RyD93%R#0 zbaUXYMB9I1R89w$-+lDUhIY~D4;$K5S^sx8%8V86=UOIhX?HRPNzf8Y{G<``dC%ei zf?03C97M5Xk2)LB=d78F5-j>aWY%`d-v&oTyy0TFY}Vkm(T_f~tZ@8y9|Eh253vwP z8cwFUWP@y5;@KjXi_Rd5s>Gk0c&GS|{Du^X)T2V{EnuFhnC&-~KO6wPr`f;!i&fLx zSwk2_z^x{K1zi}aoQB2k_MM=Ho>?@Ix?SN?TvebxTl zSsfc~Q@LBznG4Q{vSfRZ_PlKSx^{N`tV*=57)~&OY2p)fYEIEo~eDA&8`RTEo zxqf~)5{o(h+FCoEqo94%+6GTjC-+ww_7s=7t7_-PSKTR>SWEo2To>*#ta6{BXLi0` z1cxVBFjEIvwDUPZJOE+Z=RumV2Zm7iDL?tT{`TxsNY6n1xzjE8wAEGBmW!lrfyRT) z6607S2%{d7?ZKi)iAf|!YEHAhrI}S!BSv8X7FnMI@cNAhqDR!5AT<~f)$co4Pg|Bu zU7hHCdXV_m;N4){>#>2d`t~{-ead;uVPlr=^C-bZ=_Kt)ATTsOBM*x-F;Rh9r*PM zzMe9!kKSdxOx&k#iXGkR*T1pg+Bp4^$NnU2nP*s&N1NU**Se_p<8d?Ieh> zAd0GG$RT0}Fx=^tb^>LAi;1|;4+)3^zRxJb=~OwF91ubSk!0!AZJw;>S!*2+T+p=# zm)gb4gWCb3uGCqo-svK%vG4&L&ILZ(F*#;`t z?u;jnPTbBm-2nwy&WO;MRSM^Sk0%^|_n47iVfcJfo#D_n)_E7& zy$e+x(uf8FX?iwX2b(JBEEs0Q$0s85on#CxdF30M!(F$&R(J*=jT_64R+VM7it>7d zyF_M^$T|UnmUe}ewBKB+!5o+(QXa@{(yh6C+ckHbXzu=mT@dtv3?*Rbq@_Mt111GI#xA zGP8!HSv|pe?@;ihqF{*pk(dWWOs)Z+cvpP@7*+L5E&? zdNO9wZS;d@S^aM)3))fC1SGY@urJjrAmJ)04Y;n7f1oa~%Ede{x-O%I{wW@obliza zZc-o}=Tn=WRUpudlE>;;%#3N-ius6SR#!ZXNvm_}8zYD1he23p-&3%)L`!|T?!@e{ z*9AMw0U+x89TrKy@iiXi|Ce2JRe(7yRxcgC4@9RIK#E??VjhICn940q6ZxKIqv5zR zx=OFO=I`eKAivRFX|w%xEV4BT3_K7sE14N!W$hnoj&D+)Tv&{UU#*c0dna-c;Y^-%OgW>H;t{?;cal*F|wm_}dZL41}XC23If|`D#C)%<) z5q#D;e+^4C_G9Axe6M)b^E%TrdZK$5ywchT+rVQt$Vmi-+GhC)0 zO66_;QOi)pFKj9TGPPUkF2oD?!pQ1?ENAWDquuY}>Yzp4qor5_puLnwv2B6WAqmH6 zTQDq2!rxZjC@)jI-M_yHv0039wB&B_Tey^RCqmla;51Mt-Qy%a-eff4= z=*H2P5914zk0Mu&kbPB8ok9YJ6+(Mp8M6TmWWRQB`^wG{yW9KaQcf7+F*U+<)hYc& zq0c0*0PW8QjslT&T^duzX1!02>zIzUr`_1;+WHXLc8IOQ1k(usrgnxiEAfQ3jaI(= zCbY!I+Y9skZMqm@GsqQax);6?J-BpWj$7Sr?_cdviCF6x=te(pCh?LTWe}0Jv;{!c zZ+e-0e79*{L<;(I9E8;=F^Oh4`STGmgjaMy#(D2(_3Jm*Iw}Iu_EBVRLPzz48_Da*I=*hUMkR^Ll+iEDL7qfz&=7%@DZHX3n@fr+&TsB3XN&eh!-bfLqL{II?$I-=sntzSplu4E`NqWe|L z5E)_n^x5{;0ZJm}Z<`MNSTT=K zrgdL2>fT)_Yr=~v^VwBbU0NK7W~~B+#Ieq507=l|*YnWLxm)j$(H<`vf!wTtQdB`| zFL7!>tPM5_gy6v_1NvAUfqr*~1cBZu(RdEs=a;UQnzU}bo_xPTHG z>h<*7%K&rj`C=ZigEss`b@nbix4uX9%HiH7X3T>DZ`#4&&Kc+P>n=S_+iZr$zdSE| z4#+f_J$Den;ZZEJQ+o(1h`%7zk_w;9%ey@jH7z=mjFlClZS~1^@`sQwo~gP<0FM)U zB!{~?57jTj3h@N{-u%TE^Wu$8^G0Cu$^r&ynwnG;P2R;P&9r#4+iS3JB^ z?QUK3M0W;!0>c=OU`YvS*%VQ|Pg!Fw(~B0)WrBw-9Z0{O;*LZ^xn~_fE*hCLORY|9 zy-A+%y0+G9n+H9p!EOhJ9hM3I1pLZ!t2nHY<53paEWzohyy@iF&>qAN=-6~c`mMD0 z%0WuLiS+nHVA81lWuNdg<|h|*Y>_>k<$6(mt*T&jqOGH234fYh#>bYA><;WHm=dx= zkbsdkuQE2JB~DENEi909h%pV!8Ph=837q`kp?kK9 z2R}QzBDeaT;P@9j_!j`biU+Tl`hTNZe!A}eDY5&-HG$gX|NC>cQd_L}`+g2OTgC%T z(ZGS6wW>Dze_1p9cT&7%)LUz1mf&)%z`>OPb5X9|fIfbjyBtON(=YpfB_Djfq;ip6 zi2&TSj8ty^l~%Y~y&N931tD%rh~qC2+<&e?{L263`z7Fa{O?qO|0eqTr+*xn%slQo z)x#zd@h_OBw-$bbhrb7y6K2m0^HQR$ta>WW5jF<>JT#a&KC-)P+4h|X2&-hwH;csq?ugiKRLOu5hQEJphyZmy_%l3 z&g3@x@xQd7R)xLX zmUq3cLC##mWz+YT)9*M65Il~y3dD)YXUy#|vEMj}OX{ZkPLu_%;O6=>*=rn|M+_cR z;hcahXu8cYe7{`dXEW)td1Y#Nehfk$K9b%-I`O6XgmY?m5@F9BH*?xfU6`iLNAp(Ro+ zE0X%*En&Ax4MLLlNm(T%)y=Nh7e~@*JTCyj?2>58w#7XOi0f(cz4LPC9o6Yi{CcWd z&tIrKFnHd(?A!}Gx%CrZmfF!M>H^#n0g`_4Fu%IX^wi zxT`M{Hw={FKU~d4ISL+0Bu$9|^Moy9?=Zo2g^{=qkX}yW0gSG9n!*#S);8V5UhA+@ zNI@aV;NaSk9;WY&^lN%#>*pgH;huxz8n`ltgPv#9^%`ij*o`R_F>`WaaIo6a&o*m@AhoV)v52Xi_-`jZ^mB8m64o9Asc0cbw3 z%-TL?g1MvLr|Y;o!oMl5SpzT_uGEvc0aWy+bTN3cDxO4pYVPfRHR|g3izxn zLmz)7Hi6reKsmyTB!;AKWVor8nmykJNlxS@KPSm_@i?nnZi*TK{9x2K9uB!azL~W| znUfsk7*^Y6Fc(J*ir9vkk~%}tUnWzO9)CW)mU!OO{I0c(l3jNjBU4P|oBj2AiiJdA zL?s6{p|kVh3qt9$@+gxXP#iy#oq-04tH%kSu)YeECu?ObsbOR5YS){& z&U2VXzWiLW$%ATiDnm`^0cPhhQY`bU{~EGqvxB$GcDAe>UgR|KO?W@j10DSLlam!G{-2kU*vGlv2F=xC5=|2BW5Xc@vT3+qeZ$n4n) zZ3Bvz`R#XSLnMqi$buNUbyF`3o1L_mT@CY(?q9l;GX9GV!Ow!JudE|@k3Rv*=JrT% z^$iPlaGQ)7x+R`q7mn|>1c3_htonojveUQU59B6%=S%6_83bmp$T87C2n9^p6Ksje z`DAw?Sn$9IP8-U>Iqx;890>2 zVJgzVa>&(72Tto-;t^n+;S|EyU+AfuWlQ(W9)IV((-gS^1#+24$ftWVxop5Z9ezo8&9#CN(o>e(X zJkeK*%d9A%9T4*yc@>F2AILh@$@?)tDb+vg*6!D_IN^IOdIz!Q9j;G`L$Kd%jp0uN z;Cng2I}i(0&#@cOElTWS^>9>f75cF(68}Kx?kC~X^J5xsuB~;H-q1c~lR&~nY-1SYh)m{mK)CN7h@s6)4N z*IDMjVF!cR;X|CD%2m#DSNG=!{;&l$7==}`c*iER{vl+0AktDcFfAp?NW`4KB6E7c zRX!j?9_T`3SF%Jvw(^e6XuUeO`2@ymP+(UGgGopmo=J=ab7AZE`spVJC{_NJt!BuV zHLUl(2}@7BiqB07A39qAp7P`iuv$6vz;C5t^m?p!Ju_jWk=YAg;e*jVj{}sx{Js_S ziZ`~Tt#ZO&9L$@!ngq zE~W^K*Na`fCRt!k@w&1$t9%zh$3$coU(!TwB_k7ryB9WPdYU@skLEUk@YLJO+CO%= z7Mrz}_&}saY~*|9NBgg%LdHXBm1^PEq;EWPyBN2?U%1eKYV5bWlkrZ&sBJ?3t3yY% z=~JF&ryyAaIq+`SbwmsnWgEpetrMBO3_#!C_-JlEbaD7 zma}8Q!3A5^Oeu~oM=t&E5!8Pc;_Nj1KkmW(Wi8kL>E8Y)x4^a$RP&?R!|iF1b=Q9@ zCIJX_tWkPV$IXsUe6-M_l4O0PeDMWh^tjsk-+yMGn{a6^XR(tAxnfE3x}85YzYbgd z+Y<~*{d(Zj=ZcRp)papVh8obA@K`W?HCS&|yTH8#4Q2}j-F-7g8IpWySdAz%zek{iYd$?n(+P4bqjD?Tqh+iG0qL%m! z<1PnJ4znP3mS`4ZPXiIs;(FSTl}2JJrNi9V@zq`bUP_F<{#jC4F5#w>5P>j$#bGf_ zc^&r5YPT56$z>9)D(kfKss9|zY0e$M{xfKmZ9 zwZz#&NnSO_EDs??C0E-#w!|k(z4JouB??2wPQh!ZA7o1R?{e%?YPEJGwbKVX^KsI# zfzYOtFOBiIr@Z=~Z#0=&{vhdXKv(cIcejCd8;o&(X=V z)p5rbn7LXn1e^Nrlg7lP5xArJM0G`e)y@Qk665t)IDFVpU3H+jQ$WxUqd0YFbBh2c z(B%C7bOaLCWO=%3oyWdV?DDUH?9R2CX^smJYW^!7rPVXbXlojIcuy-He>v*38F%gY z`P79Ndf9mBMuK&Vu$5M2ef`2aQ~hZ%`Q<}Y?b{OZ@F@pbguwWi=TCk}s;n+(8o%rZ z$t*-Q_fafQaCqRI-@4Q(<%}Hk8A_QrtSZ3w#%S6@bNI`6Kz26MgOM8hEi)o3R^jsF zGt>Jy$k0_j0#sFgRGsGF%DMM$)n&eJcO9gjicoYV0HOq!vfYIkdl!LboS;MvnfO_$ z%c-KnZ>p;*OdXO|Uej4Jrc>*koFBza1E25_Bf*h5M(3DTYYd z#P!pm2#}pj(%qpUfp4l(FudKNUsL+!uz#?1tm~BZ`4~q!tY9j})1w&8Vzl@&7iCI3 zmj$eQEuEISkY}G!jC7eFH)D1al9S~kJ^f?_ueK zRWWg&u{A=}B(Eg@+l)mRDhk%RiE!3B44yt{UD|K}sg%Y^%u8mqw#|#}X&x1@_GX%4 zF6uN5C$k6W#dfEng$@EqSyUxAZh zEN60m-L`F9SnGHV`Vc|LpsN>a4Pm7>XJ4fvL$fh=nEYo~5SrzS(D;3CCMg0hQu|_K zGUgp;YQORW2EMH#6T>7lLr{T^lgEpH$`sARKJU2|cVy4*L&%_t?<|Q*#Duc0IU|}k z!y3*kN$@$5wo>31kt@b=-aoW?@0x4~5IL zzo95HqIojl@0feRI(=_*Gz76Q$BYW166LxPV)2-t?=1&fcOd4T?{GPsDDMWM5*(88CyqC5Y z)c%ZF#;_A*&&GQYt=wcc5T~OY8!or8lx$)=KJ)2x-sPdI-!93H^{`zjYD73>cyL!v{ns|Ja*3GO;}{4a6GkM@A^Fwl*e$ zATtx4iUqEatRG77xXd5gkEMQVGL}^wcWf}6rjZjMnT4>0{E? zeG3LG%&(%t|5a}RhQa)q=RBQ%?=+T~e|G%! z>Tn+@2rM^B5&A`T{|6=af7kZ>sgYR8)mza|{Lu=mY~&@1_3c+8;Znr*|Ge|~4@41- zflH}&%T9-%7wJEv0RGW={D+sJ`&qze#PPa^tIh3*#!nf;9~zB+xr4l5rtfAq9cF9y z<+=#lA&aUhyJ1FDFkM-csoZ^N^`&w98ld!c#9}u8Oi|}FLzR5PjqKQ)4^XW$t7+Vz zb%>HCSo6rsSkExpzdlfM@qr5ZLP(d_61V-VHb5T@x)3m?-`-=eQc-KKpNiO|5fazxnaR^ZY#22MUSjBV*2{R_(Q2MYUiziDIVux zS8xf2hy4(vKOW3^`9D~FBZokmK)tp0v8=0bZC1C5p}RSxIPLzAtLm!!yswq=1WkNZ z>;%1x%B(gkn8#^JPsVNm5J84zp=z-Uq+|z1qiZvvir&4cBk2XRHS+h;{Wvn~TgkTz zM@g~j#KcWEkegn|s0u?RgdA;BPuL_|s+5)CXZ%z>9eb^33_M?ub!@l$#4VW!hAAH_ zFO?PFI?{;DW*Rx&M)WrtLl0o3);Q5G#2P;W=H<71bJa|Q zRrvMSmgUYxMpfQAGWuCxGyPw8g`23UMR^DA7I2IQB46csGJ%9nq%s}&)$;Jh)X@NVtMxmQ&}*Xc)U2m)vr|n+zND<-6&EW-$kQtc z3e$Jwh+K)XmW6iJy&a>mSYh|^J)FRvFg^UyV4SAMH@F)45gSM#zV~#kzjDP9wzD8j zS;mWzUK;W^EaSW^KXjN;7L}JAsdP#(bzSEA(H))EDo!)wj?W!k)>+LmN;Ze6YQnzG z2$mm4exZ4FM)Mb12->jGih(AqNhFVfDO zm3-b_&7lQib{ZcCg-YBjz1fVnK((gk<<^Fk9a_xQY>06&sz~W&wPw1=%yNZYJEiK- zKHnqGC=k?#MOgbJ&5|ZZFdfTrv902QV1S89q5;8wIM^UzVTvJ%tn^@g;Og2utwJ5^ zi%#@m5_2bYP4B&t&znt&so?TBqPWZPlDf!U9-_LssnD!#K2!K0P;k79THo!zodd@( zEBZiiT6xo-g?-+_gC_1Jp#B<#O%{Yvn3~dC*nR}Bi<;FhcG4FcYzvt>OxM$Vqi+o9 zUM`l-yqnSd({C6#WoC^j6}j(Ac1839K4xep?lS%a>9q3>*7f4C`L|-9SPL+J+I$y= z!AI@uf?-@_TBiJ)=Yv@D{vP(MY71Bw4=xJhB+BURf+4);B9?=*(#B*Y2R5OT4Hw^I z`W2LZ+-zK6w!Xfb_7N=-2pOaK3<-U8V_w0QIdBpy6%}FdGiFU;7DMlF^Z+3PU-)O< z_u7qtStLmGMs%2e!#Tv2B&3`nYW!uD-sB3po|128xIym5diuNDaXK zFCr8+p;g3*=HrqBKxlQbZ?Kmc>VxZ7f6$_EL{-4qT4j_btsK|Kkj0!S+uozZ6L8+o3%D{-VOmIp0}yT~>s zJUr4_d9O0+c?of^Z!l=~3NUwFPgL$Bz9pBzDm)MqpGa|ssq+@BeOI#A5=rE8f^i@D zETA439ScRU%o@ts=7`?>l`jv6rp=xXWaVs5QE%*(N=UwQw+PQ;*f}qiaMzh14~tHc zQv!&i8oGzdk~Ui3fS(FpXQEn^`Sw(ldAAc@LCZQ&XySG=FCuhjQ={3m@>!=MUbV$B zZ(7cT0Heu{Yi*Vt?*OCa2on!(-mFX42{ya6=WZ(-k-u3$xCz&V(A&E(ySuY)->Dpw zNmNwk&#@1#G9Ip)GJ)@cj+M5V41XIsM~-VxNIZrB^sLXt_+}aG%ua5Q`B|AZUtF-& zRk*S$ClH8SR2GYI&oGv7#n!7g@|+gg4ElV$zEM zANyqId{KHk)Q3%Wfmqg$OGXm6s`^2GSOvpkG&0O^j-W3~`)D_wwwd~>aud1I$~TlX zQ3vKhl3$c&ll_%dfu(fKtsiX8CT5i^5QtXP*fFa}1|uB<0y!RR9%p{yLjW6*G&AypC>If*6nE{W%x2%Uud#7sw< zX5ME+#-s&|QQ1Lj?1FWSuV0huxoL^)#{eY(x7Ic19$Mk#bh}7~XQ%GLbY}D`Zaqfr z6>nx>pmp`}J$`VgW$^7sWM8se_@QKL za{(wAw&qKFpjN|rQL-&iZJ1S9;(o>eYrwvF4y^*aqU(9k^J^%Ypm=uSlByx(Vrz@F zB;9)nU~m41l5DdCc><|^`SXx+#tX$1OdcQEZmy>GT<*_NJK&iMnnT9jP@!H#nM;h2 zZtNc^CLAld9Rc5ZmaijFZ`Q4_XXAYjABi^4tL`zWiO%jgHy%ERmRsl|QX6W4;I9lG z{>smDvZdBnBKspVbZ`drT(uh5WC*p;t$C#~VLxL-=VUEglp((TpMpt@$s=9S>iCvdCI*>6sa@2Ca~fZl-5~!aR|^qxW0(R--9HM0 zr1DL6_1R=#!q*c0N;jK=#5DtzU{=?BXOZUmm8kZ}NAN4fuq=0$#YPGwsYvU<_~UcuIAw`ZGoUUst9;A+A35u*UiVra7iUO|o>Q16>8><0_|VUf zi^qojl0*7O?IvgM01Z{~SL9>@V;3K<1wvcwG z-Ac05R(2r6(!9f<3uNkNaGSuo$35qw(_|-dnJrTp)zXLNb}Fi(6F*5jUh7z0-V~?W zlBeJoGuLsN$qoPm+H$Zt5R>LKw5NR2i+DMHU5`8686&_mAcw2mNovY3bJE=(KYv~tt)`g~*CFhB;Mn?=Ig8f@b-;{^3&(*<3+!#N!- z{;?tQ{iYz19>(ZH4R^^$O+;vk2y>fDLZa>Gge+@?n6*E^pdVZj=WN_mn!oXS+^9WD z6a-SnE?G6dyyPZ-JAGB12WE-iIPQ^Rd)2Rc&P^!a)6t|WT*w8O$YhBcO}x>LT+@je zrJ2~v3O%oB|+0-hE>{ZtL3M9_r~h1REb zRoQmA-_{i7>(+723dg=zAb)c z-%N#ID)|nszUCHVL1sx-Baz`V#4gFNo-r~H!kWT!wNYbTL+RJ{PP|V&X3Y4Z#6T5A ztoC)hJD{kBD~!=^-r*s-k-yuP4bbG@{uPgU>D1$3NM; zvmkEeS0ejLx7ygL#vZBkKb`Zd@wlIR&Eua0-v8mul>evD!k;bt@{?QNP2O6^@k@L8 zua=H~m0kYdnKAw(B>#j3{zIsS&CMYQ$;L&dc=PzwGsL@mUsD%I zPD}@mnjf!pkUO*I`>o&B@(&z1NIF8^7twnR1jYlz;T6JadUh@G8RK9e2TlP+GN3Xa zcfRV(D*_XIiFL0@)-7R8su)v)$^Uv>IT?4~e5ZLd?}keF5jqvbKie z-wQjhHK_HRtn!vHFTa|5Vg#HOa*2bLW1HjmoJLx`<+B|(MT7YgW_xso8&hM0(q(v{ z^{u@3L00*2Tb?M3 zeaieR@;Bkg;|*vEvnl%(rbL0X$W%egD=Dodz7QGu0X}ROw?QpJ0!m}Ld3b-$cL6i# zK&L8WrMD24z>^|7r6iR!yJ;S2)l|kMH%{Fn1hrUHMgK4I-aD$Pt?d`3h=_=&h$x7- z6+y9}^dbvLoH6dWdyM@Du4JvbR_2=X`IYB+%mpJZv%%FlSJqDcTDY$MviwEP zs@<7gQ&SXuEtae95;wNskeBGggA+C!a~b0xoxMCD%;QsPEQ+x?hZghvbb|5sZ1-~W zN395;u+7pRTkbz!Kf89iEYRI!FS_1VHiXh=w5~i}a$s;bw0W(ff9P6-$-1)95f_ie zGR{Rz={I6=IC>;5w}RG4L|u^_T+2q_Ym!V2-)*#;Q#7jw{is%-Y?}_B0EO@(a|*Ff zPhuFMKwLW(1>JD4)v{rj!k;#*(uDrZz1#wHiBhC8T;Rk#rw}^2V^I<@sek#YFQ|&UU><;+r2@n_Dot zkcgtT8+I71U!-PCpiG3DYi8-us=S)Lk>@SRfg4kyycgSbEf z0yRbF0J)Gq<)@dIO&#t*D5#@t*J*xnH;qltx(uky7B9)A$WD-2WAs6<2X7~Hu$I&} zqH0d~H9Jic)^%UTfv7Pa^<@Ao6RYmj+w$?lo-|N@%VC3U*#<@J71?Rd2O^c4`*&kB znZ`Ps*|-=ZBXMvj?%-OAzHQMQg}BPCqJ>*7rp?K#9?ahP!-p5m{5Yr`=!1krG2SV> z;zi@tGAOpbCTgU9EEC$@Ic#ljbf88St7$MdQd_c&K%SwedaW~v^d)?qy0MA@({p!r zQZVH6-uPHl1bS?3t0exP7LKDuV|*c zu0Z9+6b|kT@doB87M7X^%)7qA3bRdGA>)&LH-bR=a^1aeo;oG0$RZ(*X4n3l7^By% zTS=kcxlo*KuE%#``N#(d_TuMyC*R|A=<}XB=-d(Vr$O2hKG$yn(V%`VWd$rL63Bf! zu)mNg3}~)fP2-?PYux^WRMSBmW#~~ z+*RtaDw~w;3@})~B3*Wg@~;0iCjnzK+{|Xh4%%^~)=Mxe*p2VeoI&z_ZHfrw73uH#m-Wb4sUWgJNXWftioH#!PdRguLK_9ODAF0{9ek7m}uy5Zo!X= zK_Pc_H0|Lja`!gGiF1tzzmdob)wA;Qi<4CS@|Urf!h5X=cA&DJgZQ^B@GeFgU2>U9 zRTN$vNtL9OUZ65SxBQVaNER4jh2? zmo9CGqN=dl0Z5rQirniYyil3W6+3idIXx(yVrQZPT5v1%I`!Wo?DxnxBi>BL?-f!D zQ-+RkTZ+@@ct!m1@`bTYTjCr4QimfGPfMN`rtg_7i*5X#it2odhHn89YJZ%Gk_PUD zrF_OJ^$pgp8sF=_7~|hFBiT?1ih&&`bPTV=7JNR6Ido{#zJ3ZLCoaAit}iSO(}-{9 z7FHSctFDC7pUwrKyEv=Df;|+!Y^B~EXPMXMOBYa#{8qHqKo@TscN{b^f{_EEk?h$s=I|1snU=f zbcj?}dEN+gb$urj8+jR|OY?^Rjxj(0<0z-xg3JItKHAg^gd4BG-BT1xi2J5vGm4SN zWpCwYv%`Bm-id*Hd-OHN4Nf@ty%sTt9{4;`oFOt`cKd-aq=MOe4tyv?`8=fpP5ntuBId zJhqOdfK_w9sD1LcyI;BsuXa4`8I+NKW}N%#%i5c@?V!gjU)FN2nEsXfEotXcR0zV{moe@wS_%|kW2>7;2M9%3UBD9-wSwB^6~jY zB$6G3GDs#Z1U*-Je+z4W{A7#N@M zkB7cVGul3`e)>kT8V||gOUYrn)5!SGfb1KW(f>$L{-?qG6J+{XM=s=U!hhSC^FIeNU+`k%?zi;Ex9F|=K*wFT`-zx*_bqSsrc2SJ%HuD`JSHold# zcnAq0tbsbE!Q3=%vyG&eP-ai0IWcCFPLLHD7a4i7lvvd$-|D%(Yhv%Q0=fQ zU3X#nQrY!l;cZwY*BpfNw;QFyUVM?kUR9%`dLS<|-A8U9#ZUEYIRO+)P9wg%UVHGB zw@;VnU-A3Tv-8jkoM!kQ+-D0_L5TylEv#c*Nuh4fSq5`M`68(zn2^vRS;_!l?4O+iVHBK*A=HoK3Pfj0d@6nHQ0RGsU5;HAL zzu2FgBy0odRUVq3GNGR;sr{6VT&k!h;?Q(pp}@s(IWEsmKGWVCwJ{{K zJALu`*1Vum#@k|pJ~B?7NWUXsyFo?GPCuLYb$7bp?iK|}$6q@v+`1NfqCX-w zRvUf~Qod_T9I)KqN&u<3foXUKAx|}O`ROl4fC~iAywCHwkcX41SqV-}>a(1iu zsHB479AW^Jk8JKWLmTixd7CdW4xjk7z0ra`D`?G4Jyd#S_{j^Ja?%uriI4Bwc0JAg znW;*Gxx8^eYI-_oP@3r60q(y)*W9YUE?ob>X^mVQ0zl%&R*kW?e-(aqS*BI}!A6zZsIzh09P+nitHKLET{ z3!rvv|Az?x&mWz=_}Kl0H?={7Ze8Ygx)hZe`WlpGoxNm00&PO(4y}PKfvTdCEC{V$ z*{^4)p|F>>=c)QH3LNyJEm;ksWuLY5pk%Hzgz8W@Sm;&>i1U}4Z*F7?978z6 zs<_CH`YOlZZ&VMcI0dfcYNSw|oZz2k<{y0E(DnFj)Ke%<-4T8FF00L=`hE_uDG|zv1% z1YFF>LobaL`FXjxk)4fv?&ng5*&cN_O+yy&$^Tsd1pGxkS1g=Yv!$KLS8IyJ)>sMW zibp}}9`$>&2L)(6d1~ z-Ri~}9~ZrwmOE3?Q`d2;A79$uw*Bfe+`Hr!iolI97~fk%5w8MM*l|C|SNX|5e`J8J zwV~2W)nt{UH{Q+KT3V?3)D;J$zKd2PmhBtV!Huoqr!vgjBb6`)tA11DnWlK!AK>Yk zhoe>>3G{tRWh}Lut6j#^f6{Ft%|665UMXf>Ye4^i{sF)}uETfzyku;_A2tT>yqm(> zi20777_awL#XESDkuyHQz|!)z@Cfsiwq8Sz1S*F6hU0SmOg2`kiy0})EBIVcD)nEO zHfHC8-Ovp=K(sMP2R6Qx=vENljyz%|l)M>T6BzM=3Nc-PG2MjQ&qzyV^$6?I&!Gk< zmutCg4ScTF+GQfhuU$7bmS#}Jp;KR$6#_lkwmgNrk}$RVazP^EbgZCj&>uo3PaY(w zof2S+N8Khx>iYB)dOdmfU}MM9(dp0y#L@btwrMrNnwRN~7vhm|L*J8Io+^-%zs-tg zR~={yEmWDBKnl?XRz}y?t2`ZKfgj?{&R3jBmn#RMh8nL*_^t1(t*<{t$7iK4i}8`s z<(?lr@_Va8GT=&&BOT|d;e#CbAN5J9^Q{jal|CnnZDXbGV?`oi8wcw$&=vPmNuJF$ z?mdj#Vz!^*rcC-#xa&-4M2u$KN}dE`k7=@vlmU53Iz~4MYxb4T_xW?@L9nPj zxmtEg%i;Ns=cRTB@S~%nykZ!}P^*i*W9U~ZuUpn$Cb>8D-s9@ihEsBld(EorbiUMX zOoYgnBK;W3s|%~E+^e%Z-5ZNOish%7X|^5rDIfR8Ty{?O%X;=ENX;QmU}8ee7d;iU zp8cX7n?dL`v=20e0|pbE)Cin(3ENihHPFYZJm%_yKv)PiE5KZQY^=&9X1oUMEZUVh zA2=FD^;S$2i=Xj!fORjwOQy_HY31Vilby4u(|)Mg;m`*yP)S$NPr}=Rw7du?#4L_p z`*Tu!0#N&n%7q2-jAGXWRjxq43>kS$2dDb?gRh=g?tX2|NlNs(Fe7*NqkpWx?32^< z`4tOke6ZWHC+O?2O5CtA@T1jq$uW97n{DHhl2l+s3ViQIw{(OO6GO)aYZ^#bZh$HM_7yNYb#A*xQ z6IlD4#GUU&vnYo(pK`wqdoeW>D1mx%)j&MLmd}NkV z-bxtpD*uXXUf5X~{#rhZg40u43>)#YldNBLIQnd^ugR(dziD0$mf(%4by4b zHFa@gM}_a}Ioq+14yWNRwdPOoS944^l)w)+c041vfyK0WxUm2!g^gu^@|F`KHO+Gv<$a~y%elyTYk<|e?8ss zCJY9egjmm&9vz;-DjyHgG)X8Ga%mShA?FMF>^-J)%#YC874ZX&{@yBo&DB>L4tqFA z+(_7H*1dc|Y4JU`@+@UTm)H#-He1%2$i7bZE;xQ6RXZ~bKK}Ak^1DloEsaYitOOMs z>_dKR`(eWP#}y?WhsnOX`;Jfg~`kNri7jeEh$Veg_`#`!oTNSStq<@JL%H?KR@kciFz|E)6*U0 zUwW`;7H}_>`PR@Q(jgqCX|aQmvZ?CA>Dyz4BPHt9Whj|El~c{5$`|^#&er>vI_BR@ z-AX+yWk`CQ4ODzJb=MGnvM_>?QM zB2$ac^2g<$8#GT@hCyT-X)e|QmPXuHA=&pLsu|iG}Y{LqxBxE`do5#3)FSx~K1`CEFEXO!)pe7QXqbkI#)~Poh zyGD(Kp_;7+H4;_$O)>j?y^`UJCFx@yY>+b&aOlgLUz0vW=Y~TbX!g0V*xZ590wpQ$ z6pV7H-#uK$@sw01Y}ot=_szdld|>}^sq0($^XBif%wL+K`BwDVrv}pEU^X{EKEtg| zlW^a`D%<_FldB;GtOT<|G@4Py_>Q3lqtf;Y`R^KAC|(1HZ-#aP&&waH{+YKc7`J}9NLSN*=NaZ5IM_wu5h&xK_bDoT>JBHqVRGN2ELV zK$X?STfS$N7x}V94W8KpMeOQb7G`0&)_vx9M9dU9AXwJ!_k00i^0iGjH(mEXc+gm!sZd=iur_91Q>>dAd@C#Q!Dhms~(ZlG+XPIt{H$BcT zUobz)WfTAwu{%W}mu>(;bnm7gej8T%!_Q~m5Kt=j`>!G}!T#Hi{yO-OnQZ-EXH8|Q z)$I;tsTvO8G*1Mwo44xy(UIa4=!qlnZa0Ei3aXwp;Uo9NF}P;depR z;BvFwj6=_!{C;i?5?jD_jo8ua3A(sn!)6@1CY<#rq>ASbQ#HtvVlFYvDf8@N&8(&G>J($rNEsE1qPK`o>S5+sB z2c9mtKE~*GSeQ9l-bo)1By`ja3*7mb>^FEzZnl>pILNbUQig0LQPStn?!X3$kYz8n zSq1O6>@h{SbTrOupUE`0C**EG%PFH&4<15NMz|xhSn{!bs##-kjhwXifsGE5Ere_5 ziV)0Y`p1;w-bzZ`7PcH7CL%Wr+9bO!&_h!UXAOlir3c9Vier{zgjogJyeY#f{qAHFC^;6Js<6`&k_`aWsKN%zR7h zNpXq*S5a{F(^Mt5M$56rJ}IC zj61@c-jXvs*uTPyLOH~|m3+gq@CW{-*Ug;eis1y#4=fq6!LNnsW*ObnkMj!m-5TyR zG3`GWy0WY<$vUyZsPP;#T5A7%mf(t)^ULUVZohrnc@Rh*PwdMu8V+Z;u*FGBXfC8@xeHwVhNJ-J>M zufYuaK4%yjAO@9C02R7EL_yWK*l-7~+uoAvjqDBHU;5fscqDc}g=#ujTHr?(VN4>F8SH5ASZ#5P`_CZ9i4l^cE!;jow3xr3}Ef*bX^ z1%b(8#}50`3RqzeJ3BmX|0z08FDO`8C~zbv@fRl%cjYEMG0&Ko$RJY$CYrB%7XONB zjQ7O^>3SM!stQX6J*3@rh}4(fyOAlnv9%G9SuruwdmX7bu|BzmqMJ`rjZfE-}}s zM_wk^hP8sZuxbYFuM!OkS2#;U!5YPjt5N6MFlsMv>+@BJ?}k=?pA1JP_&r2y$?;(_KLU;X<>=fsh8#L!wA2Anm%v$j^xs&&bW1y<8p3I@x{s=!(^Pz`>JMO!JP? z8|(K+q=PbIlwy<{O}Ag}#6*vFF@}a~yk7Pxscj^z+?fYJHE3nI+H;t??B;Ft3sE|P zMNFJ*_@Q0_b4X5J4KC^T%CkOXVL_yVNY!ANeTI|TdMFugSJZw0T;?4%Tnkq!kDq8% zxlqzO&?^*O9Yp9^Ixj3A;*%)9A4YAu@A}5JcX@rO@()L|C{*)W!_snT@((~aWwZ_l zfR4GbSiZ4tI3k_Lo4=tx{u1ChM1rc(1!C@P-{!bB-9puE4<_EXI7u8aCHwbCBWnA% zYVC(E3=8L_iDU*RT!y_U6L3N2;>l$bl1IbO z*Eb(O^LEU>2XIrEj^$GIT)r6|FWR%)$pZ-y{w9A^HuowAZd;NtwJp`6P9CrI0eAIs zPU=?KM|;spF28`khNc5ps9URuLjiYw;4`@Tv8wpo(#JbtUt~~!;w55>R21b+9lx%o z%wSIK(?^nB3t_gYUEh%w_XW*r#%ri}cVK>#l$o7%#St6-8hjI&T0oO1m2fjVe>pUU zFR0KWJ63D$zW7JS?9oh?Tk!KCQX0W8$XEwI#Z#a0q=Wrhw$l3RT0ZZfTqmJ;b$H{W zIb;*gGE|JQx0UWv^lLf$lm9P}cEMhE_wFgT4=*pwoRoO{+4BYuE@et_c~lvBKpTcf zxr(ymx@<&FS_b?bqq&>4Z^MGHdownI;ixK?F9?#Z;tG4tW#(tOk0d(RG-tk|#rQg6 zd%vO%K$rFQg>E4Q)M})UY&V^gx>?QJcGJ%T7U~~c31jT({e_i=f*Vflhn~F9&y760 z6Kj1yruRcbTIo#TartlF7iAS)(VL-KYo)D-VS`9EM8B=lP=sXv1~mv^Xf)-)4;cDc zXKvZUr~f7ojthIV{847ti_p~yb11^|lLNgdM?D++=*?Ebu%_VJQGV=*WowYSu{0j5 zZoO~Q{#M0?%XPo$D26eRovRNr_yO0h|A1>S+Y7s(2{%_T+i^^? zGV2KDO!%faB3Tyu37*|Ub$~<1WK8&F1qVstJI@d9c>ElR^OE(mP3^o&IgF)|SYe`# z>Z3#0i|K5Hul02%;)za{hiVTi&n=obzHx**+#0ssx#_h6_p}33@#IMXYr7taD73iy zw3qrW;o4YIgm>}8WC@gC7`j|3#I9-f;4wdzUHC2&yH${GbV2*J7p4dQ*+*9y-0RRWj{eF}G<>2MB z7`eEbbF%}80e&K_(ZTDo<9lTO?=P1m`deP^IB+|AQ?!az|0h_^r?z=ym(OFA!d0DJ zD{SRYN`%kI#d#hwZU1Nr$(E_~@$6rZA2|q`>2_tOdEzM*=;D&qA)|X+<-NCB+}MOl z?mBasC3TH&Aq1&84i`^?O$s{WVHjbC~nDT9)Opf)q$_*wdeYPo>tV;q> zYi0oUx?)^o$G@F{dX!o~a{hcK#WN07&NvDiR5S|k?sS%jB$JFh^d#H!Y1GJZVM0-2 zocq=;_a8V|hRH@0+=i5A_?g8u(k&agYNkZw<((o~+qu{sGP+NZ;8D-b_Bxd|j<0bJ zotq5`P!fn|-N&@%0s=xOsNsqr)G%rNp2yO3-7S}ySsw*1(}EHcys+%i&)^XbtAKjn zI1g_-xk&Y*rObPCcdn-ndd@saGxXTL(8N-<8F~E|IPyVDsm^Gy!x8yg39_zTgU+h4 z+p+D@f5@?))t?FEkh)_-l=fGe6~r3NlM5gB(rdq8A4w$b3kv$!4Di&q!W zgzoe4Dy=uCuJ687+#u!?enS>srUkJ+z3IDotgx{*CfS~(T5|ZcLG?xPS3BFE$jybX zVWMLjhx^*1G>&IK*1hO`4RoK$Pg|XG{xxYdC74`2A_PiHxz$air&zpl8A}Qd-ze+; z5>nnBMEhj4KGSdFM?L>F(uuDrG{la!P!- zeR6d*(|fIIb%EPcKi$J z=xkga=>ImMPKP$v+4^+#Wqrn(mE^{AQm=x2KD$@)u4iaL{H3(d;auzzOhx2mlgGJ` zhRQ9ZwmKz~%Sql!<&CDkFAPIUX9+@{R2pb)g%`0|5zVZy@I-h{ybjB`7Gg*v?+49t zWv!Z*ccErpJS!gWD2&KB-|8h zsH~Z1-+e(|t;HnPU@OT|vmk%j^j4jc;^Tuhr)z2UD|NOb-j>`b4izv&L$9YM)^)1+31k5-jKyE5{>O9SBw4kL z^==`>&6Z7!D7v5(A|>r*C4Pc}m-)4N!(xoc68-hJw+vkCgVOa7!@ndcV;^>wH>J)3 z%#WxWi@S5`45de)y*`zG=cAr|?8wZX0^-i2Q&N%lVlyL4+VqCBb`t}Mk!ndv#Ah1= zD+cTO3>$-=ZgU{f#A`b218#Hqe}^df7bIkV$~XYJ?eir~rkzL39QfS7G%)bSbf>v# z`1#yy8~!T%2Q1EW=iLB$4^x2JsaSxU_OWUCSBUacbLx-eaXNek^O+GeF#*VdepS?- zopgg+u8?fr2VWJ>@ zAuln{wS`r7eNAXJF8=#$;ja~#InJ4U9)lk%*+?XJ^{ly1zKh@S@aKG!cVxb=zs`|5 z&5LTD#ra!q!x{BT-HI;1^De8MEaC1^DJTr}p;2=C*@w*P6v*#BAK#F1;( zXI9*5QJPiI)v&CgyLVM45`D!b*)!n;&(ePvlPq)vJy@_0OJtM;L&O+kn~}krAX{9= z@(rF_(7?TuC;2j$^ebQ=N(wF-7sH^WgXz zGl0T2Cu5QV{&zQPsU*s5k{^J)W|G#O$c2qiH$QtAEpkS^)MVm{Gi?ju~t*RctiX46T-@rm#25yHKU_ z$5n{QKQty&6xmrcS9wW4MW)`CnmPA2yl;81Ajv-O_Y|+Q zJ4Di$AGw8joQSqtyFLQnG++NqVghRj3S%%$azXp0ZqldRPqPxHeOfMKOgmxC3-$=( z-XxLCO;XDhT?qgELW+j@sJF}N773ztmJRjQTfa?|eP=r-ztj6A6abnQMchABmC8+& zE)YN({&sM5Qw00JC$jvn#hU+#Jd!Z@)e+}3-$ISm0&Hu~6 zKiZeqMLAjC!$CsHZZ?VMN5D>Rc7PQb3iPR6%uOf?1X4!<`eC+S$~whH7OVptYgU(c z4q9i5Nr*;ig5#i9ZKRiE?p2i5AX{hBZ58IekiQMBzt; z_;+Sd3k5@acroF~k0>9M9b#VY1T(|_WQc#M7WhH68Rg;_w)e`p&()4cXGt6yJ-Lwc z^yeYwbHBZF`f=O)`_euxTS&5?b={xL_(-Cvb#P;%I05nl!uL}7m(-wwub zFGCT0KtqWxU&cxuWKafGH>@%hOagImW2UON6^!7@E+}RBA{)z%jC;`B23{;@8nL@E z{rbjbrB?#bJTWk?tF931D26tR|8&FDNDTf3!?gs7o?alnl-NR|Y669GfeoH!Az>De zqv*1;+(;v1t=6W6)of{=y(M?2(U-i~$h0GvBaakW+fPK3Q%zVbq}yrns);25tq7Nz z*1W-{!a2SlkAutlZ14ugv>0U%F4pe2c5gg5E$Y_L#@B#mUCp0o$t+omb_D;wnh?tjH_j->hvR*<@Ushf{CQvRmK$bo?v-?RG)W zIZ5d9)v@+wFrf7lsy7)&PFkW@$82*48w}&Om_B@7nXbt`oP4N+n^G}UD#UH0PVniv z{ix8Sb+#@+@X>=FFGQwoEA{SP3ozOGi6NxAdX+b9X5QY)|_9dcB~c6J8f4 zSP~uF+Tt%E`L4AyZpKiLbJror=^)g0n%?z`Vy`Ba8q<|z4(Uzrx9Kwa{z2j#9v|p1 zq2g}rINUn_K_1u--kb**mUwq@-O<^c1QH0S?YX5A)F|f}G~H}jn|^upi>;sE>F7qY zjc2-&gX!C`H%PL1@BA%Y-dZJG{RBJaRqwJx**sh0ifB)euafD8ZW1)1AH|OsIz1Pn zClBk?mp3g-R=W+e4}R=3B2seqGNk|ZgYU=LFd|z_cbCa&RTb&jz~4ytrSH#$px~`gb@wnw^m#_Jx zC2D`nkGqMqrE-41dpIFSUFF3~3HeZueb4f|%8uq*92-bH-}vTBh~K$D+Vy&j7%L0lBlg#aOj|VJvWx4lb=(PoM2siylJe| zTtZJ^8@6Iwh8sEpO26x~Q{>zBulKh}jiKh?R@LoS=h+wKDtGc$*B?ooMJzs_B|ZQ$ zhSX4I=h59P+*gZ?QLls({!|vTIfN@(s+dugkW<-(Hq$$MYVuIRkwL0`VKWu!fZu{l z9v0<8J1@hu>f=m8zIa?|i$i>?C*!lj@4ny@?~dwy%pWQ1&Whw;E25^iNRTQr+xna` zC(V4?__JiYIS4ll`ejAzd^|21rZ4l!ZkP>Tfu6R=4%=;=ARVKU;ZD%2nm2jko9)qa z)^U0dl=YNRus^(K1!jgqT{S07dNTg-$!5&g6E*&9_gbXJy(F$LE`?6MmcU;hWGpTb zu7K6A^OPiJXaDKdl%e2<=AE!0t6OXX$rI-1y|hX;zUa$0lpawIbA6&+dh9upP>`a$ zeHs*=JB<@Qa=}pvUe##8_i`&Kg3BQAh;xj8>Fb5$mD5mR*NF0?_M-?MJDgTD%shjxe+V}j55qRx0lpreB-Er zPf7kUP5c7(e2YXGR_cWONHo1z0I3P1rbrGxK0vh<3ks$pkdHFAClkpMB-_See8^Th z*eSd7ouHZ!{wm7-?bv;AXzn}o2N&#_kRv8V4f``kOxX#Zl{?w_A(b;$Qi8%e!ixJ6 zs#JqrUUHCdO*ici6zM_w5ivT45ArKi^^DwMD97vjz-uEw0?g2plh(y=g$<7K$DG3K zGx&nOF|hRD;1pg!#rmREq>dssvhRzqk>eVs$Z|~@{in|mJFaKkKYOx5&}S$@_nb10 zTlqjzAx_QbcDV^YfVY#^K=3g&m{vQkR|aB{`bc1--Rn}0J~pK>0ZaZYZ>mKP%rrs? zRrYo&VHmi5$Vf)7-5h8kt$Z^mAoYd_=_cfK_qPD=F}g^Jz2b6aj*`nXuZh8R^4lCi z;qh;Gr(83?8|n0hAlt9(gTm;kIGzSzPYCtc9c2XbIH$6xkHg_V=#5AgkBH#zXw`g} zYlv8rm2QULeHe9B?3;YEauiC5>fLWPW4Hv(o*Z{8*tjb~is-A2d)nZqa0r)LG}EXm zgA?)4zkSW)si+av>yv+}8iKH*>wdRSo}HK;c~|^AAMi_^GK%%5sh{N_=4jKY_Rmx@ zj^bv_X_i{e=3rahK6M2N;c+rk`*J<~!F4|9prWowmD(TF<9^Nk?GuCg-5SL2)Y-Rz zC>1JX>f82|IPX%1#liHJY_l?lEQ=Yd&ZeWi1#Y(>r?GoWCHc~KVpnfMV2U}}N3b!c za5;f4HXKy!RU4GB+2>YHs&L_`7qlN?z1&jlmY$C2$2SC! z%UrDzi3#4%+LAkG%iNDKww1~f9VLuUmMW8@%V7H6{!w>tLHN5Dp^saQU9JQuJui4M zJ|-#F%^$YYYDnA5vC!p^dKym7GbBvmd5$^{LqA#i8j+A4euTljQTIk!rJm%>11H+Z z`-}xp7vRvwS!erY1wsEv5+=G~-^&h6`ZlcFR{xO{1I@5EIAC3S9|qnrm%?h(Ee$UP z#!ZGqHjK;l2DRA9hSIE5{d?M4bHsq?1JEdK5fZ-ZcE1Xh$dTSdY^CzTu=_T+Mg;0E zdPND%6unyy{pw!3gy1tr-UA!g({L8p|K6kCiehV0fYLE^UrW@)TLoKSn*4_zixb{!{3qI@-3oZukM#Q=k! zT@cvy%+-rtlp9KZ^P_!`M0|>1;9gGRGdU9tI7qk8>5G^iA(y^Ai-{Jx+WtE8(hKC1 zP?D^_`A%$NW^1r2u5jj<>mRw^SEITVFddPeH>_naO6gnMNi^a5@UJD4L}I&}7bIJ_ zC=B^T3ZyQk8{(E=Y*TRuJ&sQa=%&9WV*C!pNz}LqLwWVKo3ssfn#P_tn_c^G*f$M# zQmj|KfNl!m&-1jkFAGyOx0;#W61tv#&Q=aP(SD!Xp;?>Eoi44ieL_892e#zRQoFQQ zC-6C{m0ZX9_sDVfCn#%Q5_3kT{{gK2Akbx%y*c#q)8Yn|{20^k-G6LCdy@awnV*cy811na7-+5((fI5lx8wbyb0+yZhNmq!z_^)R_5!1 z<{!uSsGWBWjbSkdyq=Thu=hxvnfjtR45K_0wk>0NXAi|?DnpO*x#kjoFG^bbl8OV| zqKm@Gxp%iax>(`7>m%ullWkb};sDj`Hdff<<`m=GVVCallY8Vw+h#&29j`wJxItdp z3JM#l*4|yxF^*&<2)Le>Udi*`6Ll%Q!0`F@B0~}cIFBCfg@VD#wY znh;cG@`T#3a#Ap6bwMp)3tUW(9V?KMOnMU2(1$7cq=WQWZJ~t6k z8@JIU;11j&qHcP}#p=`T&aa>CQc}wP==%NrarZyKZFN+}PIPpdR%NHio8S3z{%b{2 zCx7%17r7-|_#ni#QV&9{MqL+{UL1H>{joA^Qfp2mMtvPH>7n8mYeD6hQ-<^Yv`fdd zyH^)q@?LxQBK$FJETdSmQ6_Uk#_+j!wqpiRiO%bg6N)PRy+^@bGAnYqA@Hdw}0=XfqG;a#lQ`^ZcW`B#79Tye4qE{Y4cO0K!ts`Csj zXE4l0?Tu%Ok;~6P?1B)pg9R>VYVD16daaJ=i~hgbBMrP;P-VG@W5nK`_m8FjpQ3&@fI%}$z-;q@(IjQPA6A(;r! zb914Gpq@QAqLu!-@i8SY^-tCT(O0h}f zg1uvH()`@5-@BDwGuT(OMyYNmR<%nBK^M}}#%6Bo`EH1XpJMlq!x`fS-}wh4^GLVS z551jt#%KzQ+wXv_1g!&#Fp9+;#g=k!?N=2Pm;#;4%_?K%nn^mM}vgQ zUnGs_O+bn&f_69Z~XCjpR2iW8VEfbQXJiWl-r zTU?I1HL|fjD~-Cr1*kCp;M6nQvBP0eYR7vjqS#pSjnCJbj}OeaGa0Cz>GwC{DMxjG z(tx+s0T1|2DE`_c;-pa^Z57=#`PU&ps`z_arCHF{h#MVlz=vDB!C3bC>k4}Td-*-g z3bgQB_-kBD%I#}{Bf9T{=3k$BXz+vOPtueM~o*d@*42K%Q>)-HHiiF zHi(7wA+-{N=~KP5zR6Bzt*et$qY`7el=+UORdXcW8B%T+Zl7E1&9EB_O0&00Ql@${ zy#)3N@mF526RZb%R5x%r;+ULmz35%fvjO5@G3)qK^BrY9&vLAsPF05xtRtP))}+1c4*vc>`0pgfb&U;t&mW4a zLG~xSqtrA1;HU{aaImtw8+YTLe$?YKLf)8M{`v9XZv#$mXFBF&QTd!Ri4@))EG*Bo z)gPwb_nMR&4Y%$@3fwdmC|f&VGg#5Pk66@CuWJvj4L!t?!;k!GKthX~~$61sxu5Ya(ke$HaN|fxEnN(2>kTHZ%M|s6h77(d+)6bJ z7n!n6%O$RcT&Aqg;Uz2fOM-hn6{DT9EU4~QBL3X?@@U$pM{MwF!BUUAjuO1VraiKf zs7~ayofvlVk_M!|w8)=UCP|D>f>yz%a{F%`%XwO^2jX0?Kee0NqUcou;TzbFie>6Z zjjb=eYNTdRK@mJZr>~_pIlD=i&zMg4{)hq)0zps76$Q7NrC6NO80^_@HB&rZICQar z8{26XKtnT>c4BQAyHFDmLU&gh%W~kOlC?QS^qf__6l465+}(cM)OgRZ$WE(qZiQz; zGkY6nTuKtAYfE!|cGM!vD=dl;O2r*x<26lb`s-IAFkQ6Qx`kORV=|=1c0*I6jp#eK zg1<`y-gFl8qibqgSqVp_uQ;Ae0=|hTl;Vq;!atXH9<6iqbb1Hv+igX`ZW$|u!(xPT(Exhy^uaBr2WDox z#z#!o6E3=b3P1RIw6x}f!w15IN7Y9&@vD%~@ixf~%R|H#*}>;Z$r-BttIu4hYr}_B zjCFlzLKN~c$$XzP)`xZ|E-Z_)WaRPs2}!3;=htqCwAVJXsUXnHtys}or!|h`Zln&< zhv!6c7B7Mccvv`Q?tvJCC#HHdILP1frpCu6jd{he)atK12TB)n`Mh+umeMTRcVk;F9)C4OiQ8e( zkzw-gAuDXZIH?wr89GTHbLQy3GV}%MLuTtYJJ#v?ZM#t?s*v)cD#mm1@!F$!_pA|| z`Ter>L4;@4T~qc-qEU0>hY_>x56LEG)@OyaChkN+4z{ zQdcvj&lJ+{shNXchIO6Lj5HiT%Fj#DD4Ex%_qwH>E{>80)|J!ZwN}FyQ=UytzjeI@ zA5o~v)Fw>qJF0tH+5@dSP7Xe4==mlW2Lq|f>#EqRZ_6gIBeFdP1Ljfb-^^7jmt|A z#$7#4cukbMPmC%yJ*O~Pi^!v2KBYzTr$y;xjvH2O(A)So{J}buYR`t_3zLoAhUGEg zB*W65Hz(FX^ZAK0rEX&M&lL?Q35>yb_62&y(Xw|2t_Q_cF&cKdO_&?IX2Zcl61V^6M2m$exIkCvP_{ni=GSq==V%M_7@L|4(Pv{tX2d z#|_6b9#+FGEVW$)_WIRS_G#Jy4B=VTpnn&_nlrnZh zq)mAYMU2O2YP=#t4Wqs7Ir|Un{o$Vb%RT3w`#s<9{eHjq`~7^HCeOW64aDtOx@^o6 zV?}MwQ_nY0By_63z#?A#&e&dflI|94nz%D5J}3BMS@aN4*vSha8;~M^x%a1PUmH@# z;w@0{DAM@HS%w53X%SyzSg=Y^??;bDhoJjAgxp?Vi}8c}1&HXxN(d?5!)h!Yg3ygy zt?iAm;Ju>s$MtM?Bm}p7D7?pNHjS;`;&p_OMh$W+=DUmXp3CfuqpXiB5gOLSXD645 z;fXufbG-*{35;&kG;kUWhIwM=$Wye8uA&fgv@=f^fdxOY6Q4#k>c-g{Vy8Y9 zDH{Y(C>zs?IAAt%ADSz0Xd`um>asKOOvYnMcyHiS2_ui_f;o#dJHd zJy;Dfz1Kgzcmxfk?=t4zUG)2CZ{0`RTN_mbaE%I$3+}3taV5O-Plii`6{y-2fz9^T zNXJ60Xe!f0>(0q}7P7ubk7M{o8oddEaL%M{-rC;MlPmV$^Lh3`g$4rYnk^m8Dw-|P z;k7dk4FiG#8>u@Ytvnpm*r@ouPx052X>irZw)sdhAMIbHeWsa-dR<>L#&~V4$1ePv zgohyhJ;{yOn1~sI8xbZn>^85SinD1lv)5vkf3{lR{kFh_aA8e&mF1Guw}awBEp8tp z4UuBZ2ytYyehwx*XBiMB?kxP><*ze@r2$iF9eSglaM4HL%Ih9wz6I5j`|_9EiMRWTxLfoh#D0Cd_+7`5 zLPm>Si$2*ifYfT(LGB!E4&O{kRXkUEmRe2$4tUwIBTR#A8^Gk1F{f5PCeG4bvU$jC zs&F59WLJCDsUI?DQ&?hxG_8Wk~88 z@2Fx(13O|#N%b`*AMD~b(=0hh&%vd&DK@?r+e3O1s3@hHNRya;ddqx+xpdjqtUXW* zwQ%n_PQ<4I$Rg3eo=t1NG$<$l78XwOcF06phZPpy!VqDj$%9c;C`EDVWM!~YYcOk- zBY^5h=N(WQKmrr5KCT%!#i)Qk<0x$kO3`%8*e; zZ#c+oRZ%$aSjd|`uI4__tzkT?V3|PMH^JF3N9z7UJ^t)o4hop^<8FESP6)ZsoW`hj z^i?a6g#8M4ni867^B!$_seZ`-P=2%)cU8JXoAqZN8`O8!%wFmV3E)~tBl5hAGOM%7eljYfq)JhOhDWXpyJ`-A)*Gro)1lV-7yjF%vgD z*WZVm>fEl>9e6CfNk1=ffJ7K+^QKS_B5W%-x=%*bG3>4@3qx>Br5%P%w$|gprblx> z{Q%btE-8CSXKvsY;#Pw_( zGpV@ou7ZPd$eFZ%>&@h6TiV(iy&?K#RNQ=4Cb5UOG@t$>;c^88YR%SJEY*!$NyOLN zxMHrYrUvN&-JL^mjo`!*f(qtA*pOdmLEkC%DyB^mSh{u7{*qXWHk06`;=4&QDisvF=NRx_9bO0 zqGS+bh^&JV8p~ijQ-06=F30oy^SsY{ANPA4uYbld^S!>;c3$WCIX|ECiUFD#aD zAl+eLP)}oE;P=UDvbfH`z~}=s0qILfT#}ZSRa3jJqob#yLa78O)aXba3GNFg9rY;zJV4NVC5I9V$$+2UL;??t{oE--`_vz z?fuZ+-dWzixvT3F3RP@t>ku6sC#`RX!!>$%z?GiOo`u>9N^8o8^j>jHk%l~1dOR+n zY;@W1-Zc~7PoMf9J&L~cBv{eAOd;Z%tb571bAlH&tp1VF5LeKXcYQ5$Memw|2_N6t zl9IQO0Od!u2C?sr46J0`M}ez0kuH7{70lh1wWYV^za~q;4CTeryl0GE`SoFS9UYe)}Bo;nh~H3B?m; zYoe`w!`Z|t{tm3wAGz!sG4Jj-5E?_fdFx@&li!BUsodN=QJRY7w>5{X^%os(`>9@= zvp0%2zFHS@VJYJxtyqGRcX1;};+}#}^z~O6x)_W>v*R`A0==3fEmWx5v)r3+{}Gl* z($2jVfVutVi(YiCdBG2}JfhMAe?t@S(`Jgs%U`7fJL}st`rq$e+UVlGZQjPin%}qH ztbZU<@0D(*b#8y2*-L!4-7va4O+FxP9$02?s3rbVj|pU<8zpOik*Pi_iJjxjMGe%C zn`-p0E(Yk^1i0P}a8q~jbEAJS$X}ILmbrFS=GryTHC1&5WpyQ0>8n@OuU-|FU4^iwVRw9DErJU$>68M<>q02XmcBihln@> z1KVr8+BCV5Wj-H$imtEYe!5l0mV5BM_}CKle6zhv0EV-WD*JS-sm7Ej7tBA!&v!g` zVa#=j8Ix`|?-V&Fsb!>ljKNjE(Nfk#V@y;8|BOw=e16D$zq{lqHQxLs5WtXbfqCsg z=us+t<1v@}xT#F$0DsF*nwrPRP>r%qFQD;^7GH(jEyTya;zj3%kcY&%rNC$gX@xr3c@S@2-tJwsEKIA4_HsgZ91G(kGJMdi0k#f>T4POZ5lqO5hW5;dGe1Avr1dlrb*5b9Z~3oN~(uj|5$Uo{YySB2#be$UyGyLr8&YNBM?WDosbf(xDK zgy*Q2LdE{LzZO5aLINP1_P_2K$-niE91b^QJ1mxOZqB$TdYf;2gA(RUV1>vIglZl~ zGkBHg01%_6Pj*ET4?GsKJ!=R>O*;}VP6Iiz4P2xfT88fjlpHi)edk!f?|>UVE3O=G z;n(o(q1SG2k(n^i>7kLFy_2?sx3PRrsh~0BlJokba76w3xP3#a0EkfvNszjM;?q+krWTZ7G<%jf*~RwC zD~?y$L9$s;3JtRX&ugmLnOz89_83!wEO*k_*$=<1aW2fRDRNF5**ff<9Gt%-D z+GdeFjH=kKG#TlPd_a&S8T8aax+A|A1~S3-1?#d43${-cWbWji0Rl|)!?fXQ?&Ke| z-3kTNmL}Oul&)P!Z8pSNU@8G69n(3MTZ*LsA+pjd=Jwwvl zRTKc`9?p2vw!F0tC;Y`qV-eEcu zhtRxV-&eVRCEHl5v~3)#+WiS>7r1<#2WV$#+8kV4EWY$~<>l_I_Es;C3L*McbN@Wi zVg2^`Nb_jg1#bhrRJiJ%l7nnp z*s0cCpyKg5+UM8GowC+%<}|q~70Y|gdnOHlC>i`7GuagX z&2vU>hYe93HV%oRW;*R>%hBdH%tx zkKw;)DrZJswtpy!l2!ivJ^!AeY%FNJ~WnCTV{SuZU0{wT3>M_cn-;_$R@ztCbp ztxX!5Wtamkb;>*4lIyA2x36TZ<%DA5(BLlz*=H3i#v-#~V|Nv6Q81f00?YT5@Ikp5cJvxU`RQOfeV$lnixre?PI0gV zxfkYB1m>4ZS6#{2V;?~5L+V9KY!*R8q!btQ2HmgagTjqp+h7_rRiO#uIr&5Uz~yI? zTae@EzKFhv`l{=zPER^4AI!)#l<#xT54GJue>tpsf&OCnbLj_?`pr|qE(AbvQ>t_7 z9(XgyaQG&me+hf9<&Yx@N#*4=1K&D;qJL>?gLi97zh#=-5R^EkgwS@VAJ!Da*0ud3 z!h0N@_?c8(#&@0aY(|BBlpok<+T7|J82PC`mFf9S+bPb9#n^)I28t_KB>MT&enVm_ z6Jb&c&2C~b?@RC+X(4`EXr{dltuJ*tUbXcu;zt%d&0Xc?=-V}Gre$Q)CC4U1GsyKY za3_UV=+>7`Pd|v(bkwxl)-k+Ka0Dle;1wX(ImL!GG3EFn(RmCXYo`JW$VzK0CYco) zF0}l-s6#nhp1+oK(?d^NCV(>T1!e`cf>mt7K3#uXU|sjwa+taC9h08*1lzvbLEGhsH8sGcm?Tee+f1F;;?B{X`genC^XYLS%7DpvFcwG1S60gd=3`wn$Cc2}f zYPzHw*4lHV&facTM|Ge7%^P#@W!Y}HHxRCI3OILz$_SH=DEWB|e_(J&d(-ddmUUkt zGz!B_D%`EDWC70($wjQIxTxo!Jb{j`jEih1vEscyrijI~IOi|MiG{U6cQ7{8>M#>U zRD|jNPIp}NRI&p=cejfREh&#kMI0+Qp-+#2qgD649Ip(NLcg)5bXDxzo;U^yb*Ta- z`|D})u-zW{MU>-%=3hANU0ELE#H? z6MJgc*WaZFBL}vefhqyoymO^(tac$M()fX^6>1+1Av;ePwJm)C2xK^m!K$6X%=+6xDJa(f;+gGFcZP|4CBXD9kN_zbCz)4h_iDI;LW=QH=W7fs zvB~A;vIB7~3RAgGH7^d7vVTh$c<^kG(zGOSIS8~KIIFzAj8re-GCfa=a7y2UMb3Gl zbZdV!=x6_41wHvlwr_1)RqYS}MY6gb@B~tN9fOq@!&_Rr)obtZZHJ!QLtH{u}6~{qD|epFJvyHozabS)Uu~wJ(5|<89lpiGStC zM8cBMmu=mpA|uRLzQ1w1osw_5@1$ZEKF3kn!Y4Jsh3>2KNK5S>mI^SF zut-lLkHZSXL{T|?tLnv2pY2ap^>2`6)Ryt2lE;3y_myb z9$+D*8aO;;b#QEcxSgzu@f(~XGBuaq+z;0jd55uk_xvFBa~Yt=>y51tkU;J_kqX-J z!Z0*?-!+v(27sb zbuYVBv6ccKZOZb)=5V(y(D_I88YSO94@r_e-}`y_MzntG$n{nS z$`jfC)6v#V&urDKG%yXP4?80RFIw%{;Q>nXsLI}$0GQ@)%>6H)PeuU{!1C4SbZ^2m z<~S{;TKY}*r!Fh53mf@5XpLfyj%*2~Y}hX;uk*XA=QcE$M{K;B7Qip$Mv@y{0bs~u zx__)Xr&uIq+J4!)1(WfM0xWGqVx;Wb&-lAWXlvGkQ=IdFLll%?-J5J>6KwlO1A3N- z2zL-gc^TZcVx@s52Agt_(B#9WPs@)4yIC$*puI9e1iqC_qG@$uj*@k)v-PacuQtwX z-yd$a!x5MFnkbkBGVz0ZoWrO`&~W%^kal^nziT$8k6|K3khiNBJa)JKnwVJy-3{Ie z-cZ_LB!zp+aqp)xzH8{Fhh`gJOcQ*!u%afu!UU`KsC-!}W1txe%{#3mG$cF8k#LvY z6pF(Zyg702Svc4CYjLRvxZ<`ZYWJ9WlC59#Ru9Cv!J`ZwjU=$ zLUdFP_(8nb(GPTRM#+|oTuR{=kKRkFK6gTwQz6!A(<}CSi5L_shav^k!ZP`&tF6t; zP47AZ!|-nvi;M>X`1p70@f8=YXxS?jWIaxKS*>V$5c>8mh6s*C7CgH*Vu@?kZ@(Dn z;1(FP2%5g5ch%8~?$g%RrBH#7E0q_D}|Wn~a!U7w^$*7Cr_` zUVb1pse7iwrHg|X9B=&<7n~Pt!QtOJl1F~7w#IiV$8tD6sN+*IU)1`D$ayXA_l0{5 zLO_IxH2VGoC1@&+dhXXk&XJjvXN0)U0Q6j9x#k709fxnLO6 z{p!OdN0;F01rZTo`;NwY{+^TQkgV4y(M}Qn+!`(Fb;QV}F^sFk9!finX5OGt7j8y} z0&sko($G^PKu8YeN#@&?-Q(NE6=tC8q7r01{TPnfS=rr=hXM7-2eBL{gP zxS2p{ZXg-#Rxy!UU_ZA@q`?5nU7te@%*Jos!rqc&h8OxWmekaNk=pU83&%hTNUW-J zHGrW(7?OLhx7}{i=_E5KDUa0ThZ8)Bo;-D4OAb1Xgr1C^t{`aODx9G^fWfjwk55IG zi^rJ&!|{>#a>{%baU_ELnWwTb>Z6Fk*!DI;&{xPTSF+SV|FFR^yjuE87lGYZi8Dd>f_U16MR23v(=O$8v)~tULLJZg z3xk4h4){_{>si3A{cjv{Uv0PXwTQtEucM>+opzH2b`Lj@I+lKVRs`HN3r@@lBv!CQ zBO8lHojl()b^Hatz?yiOA#aR)OHX=GRZ!TE?YA<4f?jUeaR<@qi)A`8S$zdg+jW-5 z5O4zFz{?`V55g1N4&*fqirl$ikb zGjA!|r9d6rOX)de*Ny;w{G|ZCsZ@u^#UnIn;Z`O93PKo}iR0!pA3A}}WiuAfn|2Y% zR_AP^pmBIqHw&m8iW9{??gKX_CMls0Si`{tM@CIh<-k|u__s`l57*I>H5yzPjfhC^ zYHM1I90J2gS{4h8SgIOjrC_JaXRk(Rj=pZQOH8FU@dJyz+o-y;zIiv$dDBx6F0`Mn z*|65^C(1GWuI}BtEg?hY{iZ6L2ypZE(07~0W`WO79W;QDaq@DMx|g;lFDukvGK#W? zdKwDVswL?2DviPy2GJb-Tcbw@ZETsFiehXy9VLqx_w#oy7u3q7ZKXGdnCVvnSMPTO zKx^MCq20YK$bZ5d3(U0j2KtyZo)zcG={>XdM4}ZI9at+wV8xx~4mrrO-i}ib7_}Wl zs<&{#TD`gg3WQ2x+JCm8F|}>Trk}!|(mTCyje*WhlVM^^*?tUldE_k;8s|AHvVGe! zl$KJM^J4pX>BD4a?slzqyek_egb@S#9=Q@fbJzskPJu)iAA}iAP`dlb-y>`x)TW)? z-5x0vpHuJ>Sm^-o2`dC6MSyd2`50JaXgTz5vINbA;MzXy37?T`=3d|*c-qQa5}_Sd zcycd>s8|qtezzsoz3K6K40{M}uc?ex>cGHYJ&0(r|C0rD`g*SYP6q_i72?=e*IKlbxtR=s_2xtTG|VI6=IO*Yaucr}xt!RwB%tA{2k_!$bO zaD^hyVet*w2R_=wb(Yb-W)~)mM(m-UL48Jy@v)%eVfNhZCqp%gNw|j%U{3O_fd&#; zG=eAc?dQon0X8T0>uy1@Lm$$zbJA9&m<-mpSu~>uP4~SfR=Sw+$(M(Ox+=LsXqY!y zbZT+iooZRan{8s}AN>yi+~3UG-}RFPuRAK#ix=7iSS2}&;D79wa8qu!79*iwA&DFF zI^k8oo${WLdqY!}{d<*-9|~Q&maw^2n>iQ(P~$KpF1oUXl&~AL3jjM7FKLG<_AZ2+ zEX9tZdY8@NAdaqhY0hMU>lTL?qP0=YGH#&fatx|`sx%=dr1XL8{foH<#$=uTPjcVx z`CBO0VC^BEm^XQm!uT%TWF*w4`i=0;%>#sXIE+BzhI(b37^`~&>b_pZ^&r&i;iGHZ z6fGtAj$8Q3WJ)1^9}}%PuXD^tL5e9vmfY(Yj}dfr)0jZu``;#Hyr^+h$J-j1-GSDy z^J-G=kE&(W^VN$-749z??bYPno!21_n)cWw8R@aAtjFCV5-aWY9C$&)2_(8nZx*M@ zMF`{Xqzm<29-VugAAum%g=B1IH|SVC3I;N9Q}#5{IBp!1{KaFSxE=)JTj4E-$$7ne zoxzHzswgHWYAs^1PYjxt@_-*0G3Oz?<#$D-Q8_YeeC&JXWF>*^oE3id=DyLZlQ)E} z0Vo+%)YFB9+(Ho&;uuKttd`%!%ixRD>b58NH=SJPw4MBW%)@n9@sRy;r<1(GX=h1E z!V$9x7n?$~%Nvg=RpMftBwFTu7uvE~U^ZoTNxM6=k#Qzp22#O4jPTHMimMS9&8cQ} zIHl$pE4B1_ieaex#`sof^skgcb@Kyhqj(|UTA$>qCPWArw=DK+60-MIL{cHHEM(o# z)bNy5lvH16VPEs5Clwbc&!vq34Qf%07%*|^`NyHLQta0=3f9eh=Xu4t-21eC#%&yKW*<-3YUilRz~=l1W?L z?exd_;?2utyz37D!>!4$Bj%h>hA19~W+6kqAKzY?Xlfls zFAbw6Ilg>pAGK+O8IAG+!Cj>%(YcYs{U|4eWj8_Ef=DJaHMwz(cZij8F=!bwb(~lD zI65w|rHWi#8u{2_-}4^&;1iCXrNlX0&dzgt_@$r`KB*SuCq?1!X4450iEU zsQcWWCVwVy+3!xmM|m#_%+&GikbRA#gC2%}HDttOZ8?@{ott02r_E7 zjLfUl%eXLCsGof+9lUMh9tqiYC17qNMB3Yjd7n8{JDwPg@|qY68VH>hRVB-)RtoZ@SYq>49& zamDJzg!}d%3aK6qQ;Y2k+8-Jdl7qz3CmJ`Tec%8vI4eSDYJUTp==5-Z$GDn<1ZmN+ zI25UWwY1o`Lw5C0Le6`5yPS6Gv zl#iVMX7o>O;0vUbr=iG04zHA@{VLU!`J}yPputws&?}2qCS9y}-ODyPqn%Dta=6ub z;^}Y}h94O(c!6%=W4O;XF}7C?v}K6?%V^~Zwqo_|+u|b1ytvSV&BH0IsUsO7pn-~z zmq0O!2cHvW@cQ@=Fg(u1nauk1Z0O6^`1=>WpJg%uMX|q_HOF%?sJK+fuTSVT+^`kgNy8hk+ z&W*gZOQ@|7Hsx-H-2GX}4?4ex+C9Ir@{T?d05PbPgVJ-aCo{C|vQN3Hta8(z@P6`Y zagF9^uYC|t&w#k;8iea<0Pj0ftaXLkmdQJC1*qhpG)K+ai5C{e-G01!q&AV40eS7U z^eZP(?qB#bxL-e^c&%$AS1&h$!^eBr8Qz_RrKmkMnjYtwm#Tk|@HJf&3oIz#4_^n( zT1nAGmU9;QNg|^q_IKZ|DWacHKbfD%Qx?soODy^Jm#v<~dgk3QNnM9yV?oe$)V|R^A0hI;AXHgxouIdB(5FiX^basQ-_<(zFU8?> zX-@R;IQkY6Ntd?28EWe@(uEg>mp2%t&WNHI8IZqE1Cto&+6zNrA~KfwPh-(=X3zy~ z?PClKOX4Uo0HYKG!z~d2FWV{ls%TUHQ(na|H+!M$y(A~PYxkg$-Otr4vQ72ZD&002 z7>WiPzI~|Mt9B1>j>LXqgtaQJszximCNWL={23{*f)B_qP%;SSkB@Harwc;EjlK5e zHo1kzCzL79rY+^eI~r{CNoqG*YOg8@_zxlo@!i$|#0vo24V4BA=Q(RR@chh_&C$Eg z0Qm*utq;e|3Qn&&sv2p_fNu|lIlRo#@s|0sWS1ap=#qG)J}H&+k^Q;_4{S5z<32v+ zX2z3HFlATA@QO1Uma%`<{k1eR-nqb{x-7KgMhQoJXew^%zTTN7W&oniGIPr~sunM` z>5DxYm#(&^=Yw9BgSjR*_>`w28E$7C-Z+>9Mkl2nvN4h(iXKu}e+EihZ~e69rtb*S zFB;i2rdm@r*InU;5WGEFZ6+Wn-&he{pG8-B!1t%Wl!v&j>FS4l)pSiY`uxQAakv|dok}NEl=$`neD)HueVvUxM zT*Q;o805mrK=!9JD6V2hXS1bKl@eQ^u$F{x--RG zl&xJafj4hEe05V_dZX`7$qkX6Nb8+9sxTTBIdVQ8c&_UQ}>51<#B6oAjh${86BRTd5 zF=v3@r~i!O?9A<`!Yxx#2M@)O5rJxX+?Bq|i3+zcE9a>k`aQNUrs&N^Mw;eFW?Gh> zK-C=V>@8|jg zsgxg4=aXJabL5*;&ID-$T(I`&AQUXAG|x${_zaqN4vn8ljIa#MU4NfrBf#^0Gy0Fk zJ#Em{Ccd)zE%)&%^!qCZ$Tqv*3w)`4J^!S@0INC6F0B-XB?^!*o5*lWxJ7y(Wz+*a*1I;UW-dV$J9~i0i zo@dLoW;Y$OW{X&s(?7+Nn62QTDedV=NM%QdHkmqg8h{~$;dKk1e1Rc;U4Otz#{1Zr zyVLq$c=J7$`!P0`v3KT7Flz6ef333SV)fOute}W%Q4; zz4Kg}8RVZn1avYxJ;mM^zZh`)+E~rP6T9!`EGA04FRt1(tv46MY4~RDdp6-(yz20r ztIb~G!GrN3ls!9r#P(z8l}7kV?5!O20WxM9O3ODZ?qGFr>_uYsBp0WlN#dtNSE9Gk zOc<}3M81R0@V7U}LrO%=e06UAuk>L3$MDYp%jXO}<2N(IdC`9;@H4=mtZH6Kp1Y*# zB?3IUeJ!4(f_Mov8ucPKo%0*zd@|YXPL9_Wnwx~8;~ur6>-|JP{9{(Epm1*p@%i~r zC{2<8{{CR7XE6&kMxCrsMPcTj>^=MMVFBRqi`Xl;^N6mYx1Js|qZk;v&3?%d(kp_k zC2E2JBg`OTOpV|@hxP1YT5K!FkTYHZ-cjO)Okmg(3IKF1}#Q{CNi+`8O z+S>BU^$I_{k$V5@N^G}Sj4Q+sOnG~-XP2Ts1R}HG`(iwS{O`Gv`!~Xs(6l7{?DVSs zmMWSB%-71#hIW3nT486L74#cR(DNg#6eA~Xuz4sv8SCc8^2e#0^r4NjN1DvM-`#9I z?mq+3B=K!=dt<&j*e2L<+dfxZ-*X|!HTl_FhiN2A>$Fl^hBwM&|kMDa<8o=>MlEQ1!iuK^> zT8RiWdK9c_AZPN+?m07_W^4455%Yb*cl(T^J=U%uV@KRjEEzrN;tq%RY!uX}aA6>| zZu*%!98Fr~+X@SRjUURQIJFjQKd$+DskVAmb+d}Fb9&yy@peFc3+{&*u3hH+?1=rE zu6u7e4ku~n4|@@*irR88l9ELwv?Sx-A_dfv7lpgNyt4HDno=mHgwB5v=UCL-qjx}u zKpp~$RqlwAAw*Mlc2icMl>9Z!>E~A+ywNS6sx^abM<63|pRb#jBq3p|ndMs`jeR5? zT05qc_kDHliVX%TqMjz|IaDgX?NLJ@09vt6;}V$hU7G%DNaOZ-j-!QFRrvCyOA>$Q zNxIWRG2JO$>>}~<&7~Yd3dLk%Z?==ffwnfXhTQO2s>qF^Bb5>O1Ai?5{Jned|Y+E2j#Ja=n~jxgFSTc}aK&Aifk zM+ieSoDNWSdrjTj*{y6?_Ywj>X39S?M!6_vCvKd z!$0Od;{Ev-ceZ_E#JKS;Y%dfGmH1@OZ!NHzix~XHnwg3y=i>hX#{5FxOUpmFb%@qL zAC#>&{ogM+4s?w077b(!CQkl^Y0sA20sbG~!PK`44@N zT{QUUDn3t*UVzF$k^%+XE<)DF#G$x6Mqb}(EA0F>V5c&YsQ-X zG8%!FGAaOM^4O$-8A+w^Tqo}vnZZW}q6eVJH>e*VVQV3A3v_@zJbZ7PsxWSH)bB;C z4yObj&jcHan3V<3eOpWx2k?$2{I#{9xXhik@%|=TXw9lPBT42chY{33(P$zxi-(kl z0Ln^>hYD9q>ejdcp^hM^RAXa%CJyTvQVUi@1$G^_Iwua8exl&O7Bf-VzfP)?>Q11E zMa8YuPWIOgFmIdC1?PgC;9KF|;PaitU5}F16MrMwV@yx82Qg|5HUr|W%1{A#oefY(B^SSNA=zqleiJR9NFcMsmxCCZZJVtj1AZ7b&nZguu@274=_ z2DEUi`Os53bv`I#M}FVH%@q%;Sx6*n54Mg}N&V=hJj!o2(%CB0-U;4&*G3?oiB`lG z*Rt~4`4!L$%vwMH1#Q1CE=Exg52CbA-1~No5%#GL0-i&EL=jR?ylfh=?CdR>=}9J* zEwPsOiVPy5q_M3W=>2iy)RSmU1r1WV7i~LG)Ni>(%4xVURiBw+%(H~Bl6CmLRk|~% zv{$N>{zU59Ux(zBtYaA`mPf_*97pr3zrl!J_II;eFzAD-ReTh2qg3MD3G(#v2~bt5 zlxg9&1U9sFxbf5!2v;$AvNvdrAEYf)s;`jW%Zcv);UfIJ7JGfL`QL=L?E-+3Nc+Wg znB5*iH?@y;>dB=(gUf0H)jPLT>b6<&E(F;)0!;N#2R#e7>-uasEqp9-;!;zdcLRVj zq1kW|*MQ<*AO^%$9r?pR`0jlzlheqNU+X@JE@q{lCXMOD zHiVkBHhjEieuSG~U6cM)SvF((Hk#?F1z%?n(EBWdxl3Z0%6O0e*cs@A`D6$Nx$FMQB)5WFVW(g-ARs`l`xk( z+PrMF#KD?85^~&n)2ejc+PFjLHJ9)};yIv5!!y+>=Tnh?sz4cDwt<*nKEikhV^gNN zk6o!5tT<_N(jhdO#LV5OMk!~`DCiAK8a48h1?PLSL}Xec>KuqWxd!kT;Khj=&T&i z0$Tp-gPO<(IL^}+1WtvJwrYGGf$O%$O-5M1%*Sk2%CydqqZV!gDKOrV2Eau$$qLs* zM2>sNMI9~nP^Df=))wGS*wNZ8v4UKW`uQ(J;QVh0JpZ*Bs{gbJW{RTt*yPQmN70ZD(6}@PmE3xUljCH3UCj2c#O5Me^-U*UYZ-5}kw4`_$NaNm+nTXt zCAahBGN+Z3H44Vw0gKVUw;{?6F`v`IT~CL1=+N$I;b3RT0+CE zNFsIKB&)Veh#LfEB#!RZ;#vu(wPv5v6Ei#6FWHNqOBrE0sh1z$H>cqrLDou89Lw;& z*G(F$0dJTb+_CqEPZGDK|8i&F=w^!4T#^~lw- z9K+x2F-C6=)@@o|$_YHUB*>1IIcH#}9x6-f*gHn~fZYV$^rM#p4YRS}GpK6vs9rkT zG)b#faH#mb7)9rFirsqjLI>_s!giEY{Lryf&+A^W(qwD@sRw;b zuk?8EH6s%nfcQSxPj$R`h?a=p*5Vj>hs2bm`-SrJNXZujUa^nr`p2r5Ua(E)ke|Mo zdBcVP-oWi&5ID6M1>M#FG{E<}k{dPDZ^3$ua}u<;NEKRR_^%fG6~XRbKl)#=Tsvt* zJ=%o3Lnbj#)Gy@@M9UtVV`m1n%Y&bNg7=KArZ1Tn44L#dbJ+!CDf9_lK%h4l(a5Io zhc!=wN3L45No z`;?A6_!Ug87BZGtrptgG|HHCju+~y#0BS0LdGn>)9AxA@$g) z^dU-BQ>C;6P*P&Do}6cy(3*RMgmFmdR;8lA3HgY$2fL$hNQ>RHko&iW!;HaUAziQS z!~y~Xe^+btwu?V|m3RVeohP~18`$u5N$FSR89uE+o`0tX;8i#}g8v_p4gS-d^8XvU z$kY=$jC16=0#_oqt$(PKli<|lXEkkZ{O#i;hk!S$m*09SdStse7JPHv!PlmzNT>$^ zx@jU4hlC1N@2(fr2!6cx_K&OkAz^}Jp~y!d%=Lx>3lQt*Ql>Su0KPap%nAS>Ir2vp zN+<#D5tM8lycz42G6D}B6+=x5ADuC~vq1VS7-uiO2YZi{s$+7IAudNAl%BMinwA_# zM|Wddo2r&4ErP$+W-f129>a6NIm)Z44Yr~9y45X%%QmjWy2O@fPH_H}`i)w6y&!oy za8?wuWF}=9E1#$QYu2qdE zn(j?om$>%Fai-F9rm;L-<+H8(|9=s;i$hPf}re<~v$f5PEt}*^P}-N95Crh40x{ zIM%K{-8T}!3m#=}#`HQKTS%X0os6H`r@B5^)g-867*xnU%uR6_UmZJdA4XiIboq%@taw~opep# zquA_0bH3L~#g+2jyH?Ej%)boNlHh4zJ7r6wn=lpI9Qjqz>RuNne6%`&5kQZ^@cd=i zB~&+8@;5kD0_9an>Ntcm!aAETzcD{bRfU05A(B62u!l2+bou6HjV=-P9m_kKMX$Z* zOu8oGkaY!`haBfLQvkO%T%uQE9TdYw;DKBEsTWaCue8;tkxw3()=ehN=aRK{#6lDo zxrNeZgg(ubv2$<(Ez{Rj=~CFkfim5*Uu`?Zj;7q3&^zdmplNCfB25~Y@h^kgGyQm_ zj-a-U_5rYS$U8Ijz&qUT0-k0~(<#_NRB_P@m1M#rjZHtUvEVUqxdtu{vzo(?Ta4~` zOO7N(o2ZacAq4KWHs{{YZQ8@K)wa!npjoYrwZyg7gGW%r0%0onFmx)|vyu5L_FkUs6R(gjMgOMta-a|p-ObOQf*KoI&Lv|uA zpen|;>g+?DS&VRQ<`5g}gVyayY+_9w8+xr>PP%*Q4I3q);UfQ}Fzw;dS$~NPG1N=d z&R(k5i&zruYxk=E^1=@l6G^<$q}oiV&4gKA8gmbr`y32gHp*{7Hn|dl|@E)?pNxs)$OTslZHo{vC0Kh8}AZ=YHcEiM?{9v3{WgMmyz71@<1XSU(FBPCWNf& zUqC^dO}7CKxbs3?hhJaNv_=3p_vX=x4Bx59wW-9>?lo3WA2p}xfVNGdCaiIWiyrOQ zE$wgu_S&$^_SzHZJ`Mn&lug>;G8Z(1Y8p$#;_5_7Wzmm3ienQytyv|l6;V)19Hp3e zBjoS-kcI3Z-8bq7ZzD6Sf2v~0uYO`~V*~Pcc zJ-$staY5-phOR?v#NC{opa9JvQB(x_kbH^_9G@nzuJUj4YNL&tO#az2)wJ2c8Z?Q5@kq4-Wxvgh83+o``NH}p{wZfa+Da{ZnySxXq|7Rh3z ztOiXcMLwAb-qTo%TwArsesOe)=turM|6ClKoEV5qLWV2`%$L7U__=dzM97^$EVHX6 ziG8Lr`E;gU1RtuGym5QCdKxJCD<^&b_duvi?Ya~1kqJH22DhHU)w_&u%amw%mm;O! z-#4we-WKR|ql~q)j5d%+B{Kl4e=8vNxK?{3cRHyxj5SOtd{}XMI zG&^AD9l4+}6(N0NvsOg;-tkNcJV)Qx=OIUsA|5$I<7Gtgm?z3H8syG%i6v@RRl|!*HO+-{|9Qp-%0=f)WP4-{Qn%KE-`}$ZE1n8bp`$d z{&^+}uVMw6@mc?ZFi-95npJK@6f(o=f427C-;huzJNS4%tH#W|(UIN=?eZ%)cqjsH z;cfR__5S#(;jl-^75l$JR-yNMB-ASt6vD?3RQa3{)ifRmmMb4A5vuV49~m_L3P97X z+)Q`#%sK~LY>EP*&8ZYmzi!BvX|55m-}iY7C@CEob^tCyUahWm8v(Jbkxc%=UKmtxF?_>;*}5d60c z_^1rlnUDazVy@vf-TUuaD8xn*>fV&m@ddxg*j5{K>_$4B?R7o+Rh)^GyT6osu$y~0 zmP`AD+S3qRfT2JSv}MYP4&E7$=(iWni&D^ir%F%!E2z*lgmsY{^0W_5oy16|F7syM z3;NYhafJs?tn@&Ks%sU^FP(X(&C$kz{*FYOL^iZrkZx%L zKQNlaMDQZI-p{$r1Fcsrlos~9q#AGM7LSf>cSxh_Ai#BLjI97 zOc78=c?IFWwZJR?wB&s=)2>A$izI$08AfhFM8bbVBFnuZY4m%{1&OqM^RDv4ok7E< zSL;&|zjL47$U0)SuApeFN#+L^lYoae1LDM+FKfIW5m}`BOjg6vT;5lDLBB;yIbS)7 zjBgFpey}$ip4uaae$d#7pm`&L;@i)x?d7Ib2uA*u66Q=V70z!T5?gUm( zyUgV};R_cZFOkL)f-aU8!&L^zT4#cKk?QV*{`i`qi|a1qI7ODrq@C+6?E|aR_nyjdL|@c2J6vi!iI(=eEA3Ni z<))*uu5~SLb=8Blv4!eFN7h;aaqZN_FLQ}ndzn*J5x)lwtBcxysYGsy{E31;l+T?3 z9$v9@p-SfQMgE+A1h346N}xtY$LkI{Gg)!}lEe32;y+moc?mz@Daro1u|V8z6Wp3a zc34IVpCxS#8btXbtCw<~h zu%RP0&A7F^;_3r*54NYmy%$wW``UMwwGKb7n(j+R9@L9myG2J)6Y{S(li$m1+_Z|M ze#o=?v}AcG_SN>M+?>tjqj5@P-ea%-$Th)2>&Ogq?3Z)1JzOeiPl@#Yc+HZ&+y(7_ z6C#3Zbqv)vt|1#Z71wTOogK?S9dsIT+Si)3yG;ABgVm~{&1ii*S$Z_O7cKau1?uERp-a=buR`R(9;E-%voCkF4?2R{r6sk(gYFBq zQ*sZ74tMFhMsX*>4~^BJq%U(14*p$WPPBSnQ$@15zV_G>V*kD5W?g5L3Q&%kmv{`>Xma!|OG&x(|p_0a;!?tj^$ruXY#Ny%C7gnmJ8{-KFg9$;`Kf z{~S4_4@?+RKKkGpz53LyN(oI!s9BzzL}Z1p7cBfPlSwfO9Z0u#I&tr7LZ)?n_RZab zNidf@wrLvtOe`zV{VfY+V4J_cylZXBi1w>*UHQmo@fzqSYwZrT)392HHCjSfBih?! zGDkzJUq5~xE9p>$;`XQtS_}xHt9H$o!Qf*5h5P?YsT}ll&7*M(ED!zPN8=-5-RtlPXx1JrPrBkzu5cFwiuX?qA8E! zLuk4iPFf{D0^!E_&0Mv43Cq=w0SLnNiR#C^XeGHPAwmo=$1abzHPQejnLh^bvW+cs zr-&M6IP$UyktS764~0wmLk>6{9cxV8tg@6&pqut;+7)^{-b>U>~N!n5_t^`plS@noS6%Z?Z}Q zUn#B+u__52bxyC&$l_@qA`s)*(2^k>R#le;H=AL<+t|^?w(UipKu=f)2Uvs{f zHpSD(kSjT#<^O04gFq0NU89@-jk-4vhqC?uhYcZ0DxnD7DHT~t2r(nc&R~$etQq^h zn{f*_LdueTB#f~Y*|#=J36(*XF=a`HA)&_2h!IxuwWQ_IRI$FtGE$!?%1hR zeKdW9Yo6f5`!={VWhzxJ=;oPmD^tFh;{U4|gyFUIhMnv!AZS@=wD6fY6?(pfH5VJ! zAf&>Nd*TLHo=R5NaMo)$ZDxBIChV%cTrDBu`=pevP>#?jx6pv;bNxOMyf)gKRaaD@ zTcfyersM!hu|c2kNrfvUvt5F^=8caw-HQHJ(*dx6YpcvAli}2*7p6lQ=j)2EyH$=$ zZk6)|dYvJ!bZa7@$7M@?trW0dof3F zl~sctrtA~CzvBLuWb~Nd^jy33!$N#nIak6Vh@8X5Y=J=XZl>lh>a-RSkO;C2t;Sp8 z#D=kGT*K8B7G!C~2Sa7_ovYo7M^Lw3uu;#d{kj=AX!R!^wtSeVoIF^k2P0#-nrki_ zu;b09sJA(Fl(P5;Nu@8g zf3E%FuTAbjJ4mD{f8=;xFVuQY0Ny5CYNjy@Pc;ZS>0;&8qz~(RqG-x59Q`{kJP+o! zZP2G}@3D2Lk zh}$>yMe`mSMV*j;#*NE?7cne&=BAmEhGwtgQQyCFV6*(r(^`MX^c`awSpR(C1n*;4!oEF&7IYclt8eb@@=&a5z7bQQ9N5tsr%5bNDSsFg zbjgLm{-#Rke^SR7h>`O%4UW9)Tl!9k7MXS+wMtYU1s<2%j+QN!c2g9eO? z18vk{ly1s$?^z+ZM6y+zfIufvzg2kx%d;(}xO4r$CD4k4S%Jc53z=mXm6&mI#D{IB zhU+iAgy57kfGmHw5Y%kYPzU{L3&w_WN#0Fu?8}P|ljj)166dZLwdXl_AG`k6nK*a8 z{AVj(Q0+RaGcW-IGLxtiM+)>&(F+ylBh&BXMf3c>X+h0=D?G9c-P>Zmr@>bp!Xjg&@}GfO|v-s z$k3~G17EI@q>UYnjQCeJ^YY-2fl>uxvxpXUK*jKXu0Qdyz_66xFyKa#7~Zwx`fn!V zKEx^dlQ)~Cd4^81P|y)zHZuBUb$NK!#58UPq*-N5e(P*z>>8u!O&7R`++nmPA`tAz z#{A+u!j?3YFo%wJ(C6=Q_+5`k+8c~a|CbL5TZ-}hTI#9EimSLzWlV{)zi;m=J;4%i zTea<*IKt;t0pnixe&1_SBNW3XxK$9g)I_9tWZih{9-{+(r2gkegs*&&LU7bv1Ndz81u}h3nNBq+ z*!h6k_M($JCO|CB#TZoo{fKV7Uk9~QnpWaIMgQ3`vAqaB`TPKFlO7qJ2Y=%Mx+jn6 zY2u&P1yE_h4t_1P&qZ}N%Mi5#?!E4!>gN}|&4GQX_t&ox@F3_73-U{;mv0p~uKed~ z5Q1wl+33*dZ3G+M5De5TwO_;X)be!*MRDjp^kZqvz{J7Sm;w+V|EG7kvYc=oY?MKT z(H@a}7vxwq58D5?=&Gj$kk*`!z@sd#|G5u{)je5$56=*OPV9i8umlK!tN#AeawDL? zm;XIP9|SeXV_^0@g!eASLL-l;v8h zaAVM$`nGZ2aoc|`ysVcl5o(nYW)^?Ipt(1^dJCxwy)DR1NvQYetq9%wp(lA1zIG4I zIYX%A!s_9Em)g~2^&OCc0U$2Nti_I zaEZ(4ig95z>j`iLPJr-;dUvD2|3#L7KK%WDVh6Fw9wLv0O946T#E)>W_lPo@0{Ti- zlLw4T@b7bhwWLXa29<5C^mz7uC|m55Un!S->d`SxE5^zNTL$~>jz@t;+5`*Z zT}waTh844cdGv@C|HOkx$yBn*l8Ix-vr0%|@~ale10_N)Wx&z}dfY1d{rSk-C3<}D z0rxP;W`oWFonlug4uJ!VwfeRf-WxAm$NdHkZXXAE^_gEELB;~6Qb&IDLx~5<5&@T} zTEd`lapv)R%}V5bv4xBm2hi*U8+deFedPh{{65S2DP|-*941#(KJ4CdmApUp&VOIU ztALUB-%ES7CJv#L*;aizK*<0fJ_Km5yan$c#G*E*3-+jnsMYBX9)-^Dsmg6)v_irMQc6W~f|^D|EyKnnz$4LoTm$dVI{R28)Dy0yuD0rm5FlAkhl zH6*-Vgh<~$2<8Hz3cXWic#|2qfuhF>i^vSdvLcQkBbQx71fjbsS$?Oj?y+lEjxP)k zG2@iENl0&FCcV?gwEJGqT#&OX?SEYT#K=x&fbF8)2>;B>+1`cizx67^g$w4p5%#@C=+wQEEZq1;YCs)&=cD__b5kn+7y44S=k=uh26&YHc<=C4NL`^{nUe~6IeM&l^mwLA z@Q-t2WtjL@#-bL7Y1QeAWWsvzeeia=||_#D(!Q zK=ti6E@e@~i~~A`eB~ENf?#KLrU-~e>}@YbP|rcBThj%!8|}(dZM!xLLBLUSz;c+r zwKYA0m#6<+F;uUs0RI{2It0Hkn#ZV8Yl8nmfArV`XhRk|Mc{GI_73z%@(KDW8g5JF z$j!=;8Ij|ecIl9zRc0Lh*$GYt2$CTPz^Ww&iJw0VvV-`{Ne z<|1}v_M8Z6&vbiDWY&&%DzA3k|DY$Zh`@@a(-x;ua0o?&{%yL0zVg$)Rs?=vXxrFT z(L)<`RzsF;^jb(HN0`KwYy_Y1p1L38mw^8Ei?-)Tg@LfaXgFl+?afe5OpOHM_D~TfHxgXfzsT)w`)bpKSp2N zPrdXP3y%19d8aG@^tK3nkupG|c8iR}@#c`xmS^&rdtMm(jLjK+En%Q=hOoYh0TK>w zdSRy)25URG4_{z&lNne1WRb`vp)d#@N3P~$dW;Adb20uJYpo72yT7Qm)GJb$(4sAU zSlbndsk7-3ce>B5=N5;sb*`vtrk69wLDfuV1&6n%%D�TmunJJQf?NKo5e)jx1$ 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 a#K*?l$KM+epPZzmv;ytbtGB?KwEqp?jm+=> literal 0 HcmV?d00001 diff --git a/docs/guide/img/palette.png b/docs/guide/img/palette.png new file mode 100644 index 0000000000000000000000000000000000000000..99d1c2c56c6a125e27a4c778041be1d8943b1ec3 GIT binary patch literal 92182 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 literal 0 HcmV?d00001 diff --git a/docs/guide/img/quick.png b/docs/guide/img/quick.png new file mode 100644 index 0000000000000000000000000000000000000000..650e338309b3105e76e9f1416c88ba6054374452 GIT binary patch literal 72439 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/tHs%r);i$@|6Me{vKGS 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 literal 0 HcmV?d00001 diff --git a/docs/guide/img/raw.png b/docs/guide/img/raw.png new file mode 100644 index 0000000000000000000000000000000000000000..72c925ba10bc2a682e6eba42cf6cb37aaf51c3ac GIT binary patch literal 141875 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 z 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

4xyu%8y5hK0SYb5)WB!p6ga_w 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

Hrzf?Al(AVtuY%b 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 Uge8VJ#`yg;Ed$Mp%eNl>8%aOUD*ylh literal 0 HcmV?d00001 diff --git a/docs/guide/img/settings-security.png b/docs/guide/img/settings-security.png new file mode 100644 index 0000000000000000000000000000000000000000..fac5f6444b5e1f3829b032699638ee4b0ae4ba75 GIT binary patch literal 92395 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 z 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�}8kRr=HzuH-CXIcf_+Jp{ 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 KM)_wi-TFUk&g-uL literal 0 HcmV?d00001 diff --git a/docs/guide/img/settings-about.png b/docs/guide/img/settings-about.png new file mode 100644 index 0000000000000000000000000000000000000000..21062cc6a8d2131eedeb2ab9d230f15ba65dc95f GIT binary patch literal 101573 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 literal 0 HcmV?d00001 diff --git a/docs/guide/img/settings-general.png b/docs/guide/img/settings-general.png new file mode 100644 index 0000000000000000000000000000000000000000..e677664ffda5893537cbe76a98039208b0d1eecd GIT binary patch literal 98988 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 literal 0 HcmV?d00001 diff --git a/docs/guide/img/settings-help.png b/docs/guide/img/settings-help.png new file mode 100644 index 0000000000000000000000000000000000000000..828bb45fc2a1eca66cd577d25f91912ff33563b4 GIT binary patch literal 156371 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 I$=^Z$576vV`~Uy| literal 0 HcmV?d00001 diff --git a/docs/guide/img/settings-knownhosts.png b/docs/guide/img/settings-knownhosts.png new file mode 100644 index 0000000000000000000000000000000000000000..ced393db6f34ab2e334750ab2ee3f7622b473955 GIT binary patch literal 104366 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�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ÐPX6Vsxj_ok8Nh?3gVIf|!hO(~AJO0BpKuE!S z#&E-NzlXJHitJj?$rUDm$*D$xrV@7#ihw~YhU}}BclEvA5!b7T9%UE2nlIYlsQ!rA zOK(aKJ^meq@4a3U(@dP&wp~IAbXNdwXlwm@YxuorD1bMZfNYM{$-QixgbzchU!}&| z*_S_&daz>GQTxZt5KWw;c|r<;TF!>L6j9Vw^|YYQ=oGSMOcJu}oPRCc7*?2yTj~r8 z`kI5=0lhV5CF(BSfH>)WU`W8ndA%5LI*IGozy#BA&->H!vV^P-`+4}!=9A!0`AB9B zQNws#Yo|U&g&Vo|Ee_%86{uCqje?d>)G3kK$-w$O&Cl;ZyuI`{!8v6Y2_r+Na6Q&>NoL;Yp6Z1&V~0 zGIU@r=JsLL$Qi?Kg!_pyfTTIk^=hjGo$NNXCaXrs0b8F3R&J1q1jI8My3LWQPU4Y) z`E>Zn0}4TJL%{~Tqi$_A0bc;Niufk-F+SIs3myaRV@%_{ueht_G1+qGL1;zN&P3K% zl6~e%34JLpqgYxu#Rc z`hnxU91=1w6Mtfy!MiW3)S1<#Gxyu7eJ>2y|J(-zj-!3l`MzGeD7(@{LltHS7*W`p zOm0x#-jmrV=bJ3kpP{HLO-dKL&uw*1VCQ}Se@R&XeW?BOAsTN>!1CAcpaY%ilA1w5 zs5=85hE%f>EKFH;uMrZ}d0%&8(=AWB#L`u(`+ixG3>|sAP!)>(9o2zj zqU&=^ z>3LQNcit5i<~GigcFu6H0_T<}7hg}N82)iCC}LS_KN#-p8SB9&N*u8CMfI= zTXZ`(y6gU#qc=;sj`Yhs8QS^x$autwCjqE<+Ov>Cg zdtg0NGOzeVdc12eZTPI^PhP`W{d5G<5lg_oU%gD`Os3-fS$>}X98@Ihx{F+Q^XUb+ zsUyAWaNU$sR`(j0jMH%HgXF^n6oAz5t7%Fa+B4APy>0b`$C3Ugjq}EU=xc&m?D^?;LFzmMqoJ zzT@xg`SI+ugB+pAaE`Tyjj>h*BT>HFL4o_n3Z(^#^{#OfMQxt`%=m_?SgT9{;(evF zJURcfwfO|}g1>L#f80hz==v0EGYm%Q_m!d1Fs)#ayKsZIL?8%BJ$VdtTDXyiF9|_P zE(k4@-Mb1sDjU`P8y&4EP6MNS{Dtw1H9ZcS8>fz0@)kowr+LgH`pTy00NhR_i%e8j z`+!(}Xn-wCOOWjH-MY`(U-ah_eFU>+NuT%|G-L+;`B8%>_VO|H$Q(F7`bfpYopEh- z7MUk=FE0$Y7+IO{W!P=>`|B&s-)btE`r=mf`)lQW>LXqA`x^_6jm0dR4v$QXCE0gA zEp<|-Ij@Yjvc@$OO(|;xnVFrIa~DWuh+PW@>ZYsja}avbb`G-MsTqP>pY>)%aivar zyX78mQT4i$ilBxweQf$T5X{p#wJ#>3ngmn zd^0){nkp`yq7!2DZ~1bYxur@w|8IH>-QQPBtZXOY_b}{&^q)CAP?eb-r)6yL7qD9-9|B55|Py*oQQ+vw~y_jYx7z(H|cGb=mc<*H$H`6fL&(Bt} z*DG@+_JmolorZYO@G1TWkdcr`o6ZzBm)lo$o&F5V zGBLevpc(N=jTwF{J)Mu{RboW-u`X?u(s8?)HH+BhXV?~J{OJ1XQxe%d@g_Kj%(1T; zvG8m?W8+w%hV9Ab_W#G&e}=>Lz44=PiV{M!=s|?&B_W7z)aZlJTS5>$%IGE0yXet} zC}XrBdW+r(X7m!B(R=6F^8G#kb6w}07w3h`W!rnNz1LcMmHYmb>w;d0<_ za{5fIh39rV9-ec~>|^CT5`0f^ZWrQarzWcD=fR}8#EAU3iwWWH-P+d*3vzl9p}eJH zPJ|@7&Q(J^>v6R+dx4qYL#C%+%TKDrZ}o8}Km`*j*}qcEYeucvo8LVY8vk_VDh*Fuo%PT@+?}TKrBq$sp%J8`WP*+Ox5qzf(E*sk~HE zM`$-PoESt$+Ptqn(AgY*>Zg^H`+@s7y=tfgR|(9luhm|x0?a3m$4DV9g&CKLesYRO z6cFwg^1UzNhK@Qkk3pjJLF=2G#6(uHUr|;ahXdMb4((z2alW!4A&?m?PA>az{tt7p zIn9)cJ*g!d>4g{NwLg6$@_4Zm(T`Cn<1*PM4;gY$dVvY$O5^4hdwm}?tNZricb=5W zgjABg7A!o|M@y5Jc`;6a+zvon#4<;{J66}7j@mmk2q&-wBnS^4HusV512n&CPO&Mh z$>_II$U3QD8R?8TCr6Eqz-BcK$-J$5A#c@u@H~Qq98`X#=26K`{yFWC(ojBRzEl{H zHSs9fe6c5tKQZx6e5ORf0vH1fIhe`%DwsqD$Hu6S(ODB8HHrnv^#qA`^5jE{w9IrW zvVyz%c*Q3SWU>4DkBS_6xKLBW!;pz}6sd1{GatcIG{HX_!y5n?UD=YOUZ}a5*uRP$ zordXGR}+34TvRpt!M+NodS5~gtptSXHw`6K73Cx#p z@7%_stlV__)U3h_Kl?M*4NAr>=^W{ua$p(GE2vq!Lp-9c5o|b8*RNQCNc%~myAv8EeJ>E!o9v{xrGiyL;NE<+|z3xS;$%0WSUkYXWyLvr9%Ba!SRmPUo;hpWj;yq))U@}Ms)am zpb01WD<-vX$uet=-n!RU$93v$?99K*n*U?e#Qj{`5cOuhs)R{1H$1}}S1o)cH4rg0 zDf>%#!A8(9X);+|J^?mpK5aEV)ZVsj_^fExLbemz6ZNTF+4}lJ6B{Pc4x8F)p|L$ z6^doGV+93|_q-67(&Oxc3Ed)DsGiDz`Vc4M_;ZWaTbyrpLrbFM*e>)F5_DqJ15m5#-^)r#H~8?4@BrFx)5 zfTKIh#o4gb&Nvhm6*64zczM8?tIYoxwGp`{Ds*t#s9oz*d2-+k63*7H1~Q(^b5Cpl zaq`V$sYM*V$8==!>ggKi9d<-Z>wiBsiTtuqldCrS%?%g0s*cXL_GIySu+Lbq6WI&1 zi%B2#SDTn-Ug|!Iql|!+-yc1etRQ^>mm=F3JS|D*UAEDGA~~z}0}qhn>vGxq-n1~@ zc$GjOMCmrzQ~WLo@s|2Ch9-08hujH5ap8E_fuVS|o*uKIKPPYGTPYltu5ZdME1jjbpb|Qnf~1CcfT1(u_0v zP51KMD=!Ib_8+-pC{jSq%wTa({C;A^K5V`U!yft z6S%@`L(Kf*>Yy*=R+9radM={4lNWt<#59;&ry$9!QW$|Bmuu>szsv2!M>2m3S?T2w zFwQGF1g|6u{N)Ztz;ztLn*-T2#NBd^$VrWj#$2Z=1YRxKE6Q!iK2->NK%`=l}_tWFv-!CvJ zn2qO|s3=tG*gdM5$7au5X&0}ZshMb|NgwzQBddla7c|8&=33rusDPh4X|o?M;CV-- z4>rY|N%;iH3%GEZuix4eFhgNgqsLesRe|$tphkysiMqMDVb8AI)3Yn)7o?5{5Q3Vl z{*U)cT$zLl&I=HaK%7Jj6t@}$%WK&#F@I>=X21F};<4<~s4k#w2chqn$v=H$iLR7_ zP;?VPZog~VV7o%3TkcGz1JtaNU5bE;;y zjs3(Xe8=`G)j2Qq)*NftGGnEP3C`O24EMa`f&?_lntzQQ1TIiNsWp9^UQcA!RPRzW zL!@>n&Hq zT0mFrW@~DpW6-tHo}7c+y=a)$M)Eu#`j_s0CpIvO!&gG18|13e1d2-7{L9NTMAGv| z2x+F-yey}z1BLX@Od{S{jINVCnI0vokT!N~U_xy75fLpOmb$Dv&~R+_(CtfJzZ@Pn zCc0H&dI;>o|M``C>!d(hspL6r8Pqm)%H6OftC0J$PINR=Q)uhO`yBkOJF`Hx&dgK& zfc6Cjsk`!gLV!S|)NcTB_=Oz5ipx>lGW@D!;Z32_#sgfdd2k^sD3V1EaGihUr*PUH z7rz6f6XV z_e!YIOKhlvdOPLRF?nF5OtMEzjkKFbs#m;q0op1Nb_aCbOQ#nblM*_$R!FL$;Un!MTr^;7=b0iB#)2nq@Dy~s)dP4B&OWCx z-gOM`!1k^^e9f}&<4_7)y>eB}rIYtox)_e`W^2XnD@qd|&S-Xy3421s}|I@A;FP1&SRFwhSmTW&w^7;8~7+}OXcSdrvd#W^7Vn`5Bl&hi-0Ia0b#TyYH}kZ9gw2Uz%( zlh5;l#!ZLP%0oCFv<~pBN|WN_KvIK_BS26zjED2tMJH@9q`m|df5YW;BO>C5X6;6Y z!v;my7*#`7nhq`7DIobz*n~Ck9c79=lD764Z>o0jUVaJ0(~SA?l}&lKq``R|ZakYz zZS>r@KV6ROGj37C@5!{lNx3Ow^RN|?$j0yv3hCIEmM7(Jb=;X*pYV=sBs>dZqr}S- z1E8NkD)3*mWx=cfn6gNs;RiuagXfPKo_Hsr@~G-lvm zteiTM@xme`E-ywvGtUdG_PGlIo2j)rgM~y-J>!fviSP=iefEVf!6#RKz22W#Frn<_ zBjedzK0_IhTDi$K#zx(%b27_GL2!0-Mqsw0re@U_d_ALV0yEwIqMp-kPv(Lk@6RGR zn*0Wqt~{8kYy5(VlFg-($P!`s*K95)@!DWf-K)X$94-XF(i*Djkig2X`ErRjAlfl0 z;z#wC`6#}&cs#wscQMTz%FMcNQ7_}NQDPkX@J7GODX16A_O=zST^h0CP0sQ0Q}kcf z=^^F%^{e8YiDZ&I4EVyY%Nwox?BSlO z1pF3sHflPj34n2S?%)mL`?tiBlHOW_#y4a>b)uAKFW`3&bi6yLETep^<9wm6!A4-j zHvIWW6Ru@{@NtG+gM# z(;Xc9`m9#3yUPyi2L*WfDmHa*irD=MollPxpk5UsuV!D)s^!i-)!b+#LPlL-gt0JP zla_4;P^+$YIT_efweAVo*huY+dGq4B0?V|UVp6!j$ER`7N@kd(F1eJ(MfMYP)Y;17 zEWa(xKv}p^8Nu2rPjQq z6#BSXF*GIJre&yY&gV7n&an!i0k3EB?isrtKo37!K7V zzMTYiiADK0TD`F^tVOsx*Ki_^RZB|_YG?Mx5=F5pf4&39Fk+6}sTKfmO1m z?Vi?0WV{|%h zDh)gxpOYsFYIGoWRn#w~ZND_1<{wyuJ(8L4MZgswj!M;1l{PLDA#HQ*vkNb$#M06c zPT^cWWC&70QlCRa@}cfbcvIZd+jJ8FN!P*+##_&7!xg?>0|wqSUdVn101TU2Yj0}t z94E6_^MR0;s7AVBM;SKXPGq>AM^m3Pc@BYzLC|2lLW3+NLH##xM`eb=$4ouynYr&B z)T83dzH-ngq$YFe6j80(cxJC~)>`RD;UpMd$rwHv;doR~*hjqX{|Mw+yi7qgspN6EBm~rv)o-z77&~(QJ6RI34T5 zOU-=P7=?GS9a8$~WW~FKjON4xIzED{51w@0jH>Oh(&M~doFGKX32lKXc1S>LIYy5v zQ|dozrgE*2Lsse(BN7if+PEzF4jkW@Eu@^QY0pvh*bpt56h2Go@NFk=SY5C&PA-_k z=S#|a2AizRW&ZYLdwX%-!SCxx8rtyr(|8uxD2=}}0VLkL zNh*5hP3Lc%rrNa^9aEzn;@WcDJIbH89o{;a=qqsi>T~jFec@3h9QD&3vofWbzxzCS zg@^59EJkq)yRN&d7m$L0o!?@sjpT&jz_H zIh$>53=ZlZi^m;=j^4X>a(Rgw|i8ce*s(v1f*lLIi;zkiz zMXww2Rc|@$`L|)E8_cck?>$Lm+pyWRc{4=?xYzjjzEAO|>{|!)L$-i{9HZzZ{9X6KyqUiopqksNS#{R)h-ogjucG~(3P!PvmLG7f-KX>etdv32n{#x>7 zhtD^WC;}A(P>la8WZ2&He|qcmOU`@RQkZ-I@7wQxl(xqu!v7c~rXUPehJ&0cKze=V zA72t6690pm*u+P1sl+b+Uqs6PNv;EI<^Y~iiaWc5e@qoXaveye-V+33AqWb9v-02K z%8mxP`^R#CQtF>#LvSDmuO>bevUX5%7l`>sjK)RW0UN#q%c}&c(Zk;}{2MVIqg?@k z$bZNY#$h`^G>L(uq6b zDdigTP8O|v+(YS_ZN)18bR}p;9F%FOXJ2?fUv@DhsuL9mDf4_ora?wOwVH$XmIGK` zgxsZJ?+^u_jjKfIHC#DR71ICt6VPjdX6cvViGHxedg=H5=Wj@ny?!0ss%x(F7SAV( zRk8p&)!yH|lO<0{gvxxgQ=taXx?z_TpmcRUZ=aDSQNDmZu5cLD@`2qE-oF~%UrbOR z+S9Nw`iytSsd|?QsBxF)!R%ad%FZf0g1>zLJoK|8Q{DhoU1q)4pgS>|KDoRb?QZNC%6$T_9|p} zlAI)-|MP-Qf%YkyCpIos-gDz|`Xou-`54<(}%`B0JhlKmuQe-$u@Pm?Qt8jQVj&t?UU7@X#$ z^y=Z7pGQX;?3ZJK8a3|7`T(y=Sc|IYRW4@TS=&F80IL5lCWA6LdJOytIXo%Pfn=?ybcmU{d&r=^E)K*441Hh#mgzC$2n*M2fN$$^&b&K-` z&aPsgYPLlFoJy&lkiqWCY3p7Vn+5!u^HqRssOj=_VkbXwpfi+_sU5n$W-j0{(ARu$|l!z+L+9Py3E zm&azON@psbzUl#}k%24%g#QRI-?)28t@o3Aqc8un9#O!$b84U|!mJb#4W`#E{Eccvll%V16WdEo+*WFdq|20?%EQTf6m7LLZeHhG2M1t#Luc0z%Eo*AAf;G z;Y)Ho!CumP=!Z7DEZ(*e_?P$Zidel7czcCRYAprA8#j!s|74B+Z`OKAQ6SrKt4m1+ zTbuiQ-T3ofgA8y@spWMifcv<^brS=}`i(bExGe$3G`{xeq(}ZA^ddM>b2CZtUz{%- zFCKRut{K(P!^_sBh1qOLV7o@^^nxe%{q$D>yzw9SoAm z0toj&q3lRiQ4M1p4q{F0gmLEWNhr8pbMJkqXhebjRK?n*jPK z@|zl*h8c9*4(PFVpF5Qy!JKXRwjdhUjv4(-*jP{yfUX(6;kaQ)zV>CsfGQvx3p3vO z0=OeBx@HD=<&?nu6_4l50K#n{^|5vYCmaC%2c*4hQi;;<9K+%sy$p!pos%weP>Bd{ z3roq!aCzry;p>l$4{&AYtk97Rqx>1T>0_yvgM@O20GgvZt-1XM_6yJxWccH+4`BOZ-OaS>>8$v}&-eI%bl&t_PI#O&vdiJ;oI}3Mq1Bryp^}i_ z8=tMM=ieyzrSy9fL43pR5W+10%r1=ECi)DADdE`*g zR}4JL1ZyxfQ>^h9v*^ZB(9in3D-xDBE5E`PY2+{2dr7C%u?ODkU*$Ib zF&7E8=4MKC6xOf(kZQi7Za0}9+*M~0T}=!y@&$FYi=18DY6P-v_C?3?hW}fG)yj|^ zu8z-)8hgfdO#Sv=UYb8T+Yk_Ma&wRaH=m#c=5L45Rwr3(?Pgbh89!9}vg~Z74 z4@djXZ=w!9vpt(tyF9sy!iQ>t(iOvnOh_Q|_=C!y*|>4Z`zqPjOe+07wU)A;pBDBo zJ>YR!F7L@h6Pl3J2|>(!p^}T;u4YNnkI?wG5g}U8amyz@Ol$#<+=Uno1vk^Sg6yMq zooZk^efZD9i~GV*GcmC^R>3ojq8mz+Rpwm`R@z>AKSqDJT!NL?3N$10c&)zT?^nMI zEG!eJ=9A3X)~W{9-Mp?TIEF>-4?<`ygk6+0OhNM2z1dM2h4-fKpVCGj1r=rK^mBhO z#TM4veqVWYwEp=G#mT@hicDavB~g&fZK?MTB2J$SyBSvwrwD;v*d}HAcdb&i|ZnjbS3XaXoNz(hvQ0l;TSWr}9V57*Ee(!$?{R5*GRvA{|Ff7!7gQacbn zB;{Cf%U=(DGq`11N8c$?m{uzpb?Q&TmUz5SKv?-DV)k3&K|LjX{b8?W62;c+NKZj^ zy2enM-0K{1l@nmrvy&40kzebnm26DNgU^8?;!mH~T#L=vP}gz7th*Rg2CUQWixsc+ z9eHmJ7Z9aWjYZ=*wiqcg!jC@p%Bu<`lxa^cNsLd99VV6>-sjS;8ziXC-y}KF$+POB z<)dWIQ6GE6kAjhPdNwjUI-r!+O-K=@`iaw%=FWrrS)Y;-v&AzHOTMni`qLR|*dxt^ zuOM`RpBCE0C(PsMX4XGJ-a~VyEVNC>fK8z;ptap}2fzj#09*jy(>7{E>TU6gyE*_p zwfA+ePn5+#kxK`{Vpkrg$35K7VQ)G>2~q|3ymJ9+Eb&!}U&uBmG4e?*^{CcMkR~?Q0S*#BOpTlPD^Sd!>c?O zX~URTFpa}&g>7S#L#K5xGI=$CK|Z}S$lvFgW;-Y}o?F3(N^y=eYofKUgKe7p3}k9c z!52MwpKwIr!X4+2I`|=b7)G--tfv1|sIkr@6FM-H60EfeY_b3yc6r8TfwL`Fb6sZT<149LW=w zLwqRwd_6NKrw_jLB80iR(Rja}Z@ydF@}!v$s*jhaI`@!kjej%N5iodj(Y{_tBN%p> zOHR#i7fXSCkQOvkGPp1-8aWSQ2;V3s+ad;f?s)wn!5$GFqH9cIos*cu6u@%L?a&VHT20i!~BMbeYo=cCTI}$7k*tP-XwMXCxfi7uL0(j1>Wc9D?d;me>f1p zrS*Yk!^nbK6X*kPC=-7rKyaPhZ)!HN{t9G^y zs>cmO&I$l)(MG4D|7ndlT;;}|+ZaiNjBXw%oG!`ipkso&N5i5%%BJHwYn&8;*(?+eFR172YfoZ! z9^IPQ6+&m1O1?LSK{La5pPZ_{#zf_@mw$M=L>jA{n5FXbwN}3WwkW}GwGE=RL$;5N z)~97j%b(tkRE5jP)CI%X(XN4rmS zTZ`JW=Tnm?@FOenQqlwW-Siwl*sA=6Pu2~0ASoLA6uUm$_dL6VqRZulmC{O~=;Sg}T9=$g;&Me74z!U+*W0v;bc&kO zVZuum2Dq8Ov+8Pf{TwLnsKaULr#C7#%rK8V6K`3)gHp7?N8B#p=dNO*puvY%^E^U1 zqvS6bRZdl24|RSm&w8qC^6R8>+U`4el*Cn+D~+L(zTiYSu{9wY zH4Z2`;$qO(r?*WLZ;oh0%kFR;YseLs#`G;-qH2+aV2=_Wxc>6rqxVBTm%pe-G=trH zeD?lSrm>l{zl5i&nN_jNDle1cAckM_X?3cS?6N)X*zW%DGd~A4(HL*|6#0yLMaVmx z06F#y^wR&ub&fXG6~R0A>WOd0;QC0D{ddwwjcWlaH2rKAH12q$rp2#2%!u+DP}>?s zbt9MwSkYXG@DB-3sOx;5zQ%g@cDmSycrL>AbBd>qb;Rjg%3-?(pa z%8Az|)+DiBKT^bUEI=c(xjn2m2sTXo%dKTIeGUZvU))@y#&zqEsfxb-jKmNEwpb*2 zZZJA%Ymf1&4;5dQNlj~1(FMZ=H74Gdu@bE>XT5ppArZpvP{kO208Zzd&^(L9`ST93 z78ub~6+lCjm7&cOnX;qA@R2STHUh1wHt>+`-07Qu5>->_R|}MKJt$~T|4Ag3MXhUi3n(*Y7G$a^LXLWwB4#c>h)eBLt{UOS=BU~ z*O~kiht7-;(f&|Y|3FgDA=P8e9e1oTD;^8Oj@6E6X3`r1>PUHC&h0;^Qwip~Q7|4; z#B|nSfgJdBT|ohS&YG@RPwo${4~QP5=(C;w?D{DbpD!(wCJIKi6^#xn{$}AzeZ?I^ z#Uu=Ah8>uStYtpf6{N%w*A6wxzbf77ClOSY&{%6zmQ~^ZwzDDBOzbYhYsf99ZPd$t z=*&kl_psRjoPIxD3-%>mYiA%;9qZC1q2LTtj`-PD&n(?mu3=>6ZDE2@x?N;Ro=x4a z++L+~AAMlE#m_+u;Ri&|tLNRWt&Kge@tL^k8Ne2KPmv8W;#EiPpwzlpL{m!16QxIJA+O6#V~gRT{})5ttAB>LM$F;H3KKE$KTL^=r&i|cg8hgQ-LaHF4xl(?w^xZY8DN6P7B47K935wLze(yj7)ZCBCT zKF7q67KGYhhP4VviuVX~nHH|NhuZ?_G9|UnUQdbfcwG+lwQiZta#AkAo&LdtT zJ#t9Y8BV2}ZLa08O*iSsz@l#>(LGxp5m2tQab69F&=};oEwIczZk)C}_3iJsB(hQ2G(_ zoPFcGYKI6(q7T+3ghbvx!DX9o(SHdqadPDJWeSt|`1ZU%?iqP%725*kH%^dzQf0cG zW=?FvQ?qC`uUh+3K3r?2n(yrJb?Rl-^kRwX&u`z$C_e1j$5bOTmL=*+naXk#G)Wbm z_zc1q)X9Zos==H4ZTXA|M}(5`$mZm6eSq<@40fOv>Q@?Yv^%a;2ww72F#FYN{wpC) zJ*fBpgU#}^kl)m=%utr$Uk<}O5H=#}R1cpQ6&wwIR+FOVDc`V#|E`B>)Y@4wX&$H4 zNXF&<@Qmv@V5jmtN8=PuF-sD$!9}I=(plMPA37-u`lZm(%{m(&t_03f|Dw71dW3^; ztNEP$=kliYyh|ivgX%pse^`xlgTEkNGi*wUp$itKF`tn6o}d3jPx?fW?YHNkuMa2S zo8zZ&+gegz{IIr*9T&0Hufyplgp|p2g_H`6GCRD=GmP+I3lk6Q(}9h9Vp=Sn-Z#?; z_IUoxXrt_B9y5a_lX?nmTT9(-wl2bY;|#rpu+SFItx$;jx>kh&Q0DH9Jg-)gH}YL# zH@-PYntSA$!4K8of(Oc{4y~u!9;c8tLQ7azVmoqQnay$5MLqeASyzZ@2y0n+UK|Ae=xlD?M z0q|1Sw!u{|KaT}{wklX6zI||$ylXkJJt_xcUY)P$ zO;f~W1{yN9u5NM?zhHwCnSuJ?!v;SD7lEk^2>&K1Tv08FZ&&xvpA{Q6$pHXVBj>BS zot2TW_cfVKo-K!R*F@2?aYM$mxj^6~?>vuIYtAq1W1FetzHF9Qh`*+*QDqxE0YKI4 z8{7y}y2)l;{7@525H+xM(&-zQ#&PrVR+Wn8HZ(C<9S3_KF&(7!f( zwDRW7$dxPlgARJmyOOppQw_NR0qG5%mPZIhaDmOqIC1<^1l01S6q7I%^w+U@BU@L)*)z6ifurbLd8qO=Lt~|`*2*;>gDd2t| z4cdmM+ZS**P)mIzp`7XG648igS_^m@QQw$qjaRf|edW+E6>3EJgD#a!3PmmIg7wEL z=yg(;wgnNSzT29+CX~p#RJ7y!yBye{0Tdiax@iF6ZZrjNgvrXtswnbQWHuEByIbI) zhd-=SAV~nN&zOZ0A$XhDXFSAa8W8TB^1Be(59O{giOG5y_k`w{y=O|rz1pRg$cbFj zC^mdseq8Ub&0uT)lWViQiIf9w&A9NuNZ}@CY-{`1t1WeXN@cu!jdfJkCH*%ozi%jh z0IKRy&X|=ZDJi_h$0z##cWd(YNLcww14B8aSy+*oYR~570xW-L*yEb)w)_~^YF4YJ zX-$80yrX%kndmL*D~X`cd$`cY0U%XOq%Gd$YdN8lC%qYW7k%CFODP46)N6k|85`*TyMWiZ~+qZF3mR$sUKP>Pba zX;~PZP)6cOh>;ZBw1BiDZgDvl0Nrq3!~x>pZgV?jSX`)SM}BUF3ErO5H| z?az4(*J1a`P_JBJm%K^u1x$B+Fr8#)YYL7o2be0J!Fv4k^1#BO$BDVXLrv!9(g3@! zBCFB*^4!d3@BLxfldArmPOEu&gjh+R!Oa1R^tzrlDWOUQ>m(YgX5O20M*=PQhJa~t zTEyR(!S~R^IpP%Zq)}MCM)|8{v&4WOIf*D1-`jU{p3Ov_N3$<)O@zVTUth>0cmP7@ z&>49tclbQ`Ixn9sT%5yCNWBxjeHi{Kp?_0B+pEQ>;Ox%^F(~13V)>Hg*{`Epi{1GY z>Yg3pUGl&5_oespmKzN zJ-smPJvoco`ti~8&4MBghqa9*t$d={kPS_pZ2`1gboW!Kl=%x1_j_b9@~>KU`E8LDeQ%ZyY}N5Rmz^m&WfQJNSf3@dwT`_Ujk3a zGWf0)EIjgYOjG9^iOmN*&$Zk4|IVM36Iwq$9BSZ4DG!Sjj;Z)(c&!-}U8ZNrq5Gq1fKxw&lAHv(trwcQcWY$%S_k_*m}?=fV#^y^shjPkgD{N_{X8_uNDt6DW?rmi_8bD?YilUI3}yWRnth%CA9 zRve05Ua-W@M_W1XdQ{`Q$8Nsf!svb0xGrwvgZreIJ-o0nPrFjFiALTUMTvBxI1I>P z_<1|t^7O$}Ab&q&fpY1^V2p9a#vjjEZEQ_rlyECIj06YHyeag61DBhhBh@&0jV<0q znfbPf+^o7IgzKi;^N45NLPoz@u_ljl%(FI&>t^A{69zuLQ9P8MI%Zy&p5exyU4NAE ziCk+DV{X0${sXI4iR!h5e0O$v(P7mZ<_5}}f#Zpl%$I+^UVJ34s-i`IsdM)5P&FD#+L;|Ch|cIxm5|{& zz=LyEQ!5SO@WDnp#DzPH{ZrmxK}U_{6=bCOzFKZ?Z6Hn`>)jj}`HoEr;VSHxx>oxq zm00GW9c>~yIbE>2ot4rtFCws8 za3V1vZ#_3oFI0%Bd83{%WIcaM6-+jix>#eK@tG)8=O8IfXfKAYuT9_o!DFVHXY+iuuK)$1y*qKpw8Lk<{*s2=-c+t{_Z`UVjix?}{33vs^(yvsp zV#-ozz=h$3%EMy(z9QXU>dqQC$NYEW6@G)XCf?eGG588{tbO$`W{OF}H!4mb1PipW zbBk#9jBP4>TR1Ja@}lMJXkr>k^rBN;jjcOFsqVWwf$XBF5(^N2FIPlSF1E_%HDi;e zz7OjA&HFs(mM%C&zX6(=s!7)cJ5mj=%vDBlRpK0V_!ufUdy;D2T8bLdS|!HYzu&g> z)kl4qI;q}GPb+yDeao@HycwVs>rr=nR#4wO?&;xHSL%oG+%>9h{<919e4ND{wnPY4 zi+J`BT_w)YOq9_&Pr)Z|_4E7F`^RGy>_|F0Dbm(s$1qATy7GBtV}c#+IZmf>l>F`7 zp^L1$ezlda;&Zy?PlVk(9jE1%n*-VQO)J?na;z|K`nK}K7>W24^}@tN z4UyVT`D;K|OYWAJkWo8$3}-Hx2Tz6Y!=g0J*&0Zy#~ANd>Q7%6MTFm!Rs^pp?@z8w z@s{=@JIpoS3^l&aty@Qqg1(8kWlvSPtX*r!)SroOmb%QvplUErlH5&v&!el7O;dUBg`Zq6$frL z`PG5BO!+Bz%_O;4PeXB9e6LfelQ&gK_hLU@o!rBtNo+vo?9F~#XF-EAn`+rgr;5Xg zo2~nHi_EKl{QAnOtG_1BnR4`*HkIF1`B~nenqAM1He}Xd=hOk{2r zW-HHA{$herB0yn|JYGFAKT2283hcoa@FmP-nG{U!FQb?MyI+aYz|W!Kd#lL_MKCFD zLpAe#ej+zaeujVXxQrB*M;}j6AbFm{78C2Uz9547A_VbLz6d#`{pqr*xhUZ6E>uH; zWL`Vs!WBz4PkvPdaS52oCic!ETzzvl>kh<8J{Xcn>6>ed{{rr}IoZFMd15w3d!O_k zR+clo2N~L_MR%XHqnj1BQ~y=g4Z~{a=yLp$Cn6)69x=Vs)HJ(WYiIGg;qF=@@cs8L zf%Rt$v)3xEVyZ&wJBT1SD z;A7&1o01H$7vvZao##Lq5sJ1vktf%eK@Zssuez~;wty({UHKqhDY$9uTB_Qfz#tPI`>AGftim-0|WAZ9)BxLYe}{Em5f9O z{&QHXpE3>7()iQ;dsVHIPRn~5vv388fA@H=i4C0^*S+_fFA_1fOSAGSoKA)w)+#yn z!Ug9J&G3>KKbN&lRSpB9B`hNb6#9Agiyrp9c=?x#Z;`}cH9VkVK1P5dF_C+iHdESq z_r9bn%;q3-im0rdpo;?}614xWkyjtR(*x?%+81B{qDK!ip}Kp1I|&*tUcT)@&U?QV z6s8NxiFX#GF(J6e40J&KP*gubKfZfCeKWu;p%a$P0vxQd%et{Nwzz-K^sw%Xy?{42 z`+kBDoCR23+_nE_<{Y3u7+$Ho0j9X<2SDuu-goyt3t+gvq0bBIdo0cFjXJBMIe=4T1-VI6`oN|G#eV0l=tm zK!FmpdUh67GZ|$h{?(?$T?cLXl@Ub>wgk!C2P)+o5!jdz=>M{ieg$^ibHcJ=#NGFUCz?* zlWRNgpN*{5*v17r*Ht>kbEzlOSO@C$r|;cu3#qRjL6%Pr9=iUV^=<%$wK;>mfb@Lp zK5JDHUo5huq@>K0dUo_1{hsmfEgur{4@yYfg%2#PSM4fuje974F0Kl^x}vXL8ydIt z0T~^a9#QX$Bcu87T;bClQD?*I>FspQv`-Sb;dGcl&z0)N>bSP^|Xb#hDaf^z4%_0aBWjKr^B0V?)GA^?)1}v!{CdXY{H8Z0}=S8u=F>E zs_2yLMxoMO@%<)dlX2gpGQ!GsaP&~&-!;A>AgpBFT9 ziRVXGMz;k@l@{(z_zw_Tq5>P%ini?!5K8GL)54B@eU={jSjfE9wKzQu5oNEhU2~(v zP43FOjQd~o=HJ-E#q3S&mn(lXe5qp&P@rU&Tm3D!k37(6PVwsR6DW)!5t2M(HP^L4 zPb+8`AT=fUGA(&sWUuE^*gAG5uT9lsw&!koG$^O~*?YG0ug6S%>$&_#N8_$Nd4!Vx za63(=)ce*Y5>D-KVt?pQGxz+Q(%$xDS-w~4NfI=$S`&_adnmLM&T zs~oOo8zUbOb?;rO=xu}!N8G-B(}?uo(h8>YSoE&QM32fE3->;T%PmgW@aZS?7Et(j zLXS0E8O3)*c?z7>j3P4mNgkqOlk`;w`f_i7mh}1lCe4c7l&a6H4({u#d#xk;dbWH< z*Jw->Mc!xryl?TKe244%>4|p3YFDpbQH%PDP;zSqQ##ZBEBBI>;PlVt(@CBxw5hy( z`Dn6DOm3{f(&7!*i{FaT2kD#AbqR!z`)+&0+&;tXGVMf*H{N@=3FX^)o_dtYt7AZ( zTC{!<;=X+{54|M#sAP){usX27D#g@Hi+1w3{33L4{5rJZA$m!e20V0qI>!GR&Z=^3 zKJe=+zJ2DS4^)<12iFK9aHVoqaOy8;e>f(Das9?-Oif;JeM$jH~I{ z$K*o+V8gXbd1d(jkF7V4hq`V5|1F^sDk2J%N|YsQ*~St=n5-dGmQYBHotY9UBxK*l zJ~OtIeVeO3orxK7#jxiYbsdS+C%77dw{?i$S(tU$IvB0Adpr_eh*7dOuPiH*cs ze-gMG3+d{_bMkFBT>_jsWeTcY@wNG1%>q*l6aWO-!I^-W|MTO|g?vI~5smbh* zmQ(0$fbx&ZpLxOwp*^*eAdXZz{>XYP%Ct=cCU39I%T7y(;|8v5;rWnT%5CKlC9X$g zt?9I#Ua@cL0sT_-nt?$bijJ)fY4agL<^6h6Vt0BTPOnxg5TkZ_3wSVc`H$AowSiV= zWs@U0>qe3Z9aRoRRtZMGnQ$#3{w}+eXR`O5b?fgBl?LN1clzX-XUOeiu5UN9E(rpC zOwbuCwtasVk(#^0Gn6z@GRUdxYB9H%7r3_^C@$Kissr1RA&dPpUd^+-eFq+4t8Dqp zIgIe?g$tqH!KA-WefI@C+J5e$;H+-feXQxP{#kpUh}VTon43;FkV|3q?VXQjFaPM% zHKnlgo@IKM6@d)m#2GJJPgLVSkDbsAZ70N}>Xu+GG?3lNESBro1`9m@0y}vG?LGFp z{FzO$2-bZaFF)(MtP>oeyZ-rkXJpR|zp=Q#Q9ukC;pDs0kCR{7OXO!@!hcsNwywRJ zM3js?gp)3|t3d1R+<*E#Jx#hqpwq``UESJZv&j&0&qRQmGiZD3r-zK|cA}n3@+7%O zzC(M%D6g)XpGzPwQAfUj_@GGX!@yMIx7Mez({`?}i^8+9xu#&dDIh_Q>j0RysMq4ib(9hyhaqrAG$UrWvYolL~To=pq zSAV4eqIdf5fs5(CCqL+KexWD)zzZ8zqj2N%?~b7SVzna>Z<8e+D#v0|YyHZCeag^^ z&Qg8kDc_6l2!-z6!%z9ry^lz|j(v#D;{&W)?)ZQs7>&Sjn3q6)WH3^dx@S+SIbK1@e9JpkecmjSJKS-=N|Wx zB_>=CvHRUIZF%{y3}$?;!%}ss;MefUnqv4+%em|!;i=-qtmem^$(Pf9AO@`<3LoV8 z-}Wvw7(Yx3_q1Daaw+5E_elg+el0vwuYzB;&U%c}`XlR|*wT;LqI~N*38V~g&Y^1+ zk^b?`Y$LP68XWx4Ep-p`c8v|ort(PkYG=&>j%0ZWR~gt;@prKfgMewA|8tAXs(BN> z@|X@`dOY!=F^j3u>fU#-4r(%l-f=B3>%Y2J>MK|gOL|<+`Rav=a=^@pQrr=llZ{)t zRy{G4!RJo~+flTRSIS^gh8o zfUhejcEE%mC+9jEGT~riV*JGBP^@D9jF_SrSYHfhC;~rhpakZ2*WE8h`qAcaTk~ZB zT0)wCerIC}=>qSYDEHv#wq*Mjzo0L1t*ztcr((H4ZSL-cTUMouV+W; zxh5{t;q5&}+wS+2KFxJbS2rJNvP~Ms7qSqp{{H95%esn;`auhO<;l8?(**=w&&|PS z0)l9lr>HT{*j&rJr9$JoEX0K;m#sw@d+@r+xkN9Yxviw+2HAGVC2ZRV3cH|#@#x!h z_!Xo~F9hpHsQRh@Pt{g{m2BNcXPfmga6()_$yvGhtwD>KW_60~@3}l?LP)#d$8)!= zM?&CV`TPV-VakOl{Bf0pJ78S4UiCtJ@zJIu2}O&~log}S%q~{{iiN!ECr&`_Ss6ln z^qCT!oo@~!;SXm{EsBVB=9yYyb|3zByg{_g=F82tEB}J%u&PDiNU94jxEQ@&3L8$c z!QQ|>wN|XgNav@Ru+Hd0$M;lL12me;MR41qwVAkwh|PN$@9% zpHu7k5i-VTw90cbiO4lml<1!M5*^r)nW$B|kXPYkSaB&4LHxeFq7WXQ^<=lk3Z z?jDctzn9IS>2Dmq$xF1yIu5O+d)7$wy=gEJpv@Cf#jcg#Igarq7}r>doaeP7T76LL z7I~9@#PDm-TJm(iHCue*+McILvLIeZihq70@Z?)BLf!7@Q=gsX>G>}c<|t>04cOa5 zxN^DRe0&O#>Dms1OP$KKvIaFp4vXf_&XBM%VsYJcgqbrQUSXAZt@zZvK+#ZQ>Y=eu z&SK__gd)`^cG+b}44fv4_L6aLVvd!SEhk{p%KYA*e&GRKM<@C=C zgzsDg1GJy4qHJlPST%F*`i)>Y2gAjpmY96RAYm=vi4rZ#d;Wfzk0{^Hg`I%Ag`kT>5hqhzmWRlg^UvBBT&lY*T zS|C&IU!Q+2d;7!|tVx;{Ho|XR#gS%h#G*aAyl`xe==@p_nn7}0r6pv%Hi;PIb%LK6 zGzmD-@2#_DoRl2N=RH!_Ic*)H*(rNx<=4^6um%0W1t)Peq1{naALn%`&uF~6>!Ia> zLq#M9HN+lw7auwF~6;75ja>KQnE|b%Y%=F2!w$AMyPj}nnT~-RdwZ-`-L=U3ozMFav z;~~aEZVu0DZ7_>|TqoDnrmz`iyS7)~fw8y`7}Jtg^UAD>B}=1?q>%LwI3drkJM!X) z8ELxRfhN*JLbNbr|2~hx;$V7waJo)>Ee}nTq1b(ya$V(sY~STm-0st6I8_m+)-L|u zM9!p4%dpzO_9=rMN*@dri=C3)*wpTkLQj|%@j}xNo%-JYRRpDGm)mjcb^S_bKKy>5 z-@}~UsoFwHT_VD%W6l?=&qUiR@7roII#ciRil_20Hl_ct38+|hR%bAo2QwM%G#ks4 zEGE&WpUye^^|~^B_OQ1fO-XJnW7GBbv9E_}5?|l`jLGBIH81?B<%Ewk9AQ?Hr!@|= zTUvmds>Ni#+h|`shpBor*O|+ndQMFMV$*O*qa0bzWH}$jxgqU#&D!-isrt*4W_pa% zyRAX_S-$j&$e}MBMJ>6%HUi&!aKUAV=~6};As#6^kAC-s(mPq;rqb``x2Jla@9*NY z6O~w%?{4HLAx-*gzh)3zpy8BnI1XJ_n-Z(#C$=)Y%&{Fk+o!=~;@$%8#9yTqwY8G$ z=>=S|?J!lc>-2-&jTMiQlM-VKXJ3UrI{~!(AMtX9Ps?<@qDHlkpwf z+{EUhp^B`Ok!GUf;!588(U;X+SF4{W{cuoLJZZVZ4T<7kd?JTph^y^XkZ?uKju^I{ z^;urJhzgZT=w~1AJfnR>mn42k@pJE!rJ(7uJU;?ct5#4!Y6gBM$Uyh=`VW&kCve)0 zHx=s+GvQ*b9z(7o4Vb{$J=~xpgy8DGo;et*rEL1V=Fe~HwrSviwQsOMSlB43VW_r zkaKN4Nky8QsQyka(2@9TI;S~E=$1}U-4BK}%~Q|bD&7?M%))8{;cBqU#YwVdhBh|y zMiOl&m)GL_Ke3g!&CUpRQ_!-EP|~E*7su`gojrDxe1$dR#zHYNha>*jw^8LPLfy1b^0B`c#Y$GF1v+D>HqgWPO zQa39m^q^RzW7@Slrrd=g=BAT#QGK`cDYmBN3D*$)%oaghil3(BN$9jGid`Ejh@!st`XIHFp5 z+wdQpisDn^-HpVy0L>YfjWQUok#uz>H%_G&joUqAfBy6{OXtvdc5zD*e&7rG>t5Tm ztki~@#9@@XuG^G}+^oHSA*sQ3{pmC=pVsS2l4`(PU1>R$y#ch!TQz1~Ww;?iKdQ`gNt!c*!fw-edE+|!bEm$p~xQDIWz z_GP5f##?>k+UTs&)*TqsLWS2z44LM)oMrfw7gsiiFz8M56|=F(jhUH9$m8=AZX3Lt ztFl>N(JM1O8o6;k3smz2(Mdigv(4ZwZmBzvb8jnKuGTc`_N=-WYdC%O!Mb3C5AW$X z+P^z;y_AF=kND_<5wlVN?mA00>#Ei~KmFd5vjK}DZZ1wbEGE(2A}OQ%inCuTlNSGV z6n69rCgxLo(Mrn}i^iUW2@{87c=bwFjP+VCj_yX!q~EIt6v+&iAAV}?lWi06 zu7V0*RypCH>@T5%3PnsK)e2M7Fb;BF%T|ztM#w!)Q1r{RWx$F3fD$9bzyYSDkm1*a zHS5E0O7xh(?;cb zL+2%L>h1}RE_5<{-j*esk!Bb$US6Dg@V&l@w%d<(0>~PZ$!q#-WCmhi1*N8%JSbbg zQ|P+4*sqz=a2cu5gv5+JXfIr3-eEhhz-S!uQd7{cTHkW(D#p6KhOINL6c$)m3X>X< zUA()N-q_W&85f=aYC?tzKf$y9EqhN3zip&w=E^9>Wues)c1Xv`#kYCHc)7e1;d|v< zF3+hNvAw)ijexSHu^lak{D3j3#dhZ6Mn|bz{^tG7ylS@9ufJ9t4OsIZ+mabX{$2JF>t+~R8e3awQZ0)iD*X2hd#8D z6+_9zb4@*8z)7{BRx5TD8K}mP_(4RCQc~|S)0uvx$98a2ZfM%r)na7nD3%o?`W_3h z=QXOUndwytnCpFQaCNBw)ijXn!lD?@rM7n`VEod9J%Snxnx0hAbsstusmhL7N)QWN zuz(jX`>S0t1|d&jldU%`;mO)ETdy{ye}LK95`yfjQ*V$F_`Gd=!nb#BLa<4Npdq=@ zncW~*&G5vo_eqKz|MjrFjGG;IF0avHj{4<(0eI58x|I1F{D*MW*yCnb2bZ`!h?tHiOVm$Bl)n?EymsMoOoHUgoZP{S7jrshN1qmnK?%JlGTKj=HYPiJ*>4QP_ zLHRoNT}3ob)$UeCAf50UsFK>*?`(3Z?aT}XGT(oV=QDTLXElz`NHY;LoNVk!NgOEc z^@?oFov(#wn@0QQIvol&iN+_yX7`1fM9XcMMw`pJry8yIt6R&$eIi|)Axg4QzvO%3 zK-2;!)9+M}*0;3{lD&K`p>nJ_SXayA=QT7)Px_S6=DHnTG*J%7qn1JspznLYwqX z)6Wbk?7udA+nB^EE?XDX)lQ`gCrn^JmkSN%GpWZVVgT0>mZ2X@rvcqppMNjdpI+sH9gP(-b_E#8_oA^5g~ngB1a$?ZZ}}tGDR*g9 zP*Pax10|prWIZV?`%th!d#b`B$4U#-##bfi@UkK1cd&84YRk7B>VyU(`F6{aJ8C^U zLU`6a{kvA^oU(jK2K zQ2dzDyT*(Zg}}?+7>*SEomi1V=hWVc<7#Yj9KI>v_4N_^d1U5|Q=g-a0%{RNIZ*#; zJ&XclAu8%mk5VQojF3WkBhR?=T$uP{Nk(Wz)s)V{W4Ie+5SY-pN+STB2TGGW{ITN*ntrYN6YKWEIn zMx?I5sFMJz&BBw57RqNXbPmUd!Z_9BH}7v1iLQCr;Jj8{35x7cV?nITU2?K(a%{E+ zi6vTzQTja6*(escxFB3h(j~oS z?`KB!7QM-KQtVnxJ8=m2wl<$HolWtx-Mg{%imSZw2=huY#xV^sqzt9+5U#1eezn~d zzU!*VB5@huK_k%Xsk?6$Q9=1Nli%_m(EEHphLX!*q+F&{9*f<*jNBaVxS#cDwmOKt z&(fcV=sM1v!dMV;zmnm%Sp|QET{n2 z+2{o#o2a)J`N7b=K~wo7sEV_I3tM8u?nqU!;vaB;RS6A`qJ>>_jsDQZb4+-IZNk3U z@~o27;ZM9L+Jyb9T+15R~Vu<3GJwJvpceiY=k#|sb_5g7CJ4h6EI&ItQ z6&mJhhJ+)G%*pcgr$Tcea#x~lDiPc0v7*&&=oBo$V=bzE}+{U6RqpaN07 zT+Q^Qe&w){VudyK&J*!q%u=CWHG4-k_(o?gE5?u|Qk8+`!%LgEH{G%n@`K1h*~;?P z8deyPnQH0qdLJFoC#8_(zL*!dYcStO2QVNdEx#$Nqoz7^?DqxaXrNht>TP9So}i;N zmE@OCF%aT_$Bkl*GyI@Jb_DfZQx9a-u#vPrM{w`cF1=3w*~@xt zsTDUkaAX-=fbV>N>Nde~FrG@M-JrBTi*b}HCOQE2?J^sxQmwlgsgVfI^Fv6yDUAgj zAmD{$Lk@^1KtdtIQ&yUB^9Bz=qD$qnJZWRiNHkD6N8Mb#%4PUD$JboqH>vE`!K$8~ zeC3iUNo3#jnJ|T)V*{ianBGLw_G!gV9=1LIOgSJG@sDVY^%! z6WqXU{-7kV9RcuI*aX2fTI(iJDf7F4V52f^W}F>qdQN2&aXR6@4`-Mm3qxRg7siBh zWIEHv_&?8{eYr!wxNq569u(d7dBKBU5R!~FFNE9}1SX4${++IYLUdB;l(}!J$qMxn znf^mxG9)9kV})q0WPn30HDePm9N?AT52H%7QVIkZXzbWsu11JGOS*6Gw7mwdkxC@) z|Am=Xu9D!AzU_8`H$@NL)KfZqDVUapcyb%ut@9-`7u9S~|MdQTEzO`)PP|pKqBbCv zWe%}9ct})KFYabn#D{X+NAE;{W3B-nlp$FT#R}Ga%VXRMnBq;{E>yPjleV}fov;m9 zoF19I+daZ}Zf2;GjR9&%g%TeQBU(V;-4y{r()BWP__NXmK=1{Fl{-%hb2tgmlKr(3 zxQu#*AinI?1Hk}zLy;;G65zyD8oQH;_54d6`U#@bz}cM!mrSKguZ4X`)J_#W6{u0r z#zA|k3ea=_l%%mY_a1iaUg?RtsuiWb&&h_L*=G)s9w#}&we$Pe{Q$?kZpP2*!peT4r4^Dyg zHO-!0l77RoMZN{BgTHqMu($C%fchT(vzh#&!TkX<634EW?KKv500TT+L$6n)DE(FKc~`g+)za16Y+7l`%8P^mjRjqir9%PuAaFF08W zBI~_RFr)TS*{6*ycP{D0AB6-_5w^2XyOf)5Bl`2Z63Ft}0KcAhW|g`06+_i}%DM>` z)z5(K-h*-?2TlGVoF8^0%)Lfj+F|m(AQ%qYSdBNSTMz|Y^}iiF;M`-5yS$btxOX(i z`_NKEwc(lJ;>B0|Zy0e5{Ua{h0i;0X#Y&gL7Y2hdV_z2z;kt{`Yzt^u99(o;S2{fFCc?~=GXJD72N;X zukSgbS1qw8N@qL;nAGWI>p>3e# zS8^%(S+u>kdaC=IAy*`O(faa^<8rLSE@WR?X=+$9J^AhNYM#l$R%0OTacET7hL*u) zaBiX__0lQ`=wN-AZWfw5$b4+RyV+WgP~`XX`=tX53>M8Dut_+JU+MzeAYs3n|B9zG zFA6+ACg8LP?28?to0y%W1`v!PxbuOJXVO^F<}>FdtRJ&F&A9nEYu?K{rsF# zAOuA$bs8()+nR=>rE$oRP~SVti)aJaTo&~5CviBS_b=`^Akk|gu%*H>NP8q%qf?R z7akwmcPGK&3#?REFn=>6a=s_$D=O%Z3FS0`oizr&_LW3P`el#Re1taF0ieHe9kt5G z*B3>Hc>ZRei*Gc@+xNr!c9Pj^HS%mAKos}=UbqF*eiUcF@2~#Y(~n?)A9a^$*%AXQ ze@JC)e1Km8-3)(Cs#$iM5in<8x~LJ>ajKy^w7+NUVxSL&(dJmM5C4OL4{I3|g9Bb@ zv<2msq^!tNqmBIk0m2_hhQSBH>4}KKU)l!q&giszyheXw18Us4??ZncVC=xpQ~Ig7 zh_;q)?va2!rT_ZcwxT%?z)$5JG8+B^kMBDv5D(F}O=uqU4x9$R3xWi&mK`lg;FVEn zacbYg@3$bFy7r9cM?T*UGeR_w0|7pe0a6j|tfTc)S{fM;SlvC9yK&+gZ2zgBx4DFV zg)pZ!Qw)4dzQc|o_<5ynQ#%vZs1Z$aj^3L*8mQ>_QSw3bbKac)D5NcpV-Oo)qED+w7e5Ip= zEUHsF?R5;~Ff>U2lVF)i%6+|ftGD<6 z?@ut%`14is2#7)!wY_VUh)1W7LwB2k}lUa(cGSJ18LG3{5b#D(~YNFzjGg*?!G6o?|%*6nC^Qc zo!_eTmt`E43(JBXM!jmDta+CGT&2@p{mmG2`XsdrCl0j7@hTo$~Aj#rL&)^#vzpYO7n(t3RZpA=t&6zadx&bq& z-nJG*X3_wH(!I-rv@3ER41Aj)=N?};GlQ#n^tr_CA_T?6C#~+j{&4EpcynjR^rYs@ z#R!<&*znDh%@HB89R$bUQVSDWgdf%Sl07|Y&irR3RK(^2`x?#R@{cWTnJ|e1JuKfV zxwBOZ@aJR`JDQRAXM_=H-fU+JU+JCjCH?dxy$C!fjnvQ=pvRXCdrK_NdCKm;fwm)K z_C^QsLL#j(@B2j4j#s}E^P=R_<{J9)QU{mb7cA(h0%xI& zz5y(3FEJ?V2+7Iv1RvzB3=3Y#B|tdaLzcd7@vjK(jLE*~Im|aE&7PBOQ!;p) z1=Wye#ub?heh8RCnzqXD@w^60lJ;*rxLt6dT68sOcy@0|9-J!H3GaZbPIdKobM3{P zQ%*Z_*RbmIB1HT+M)Q2~k3t#9pM-|$t10tJ$T?YAEtn*kX@A4GgQ}uvzFIP5fj2Wq;TH03E zXE1l{%OscTH6jznjFdmRK{|C^Aq?`IhtBCXs&UsnV%d9(qM5ZivP?ACX{Qt$n$TC)3W>UTIk2t7+7r7r zJk|1y*QjjC@VN3B=dL33&^xTj2TsMZUnvNyL-F+xb`Ui1rU##J#CAdozk^}?Z4_1@(*ATq^k`MN7CZ zCFVJ^;dpCK>X1>o>^q|nT=D!aC=!Ke(Bt`AnaBvDR|dPkU=6{D8`y7P%31_RsfjT} znOXR4)CnkLnZW2rYuuioeL5I_cE=D;*4p+P-K=t9<304VsUALMHn)?}%vKj#x7qvH zeqkJQ1QoC+oeA!hSnG;Q$6y5{=rG|ZxZK=?nD9Zgd1csyr&v5zDmER2^&rD;#fFt8 zSXl)w)r^s2vVTYoQ4P9pH=F0RQP-m-^$+!Uk0K+2XPwUv)-a7f4&{f)yS7Ne$n8t^ zhG(s%Y7v7shBllSz4NXMm>oggjn6WNV%M9;$o#OLm*)4CbEtCGz=&_3d?n7zgth04AdOp=-wqzbq6H_bEvU6*$^C+ zHs*wqFoer3ZUo;IwV@?Qa6-lQwrln}br0dFuUPwHw@K2+e;h%B9PrVIa0G^#;AhdD zo!x0v@1=vawDoWdz=B5pR#~@m5at zvNY|Nhfy))cApxqj;SUsMSt76F<4Du)%VX_a%!PBE&DAKsC4-=WtPH=b|f?x)L3_D zIgs+{gr%bJGKd4Npn(x%wIo$?o9Uz`<%~e~s>A|U-P5;{hC@;-t-xRn3vSU7#X7&S zUf+y4v-w1N)v()*Y!=Yu^d#`Z75Bh?f+`dEkF`&1Z2_ZCv-I)%26-i%{Hok&zv1>6 z>T(8AiQg5nSh-ZsiaOQ5Xv9NM?=So@!c_5A#w09E*@l@8yHQTU#;4K8c(7A+uKyf@tg@$h$RPi2-d{V<6J+A;a_)|~0Wlu_Kv3blu!Gqm z3)9c<4?R}LT;^?{lR;Bvnn3is;=b)z3WC756XE>O9zOAaO&xHURvtT;N2GaFBZmO^iX62Edn3Uz%87IYWIx3hrTupVkr&u(m~cj;$nGkW{zbxqCFHf}j`ZD#Qc+4q z1CU&YDq&FWAkb$zbxYO&LkMMcqi%c`TIpXB71FX2pQN0laVz`R;~7kJRAWGNb4!yv z95}$%ptff7{Z2fU8CUzmpm+S=L?e6QadEH`WT{SER8T1lQf7|b`{2FD4H8oDpF3)b zlnk5OqA(sKdZkH5?egdDqaX+0NJij6l#I0fuLvtpEE?GFd^5tg^(n5##r3#?g<=`W zv3ap*E=Mo_OVRg-dY!h$pjbgAwfm`eF#SQ?24YnRM}Yob4J%q$2WY=GUikn{``9dH z7(o|TubNiR(Dc4oUVMAj^AR72Mx8*Gk{1vGBnOb=v31Vr|C*(0Mpe4e&&@$wDaN

Si9=R?!~l6aw)HhTiy%GQkD1r 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 k_ont)<D(>7{V??Z0T>}e1ONa4 diff --git a/build/darwin/AppIcon.icns b/build/darwin/AppIcon.icns deleted file mode 100644 index 7667632ee8ec9f36dbc1854fe8074b4c1ea2e0db..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 314885 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%0AA16Ikll@n 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

KK;Vc@)PTR+qCKYx^?HPmh!}R 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 Ts?+-AEO~xfznmp+wGjUgS4rt} literal 0 HcmV?d00001 diff --git a/rust/app/assets/fonts/JetBrainsMono-Regular.ttf b/rust/app/assets/fonts/JetBrainsMono-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f94e7155e8517403437b4d73e988f6e257e335c3 GIT binary patch literal 270184 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