fix(discovery): decode dns-sd escaped UTF-8
This commit is contained in:
@@ -27,6 +27,42 @@ const DEFAULT_TIMEOUT_MS = 2000;
|
||||
|
||||
const DEFAULT_DOMAINS = ["local.", WIDE_AREA_DISCOVERY_DOMAIN] as const;
|
||||
|
||||
function decodeDnsSdEscapes(value: string): string {
|
||||
let decoded = false;
|
||||
const bytes: number[] = [];
|
||||
let pending = "";
|
||||
|
||||
const flush = () => {
|
||||
if (!pending) return;
|
||||
bytes.push(...Buffer.from(pending, "utf8"));
|
||||
pending = "";
|
||||
};
|
||||
|
||||
for (let i = 0; i < value.length; i += 1) {
|
||||
const ch = value[i] ?? "";
|
||||
if (ch === "\\" && i + 3 < value.length) {
|
||||
const escaped = value.slice(i + 1, i + 4);
|
||||
if (/^[0-9]{3}$/.test(escaped)) {
|
||||
const byte = Number.parseInt(escaped, 10);
|
||||
if (!Number.isFinite(byte) || byte < 0 || byte > 255) {
|
||||
pending += ch;
|
||||
continue;
|
||||
}
|
||||
flush();
|
||||
bytes.push(byte);
|
||||
decoded = true;
|
||||
i += 3;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
pending += ch;
|
||||
}
|
||||
|
||||
if (!decoded) return value;
|
||||
flush();
|
||||
return Buffer.from(bytes).toString("utf8");
|
||||
}
|
||||
|
||||
function isTailnetIPv4(address: string): boolean {
|
||||
const parts = address.split(".");
|
||||
if (parts.length !== 4) return false;
|
||||
@@ -119,7 +155,7 @@ function parseTxtTokens(tokens: string[]): Record<string, string> {
|
||||
const idx = token.indexOf("=");
|
||||
if (idx <= 0) continue;
|
||||
const key = token.slice(0, idx).trim();
|
||||
const value = token.slice(idx + 1).trim();
|
||||
const value = decodeDnsSdEscapes(token.slice(idx + 1).trim());
|
||||
if (!key) continue;
|
||||
txt[key] = value;
|
||||
}
|
||||
@@ -134,7 +170,7 @@ function parseDnsSdBrowse(stdout: string): string[] {
|
||||
if (!line.includes("Add")) continue;
|
||||
const match = line.match(/_clawdbot-bridge\._tcp\.?\s+(.+)$/);
|
||||
if (match?.[1]) {
|
||||
instances.add(match[1].trim());
|
||||
instances.add(decodeDnsSdEscapes(match[1].trim()));
|
||||
}
|
||||
}
|
||||
return Array.from(instances.values());
|
||||
@@ -144,7 +180,8 @@ function parseDnsSdResolve(
|
||||
stdout: string,
|
||||
instanceName: string,
|
||||
): GatewayBonjourBeacon | null {
|
||||
const beacon: GatewayBonjourBeacon = { instanceName };
|
||||
const decodedInstanceName = decodeDnsSdEscapes(instanceName);
|
||||
const beacon: GatewayBonjourBeacon = { instanceName: decodedInstanceName };
|
||||
let txt: Record<string, string> = {};
|
||||
for (const raw of stdout.split("\n")) {
|
||||
const line = raw.trim();
|
||||
@@ -168,7 +205,7 @@ function parseDnsSdResolve(
|
||||
}
|
||||
|
||||
beacon.txt = Object.keys(txt).length ? txt : undefined;
|
||||
if (txt.displayName) beacon.displayName = txt.displayName;
|
||||
if (txt.displayName) beacon.displayName = decodeDnsSdEscapes(txt.displayName);
|
||||
if (txt.lanHost) beacon.lanHost = txt.lanHost;
|
||||
if (txt.tailnetDns) beacon.tailnetDns = txt.tailnetDns;
|
||||
if (txt.cliPath) beacon.cliPath = txt.cliPath;
|
||||
@@ -176,7 +213,7 @@ function parseDnsSdResolve(
|
||||
beacon.gatewayPort = parseIntOrNull(txt.gatewayPort);
|
||||
beacon.sshPort = parseIntOrNull(txt.sshPort);
|
||||
|
||||
if (!beacon.displayName) beacon.displayName = instanceName;
|
||||
if (!beacon.displayName) beacon.displayName = decodedInstanceName;
|
||||
return beacon;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user