mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
* docs(b2-8): record the B2-1 pre-squash head and ledger the voice_join ordering hazard - B2-1 evidence block: PR #1435 pre-squash head069412db(refs/pull/1435/head), squash1fe3df79- Ledger OC-0349 (open, low): the joiner's own voice_state takes the hub queue while the rest of the join burst is written directly, so its position on the joiner's socket is not ordered (documented in docs/protocol.md during B2-1, not yet fixed) * fix(client): 2 defect(s) (OC-0311, OC-0315) OC-0311: scope the voice_leave E2EE participant-left notification to this client's own voice channel — the frame is broadcast to the whole channel read audience, so a peer leaving a channel we merely read could delete their key, clear their verification, and trigger a room-key rotation in our live session. OC-0315: parse server timestamps with parseTimestamp instead of Date.parse in the reconnect replay gate and the clock-skew sample — the wire form is naive UTC with no 'Z', which Date.parse reads as local time, so an east-of-UTC viewer silently swallowed genuinely live messages. * fix(voice): 1 defect(s) (OC-0316) * fix(client): 1 defect(s) (OC-0317) * fix(plugin): 1 defect(s) (OC-0318) @ Route every directory-manifest resolution through loadManifestFromDir so InstallFromZip and scanPluginDirectory apply identical plugin.toml over plugin.json precedence. A TOML-only zip now installs, and a zip carrying both manifests is rejected rather than validating one while the loader later obeys the other. @ * fix(client): 1 defect(s) (OC-0322) * fix(ws): 1 defect(s) (OC-0337) liveVoiceEventsSince's cold-tier fallback handed a cap-truncated window to resuming clients as if it were complete. The query is oldest-first with a LIMIT, so a full result means the NEWEST rows were dropped - for a voice room, quite possibly a peer's voice_leave. Degrade to nil (the documented best-effort miss) on a cap hit, matching reconnectSelectReplay's guard. * fix(plugin): 1 defect(s) (OC-0338) * fix(client): 1 defect(s) (OC-0328) * docs(b2-8): ledger records, plan evidence and count claims for the nine fixes - Ledger: OC-0311/0315/0316/0317/0318/0322/0328/0337/0338 -> fixed, each with commit, pinning test and revertProof: pass (verify-fixes.mjs 8/8, plus a hand RED/GREEN of the wazero-tagged OC-0318 parity test) - Plan: B2-8 evidence block and status line (B2-0, B2-1, B2-8 landed; B2-2 next) - Count claims in README, b0-baseline, hp-0-scorecard and the issue register follow the ledger (315 fixed / 30 open / 3 declined / 1 duplicate = 349) * fix(ws): replay a complete cap-sized voice window instead of skipping it (OC-0337 follow-up) Codex review on #1436: liveVoiceEventsSince decided truncation by len(persisted) >= coldCap, so a complete window of exactly coldCap rows was treated as truncated and the supplement returned nil. Fetch coldCap+1 rows and discard only when the extra row exists. Test-first: the exact-cap case fails before the change and passes after; the over-cap case still degrades to nil.
85 lines
3.7 KiB
TypeScript
85 lines
3.7 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
|
|
import { isValidHost } from "../../src/lib/hostValidation";
|
|
|
|
describe("isValidHost", () => {
|
|
it("rejects a host containing '@' (identity.ts's OC-0118 scope-key premise)", () => {
|
|
// identity.ts's identityScopeKey builds `${userId}@${host}` and relies on
|
|
// isValidHost forbidding '@' in any accepted host so a scoped key can
|
|
// never collide with a legacy host-only account. If '@' were ever
|
|
// accepted here, a host literally equal to "2@chat.example" would be
|
|
// indistinguishable from userId 2 scoped to host "chat.example".
|
|
expect(isValidHost("2@chat.example")).toBe(false);
|
|
expect(isValidHost("user@evil.example:8443")).toBe(false);
|
|
});
|
|
|
|
it("rejects a host longer than 253 characters", () => {
|
|
const longHost = "a".repeat(254);
|
|
expect(isValidHost(longHost)).toBe(false);
|
|
// 253 is the boundary and must still be accepted (paired with a valid
|
|
// DNS label shape).
|
|
const maxHost = "a".repeat(253);
|
|
expect(isValidHost(maxHost)).toBe(true);
|
|
});
|
|
|
|
it("accepts a DNS name, optionally with a port", () => {
|
|
expect(isValidHost("chat.example.com")).toBe(true);
|
|
expect(isValidHost("chat.example.com:8443")).toBe(true);
|
|
});
|
|
|
|
it("rejects a host containing an underscore (OC-0322: Rust proxies reject it)", () => {
|
|
// http_proxy::validate_remote_host and livekit_proxy::validate_remote_host
|
|
// only allow is_ascii_alphanumeric() || '.' | '-' | ':' | '[' | ']' -- JS
|
|
// `\w` wrongly includes '_', which would let the client save/accept a
|
|
// host neither Rust proxy can ever connect to.
|
|
expect(isValidHost("chat_example.com")).toBe(false);
|
|
expect(isValidHost("my_server.lan:8443")).toBe(false);
|
|
});
|
|
|
|
it("accepts an IPv4 literal, optionally with a port", () => {
|
|
expect(isValidHost("192.168.1.1")).toBe(true);
|
|
expect(isValidHost("192.168.1.1:8443")).toBe(true);
|
|
});
|
|
|
|
it("accepts a bracketed IPv6 literal, optionally with a port", () => {
|
|
expect(isValidHost("[::1]")).toBe(true);
|
|
expect(isValidHost("[::1]:8443")).toBe(true);
|
|
expect(isValidHost("[2001:db8::1]")).toBe(true);
|
|
});
|
|
|
|
it("accepts a bare (unbracketed) IPv6 literal", () => {
|
|
expect(isValidHost("::1")).toBe(true);
|
|
expect(isValidHost("2001:db8::1")).toBe(true);
|
|
});
|
|
|
|
it("rejects a bracketed IPv6 literal with characters before or after the brackets", () => {
|
|
// The bracketed-IPv6 regex is anchored at both ends (^...$); without
|
|
// those anchors, a bracket pattern anywhere in the string would
|
|
// wrongly match.
|
|
expect(isValidHost("evil[::1]")).toBe(false);
|
|
expect(isValidHost("[::1]evil")).toBe(false);
|
|
});
|
|
|
|
it("rejects a multi-colon host whose characters are not all IPv6-valid", () => {
|
|
// More than one colon alone must not be enough to accept a host as a
|
|
// bare IPv6 literal -- every character has to be IPv6-valid too (the
|
|
// `&&`, not `||`, between the colon-count and character checks).
|
|
expect(isValidHost("not:valid:host")).toBe(false);
|
|
});
|
|
|
|
it("rejects a single-colon host with a non-numeric port suffix", () => {
|
|
// Exactly one colon must never satisfy the bare-IPv6 branch (which
|
|
// requires *more than* one), and it isn't a valid host:port either
|
|
// unless the suffix after the colon is numeric.
|
|
expect(isValidHost("a:b")).toBe(false);
|
|
});
|
|
|
|
it("rejects a multi-colon host where the IPv6-valid run is only a substring", () => {
|
|
// The bare-IPv6 character regex is anchored at both ends -- it has to
|
|
// match the whole (multi-colon) host, not just some valid-looking
|
|
// substring within or at either end of it.
|
|
expect(isValidHost("xyz:ab:cd")).toBe(false);
|
|
expect(isValidHost("ab:cd:xyz")).toBe(false);
|
|
});
|
|
});
|