Files
NetMesh/electron/bridges/x11Forwarding.cjs
zhaolei 3c72efcb7f
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
[Init] Initial commit - NetMesh terminal manager
2026-09-13 18:24:01 +08:00

320 lines
9.6 KiB
JavaScript

const net = require("node:net");
const fs = require("node:fs");
const { Transform } = require("node:stream");
const { execFileSync } = require("node:child_process");
const X11_PORT_BASE = 6000;
const MIT_MAGIC_COOKIE_PROTOCOL = "MIT-MAGIC-COOKIE-1";
function resolveX11DisplaySpec(spec, options = {}) {
const platform = options.platform || process.platform;
let raw = String(spec || options.envDisplay || process.env.DISPLAY || (platform === "win32" ? "localhost:0" : ":0")).trim();
if (!raw) {
return resolveX11DisplaySpec(undefined, { ...options, envDisplay: platform === "win32" ? "localhost:0" : ":0" });
}
if (raw === ":") {
raw = ":0";
}
if (raw.startsWith("/")) {
return { path: raw };
}
const match = raw.match(/^(.*):(\d+)(?:\.(\d+))?$/);
if (!match) {
return platform === "win32"
? { host: raw, port: X11_PORT_BASE }
: { path: raw };
}
const host = match[1] || "";
const display = Number.parseInt(match[2], 10);
const port = display >= 100 ? display : X11_PORT_BASE + display;
if (host.toLowerCase() === "unix" && platform !== "win32") {
return { path: `/tmp/.X11-unix/X${display}` };
}
if (!host) {
if (platform === "win32") {
return { host: "localhost", port };
}
return { path: `/tmp/.X11-unix/X${display}` };
}
if (host.startsWith("/")) {
return { path: host };
}
return { host, port };
}
function formatDisplayTarget(target) {
if (target.path) return target.path;
return `${target.host}:${target.port}`;
}
function platformHint(platform) {
if (platform === "win32") {
return "Install and start VcXsrv or Xming, then try again.";
}
if (platform === "darwin") {
return "Install and start XQuartz, then try again.";
}
return "Check DISPLAY and make sure Xorg, Xwayland, or your X server is running.";
}
function connectSocket(socket, target) {
if (target.path) {
return socket.connect(target.path);
}
return socket.connect(target.port, target.host);
}
function destroyStream(stream) {
try {
stream.destroy();
} catch {
// best effort cleanup
}
}
function pad4(n) {
return (n + 3) & ~3;
}
function readUInt16(buf, offset, littleEndian) {
return littleEndian ? buf.readUInt16LE(offset) : buf.readUInt16BE(offset);
}
function normalizeCookieBuffer(cookie) {
if (!cookie) return null;
if (Buffer.isBuffer(cookie)) return cookie;
const value = String(cookie).trim();
if (/^[0-9a-fA-F]+$/.test(value) && value.length % 2 === 0) {
return Buffer.from(value, "hex");
}
return Buffer.from(value, "binary");
}
function rewriteX11AuthSetupPacket(buffer, options = {}) {
const fakeCookie = normalizeCookieBuffer(options.fakeCookie);
const realCookie = normalizeCookieBuffer(options.realCookie);
if (!realCookie || buffer.length < 12) {
return { buffer, complete: buffer.length >= 12, rewritten: false };
}
const byteOrder = buffer[0];
const littleEndian = byteOrder === 0x6c; // 'l'
if (!littleEndian && byteOrder !== 0x42) { // 'B'
return { buffer, complete: true, rewritten: false };
}
const protocolLength = readUInt16(buffer, 6, littleEndian);
const dataLength = readUInt16(buffer, 8, littleEndian);
const protocolStart = 12;
const dataStart = protocolStart + pad4(protocolLength);
const totalLength = dataStart + pad4(dataLength);
if (buffer.length < totalLength) {
return { buffer, complete: false, rewritten: false };
}
const protocol = buffer.subarray(protocolStart, protocolStart + protocolLength).toString("ascii");
if (protocol !== MIT_MAGIC_COOKIE_PROTOCOL || dataLength !== realCookie.length) {
return { buffer, complete: true, rewritten: false };
}
const dataEnd = dataStart + dataLength;
const currentCookie = buffer.subarray(dataStart, dataEnd);
if (fakeCookie && currentCookie.length === fakeCookie.length && !currentCookie.equals(fakeCookie)) {
return { buffer, complete: true, rewritten: false };
}
const next = Buffer.from(buffer);
realCookie.copy(next, dataStart);
return { buffer: next, complete: true, rewritten: true };
}
function createX11AuthTransform(options = {}) {
let pending = Buffer.alloc(0);
let done = false;
return new Transform({
transform(chunk, _encoding, callback) {
if (done) {
callback(null, chunk);
return;
}
pending = Buffer.concat([pending, Buffer.from(chunk)]);
const result = rewriteX11AuthSetupPacket(pending, options);
if (!result.complete) {
callback();
return;
}
done = true;
callback(null, result.buffer);
},
flush(callback) {
if (!done && pending.length > 0) {
callback(null, pending);
return;
}
callback();
},
});
}
function resolveXauthCommand(platform) {
if (platform === "darwin" && fs.existsSync("/opt/X11/bin/xauth")) {
return "/opt/X11/bin/xauth";
}
return "xauth";
}
function getDisplayNumber(display) {
const value = String(display || process.env.DISPLAY || ":0").trim() || ":0";
const normalized = value === ":" ? ":0" : value;
const match = normalized.match(/:(\d+)(?:\.\d+)?$/) || normalized.match(/\/X(\d+)$/);
if (!match) return null;
const displayNumber = Number.parseInt(match[1], 10);
if (!Number.isFinite(displayNumber)) return null;
return displayNumber >= X11_PORT_BASE ? displayNumber - X11_PORT_BASE : displayNumber;
}
function parseXauthCookie(output, display) {
const requestedDisplay = getDisplayNumber(display);
const cookiePattern = new RegExp(`\\b${MIT_MAGIC_COOKIE_PROTOCOL}\\b\\s+([0-9a-fA-F]+)`);
for (const entry of String(output || "").split(/\r?\n/)) {
const match = entry.match(cookiePattern);
if (!match) continue;
const target = entry.trim().split(/\s+/, 1)[0];
if (requestedDisplay !== null && getDisplayNumber(target) !== requestedDisplay) {
continue;
}
return Buffer.from(match[1], "hex");
}
return null;
}
function readLocalX11AuthCookie(options = {}) {
const platform = options.platform || process.platform;
const command = options.xauthCommand || resolveXauthCommand(platform);
const display = String(options.display || process.env.DISPLAY || ":0").trim() || ":0";
try {
const normalizedDisplay = display === ":" ? ":0" : display;
const output = typeof options.readXauthOutput === "function"
? options.readXauthOutput({ command, display: normalizedDisplay })
: execFileSync(command, ["list"], {
encoding: "utf8",
env: {
...process.env,
DISPLAY: normalizedDisplay,
},
stdio: ["ignore", "pipe", "ignore"],
timeout: 2000,
});
return parseXauthCookie(output, normalizedDisplay);
} catch {
return null;
}
}
function attachX11Forwarding(conn, options = {}) {
const createSocket = options.createSocket || (() => new net.Socket());
const sendMessage = typeof options.sendMessage === "function" ? options.sendMessage : () => {};
const logger = options.logger || console;
const platform = options.platform || process.platform;
const display = options.display;
const fakeCookie = options.fakeCookie;
const fixedLocalAuthCookie = normalizeCookieBuffer(options.localAuthCookie);
let localAuthCookie = fixedLocalAuthCookie;
let localAuthCookieResolved = Boolean(fixedLocalAuthCookie);
const resolveLocalAuthCookie = () => {
if (localAuthCookieResolved) return localAuthCookie;
localAuthCookieResolved = true;
const cookie = typeof options.readLocalAuthCookie === "function"
? options.readLocalAuthCookie({ display, platform })
: readLocalX11AuthCookie({ display, platform });
localAuthCookie = normalizeCookieBuffer(cookie);
return localAuthCookie;
};
const onX11 = (info, accept, reject) => {
const target = resolveX11DisplaySpec(display, { platform });
const localSocket = createSocket();
let acceptedChannel = null;
let settled = false;
const cleanup = () => {
if (acceptedChannel) destroyStream(acceptedChannel);
destroyStream(localSocket);
};
localSocket.once("connect", () => {
if (settled) return;
try {
acceptedChannel = accept();
settled = true;
} catch (err) {
logger.warn?.("[X11] Failed to accept forwarded channel", err);
cleanup();
return;
}
acceptedChannel.on("error", () => cleanup());
localSocket.on("error", () => cleanup());
acceptedChannel.on("close", () => destroyStream(localSocket));
localSocket.on("close", () => destroyStream(acceptedChannel));
const realCookie = resolveLocalAuthCookie();
if (realCookie && fakeCookie) {
acceptedChannel
.pipe(createX11AuthTransform({ fakeCookie, realCookie }))
.pipe(localSocket)
.pipe(acceptedChannel);
} else {
acceptedChannel.pipe(localSocket).pipe(acceptedChannel);
}
});
localSocket.once("error", (err) => {
if (!settled) {
settled = true;
try { reject(); } catch { /* ignore reject errors */ }
sendMessage(`\r\n[X11] Could not connect to the local X11 server: ${err?.message || err}\r\n`);
sendMessage(`[X11] Display target: ${formatDisplayTarget(target)}\r\n`);
sendMessage(`[X11] ${platformHint(platform)}\r\n`);
}
destroyStream(localSocket);
});
try {
connectSocket(localSocket, target);
} catch (err) {
localSocket.emit("error", err);
}
};
conn.on("x11", onX11);
return () => {
if (typeof conn.off === "function") conn.off("x11", onX11);
else conn.removeListener("x11", onX11);
};
}
module.exports = {
attachX11Forwarding,
readLocalX11AuthCookie,
rewriteX11AuthSetupPacket,
resolveX11DisplaySpec,
};