UNPKG

brave-real-playwright-core

Version:

Brave-optimized Playwright Core (v1.55.0) with comprehensive stealth patches and error stack sanitization

512 lines (511 loc) 17.5 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); var socksProxy_exports = {}; __export(socksProxy_exports, { SocksProxy: () => SocksProxy, SocksProxyHandler: () => SocksProxyHandler, parsePattern: () => parsePattern }); module.exports = __toCommonJS(socksProxy_exports); var import_events = __toESM(require("events")); var import_net = __toESM(require("net")); var import_assert = require("../../utils/isomorphic/assert"); var import_crypto = require("./crypto"); var import_debugLogger = require("./debugLogger"); var import_happyEyeballs = require("./happyEyeballs"); var SocksAuth = /* @__PURE__ */ ((SocksAuth2) => { SocksAuth2[SocksAuth2["NO_AUTHENTICATION_REQUIRED"] = 0] = "NO_AUTHENTICATION_REQUIRED"; SocksAuth2[SocksAuth2["GSSAPI"] = 1] = "GSSAPI"; SocksAuth2[SocksAuth2["USERNAME_PASSWORD"] = 2] = "USERNAME_PASSWORD"; SocksAuth2[SocksAuth2["NO_ACCEPTABLE_METHODS"] = 255] = "NO_ACCEPTABLE_METHODS"; return SocksAuth2; })(SocksAuth || {}); var SocksAddressType = /* @__PURE__ */ ((SocksAddressType2) => { SocksAddressType2[SocksAddressType2["IPv4"] = 1] = "IPv4"; SocksAddressType2[SocksAddressType2["FqName"] = 3] = "FqName"; SocksAddressType2[SocksAddressType2["IPv6"] = 4] = "IPv6"; return SocksAddressType2; })(SocksAddressType || {}); var SocksCommand = /* @__PURE__ */ ((SocksCommand2) => { SocksCommand2[SocksCommand2["CONNECT"] = 1] = "CONNECT"; SocksCommand2[SocksCommand2["BIND"] = 2] = "BIND"; SocksCommand2[SocksCommand2["UDP_ASSOCIATE"] = 3] = "UDP_ASSOCIATE"; return SocksCommand2; })(SocksCommand || {}); var SocksReply = /* @__PURE__ */ ((SocksReply2) => { SocksReply2[SocksReply2["Succeeded"] = 0] = "Succeeded"; SocksReply2[SocksReply2["GeneralServerFailure"] = 1] = "GeneralServerFailure"; SocksReply2[SocksReply2["NotAllowedByRuleSet"] = 2] = "NotAllowedByRuleSet"; SocksReply2[SocksReply2["NetworkUnreachable"] = 3] = "NetworkUnreachable"; SocksReply2[SocksReply2["HostUnreachable"] = 4] = "HostUnreachable"; SocksReply2[SocksReply2["ConnectionRefused"] = 5] = "ConnectionRefused"; SocksReply2[SocksReply2["TtlExpired"] = 6] = "TtlExpired"; SocksReply2[SocksReply2["CommandNotSupported"] = 7] = "CommandNotSupported"; SocksReply2[SocksReply2["AddressTypeNotSupported"] = 8] = "AddressTypeNotSupported"; return SocksReply2; })(SocksReply || {}); class SocksConnection { constructor(uid, socket, client) { this._buffer = Buffer.from([]); this._offset = 0; this._fence = 0; this._uid = uid; this._socket = socket; this._client = client; this._boundOnData = this._onData.bind(this); socket.on("data", this._boundOnData); socket.on("close", () => this._onClose()); socket.on("end", () => this._onClose()); socket.on("error", () => this._onClose()); this._run().catch(() => this._socket.end()); } async _run() { (0, import_assert.assert)(await this._authenticate()); const { command, host, port } = await this._parseRequest(); if (command !== 1 /* CONNECT */) { this._writeBytes(Buffer.from([ 5, 7 /* CommandNotSupported */, 0, // RSV 1, // IPv4 0, 0, 0, 0, // Address 0, 0 // Port ])); return; } this._socket.off("data", this._boundOnData); this._client.onSocketRequested({ uid: this._uid, host, port }); } async _authenticate() { const version = await this._readByte(); (0, import_assert.assert)(version === 5, "The VER field must be set to x05 for this version of the protocol, was " + version); const nMethods = await this._readByte(); (0, import_assert.assert)(nMethods, "No authentication methods specified"); const methods = await this._readBytes(nMethods); for (const method of methods) { if (method === 0) { this._writeBytes(Buffer.from([version, method])); return true; } } this._writeBytes(Buffer.from([version, 255 /* NO_ACCEPTABLE_METHODS */])); return false; } async _parseRequest() { const version = await this._readByte(); (0, import_assert.assert)(version === 5, "The VER field must be set to x05 for this version of the protocol, was " + version); const command = await this._readByte(); await this._readByte(); const addressType = await this._readByte(); let host = ""; switch (addressType) { case 1 /* IPv4 */: host = (await this._readBytes(4)).join("."); break; case 3 /* FqName */: const length = await this._readByte(); host = (await this._readBytes(length)).toString(); break; case 4 /* IPv6 */: const bytes = await this._readBytes(16); const tokens = []; for (let i = 0; i < 8; ++i) tokens.push(bytes.readUInt16BE(i * 2).toString(16)); host = tokens.join(":"); break; } const port = (await this._readBytes(2)).readUInt16BE(0); this._buffer = Buffer.from([]); this._offset = 0; this._fence = 0; return { command, host, port }; } async _readByte() { const buffer = await this._readBytes(1); return buffer[0]; } async _readBytes(length) { this._fence = this._offset + length; if (!this._buffer || this._buffer.length < this._fence) await new Promise((f) => this._fenceCallback = f); this._offset += length; return this._buffer.slice(this._offset - length, this._offset); } _writeBytes(buffer) { if (this._socket.writable) this._socket.write(buffer); } _onClose() { this._client.onSocketClosed({ uid: this._uid }); } _onData(buffer) { this._buffer = Buffer.concat([this._buffer, buffer]); if (this._fenceCallback && this._buffer.length >= this._fence) { const callback = this._fenceCallback; this._fenceCallback = void 0; callback(); } } socketConnected(host, port) { this._writeBytes(Buffer.from([ 5, 0 /* Succeeded */, 0, // RSV ...ipToSocksAddress(host), // ATYP, Address port >> 8, port & 255 // Port ])); this._socket.on("data", (data) => this._client.onSocketData({ uid: this._uid, data })); } socketFailed(errorCode) { const buffer = Buffer.from([ 5, 0, 0, // RSV ...ipToSocksAddress("0.0.0.0"), // ATYP, Address 0, 0 // Port ]); switch (errorCode) { case "ENOENT": case "ENOTFOUND": case "ETIMEDOUT": case "EHOSTUNREACH": buffer[1] = 4 /* HostUnreachable */; break; case "ENETUNREACH": buffer[1] = 3 /* NetworkUnreachable */; break; case "ECONNREFUSED": buffer[1] = 5 /* ConnectionRefused */; break; case "ERULESET": buffer[1] = 2 /* NotAllowedByRuleSet */; break; } this._writeBytes(buffer); this._socket.end(); } sendData(data) { this._socket.write(data); } end() { this._socket.end(); } error(error) { this._socket.destroy(new Error(error)); } } function hexToNumber(hex) { return [...hex].reduce((value, digit) => { const code = digit.charCodeAt(0); if (code >= 48 && code <= 57) return value + code; if (code >= 97 && code <= 102) return value + (code - 97) + 10; if (code >= 65 && code <= 70) return value + (code - 65) + 10; throw new Error("Invalid IPv6 token " + hex); }, 0); } function ipToSocksAddress(address) { if (import_net.default.isIPv4(address)) { return [ 1, // IPv4 ...address.split(".", 4).map((t) => +t & 255) // Address ]; } if (import_net.default.isIPv6(address)) { const result = [4]; const tokens = address.split(":", 8); while (tokens.length < 8) tokens.unshift(""); for (const token of tokens) { const value = hexToNumber(token); result.push(value >> 8 & 255, value & 255); } return result; } throw new Error("Only IPv4 and IPv6 addresses are supported"); } function starMatchToRegex(pattern) { const source = pattern.split("*").map((s) => { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }).join(".*"); return new RegExp("^" + source + "$"); } function parsePattern(pattern) { if (!pattern) return () => false; const matchers = pattern.split(",").map((token) => { const match = token.match(/^(.*?)(?::(\d+))?$/); if (!match) throw new Error(`Unsupported token "${token}" in pattern "${pattern}"`); const tokenPort = match[2] ? +match[2] : void 0; const portMatches = (port) => tokenPort === void 0 || tokenPort === port; let tokenHost = match[1]; if (tokenHost === "<loopback>") { return (host, port) => { if (!portMatches(port)) return false; return host === "localhost" || host.endsWith(".localhost") || host === "127.0.0.1" || host === "[::1]"; }; } if (tokenHost === "*") return (host, port) => portMatches(port); if (import_net.default.isIPv4(tokenHost) || import_net.default.isIPv6(tokenHost)) return (host, port) => host === tokenHost && portMatches(port); if (tokenHost[0] === ".") tokenHost = "*" + tokenHost; const tokenRegex = starMatchToRegex(tokenHost); return (host, port) => { if (!portMatches(port)) return false; if (import_net.default.isIPv4(host) || import_net.default.isIPv6(host)) return false; return !!host.match(tokenRegex); }; }); return (host, port) => matchers.some((matcher) => matcher(host, port)); } class SocksProxy extends import_events.default { constructor() { super(); this._connections = /* @__PURE__ */ new Map(); this._sockets = /* @__PURE__ */ new Set(); this._closed = false; this._patternMatcher = () => false; this._directSockets = /* @__PURE__ */ new Map(); this._server = new import_net.default.Server((socket) => { const uid = (0, import_crypto.createGuid)(); const connection = new SocksConnection(uid, socket, this); this._connections.set(uid, connection); }); this._server.on("connection", (socket) => { if (this._closed) { socket.destroy(); return; } this._sockets.add(socket); socket.once("close", () => this._sockets.delete(socket)); }); } static { this.Events = { SocksRequested: "socksRequested", SocksData: "socksData", SocksClosed: "socksClosed" }; } setPattern(pattern) { try { this._patternMatcher = parsePattern(pattern); } catch (e) { this._patternMatcher = () => false; } } async _handleDirect(request) { try { const socket = await (0, import_happyEyeballs.createSocket)(request.host, request.port); socket.on("data", (data) => this._connections.get(request.uid)?.sendData(data)); socket.on("error", (error) => { this._connections.get(request.uid)?.error(error.message); this._directSockets.delete(request.uid); }); socket.on("end", () => { this._connections.get(request.uid)?.end(); this._directSockets.delete(request.uid); }); const localAddress = socket.localAddress; const localPort = socket.localPort; this._directSockets.set(request.uid, socket); this._connections.get(request.uid)?.socketConnected(localAddress, localPort); } catch (error) { this._connections.get(request.uid)?.socketFailed(error.code); } } port() { return this._port; } async listen(port, hostname) { return new Promise((f) => { this._server.listen(port, hostname, () => { const port2 = this._server.address().port; this._port = port2; f(port2); }); }); } async close() { if (this._closed) return; this._closed = true; for (const socket of this._sockets) socket.destroy(); this._sockets.clear(); await new Promise((f) => this._server.close(f)); } onSocketRequested(payload) { if (!this._patternMatcher(payload.host, payload.port)) { this._handleDirect(payload); return; } this.emit(SocksProxy.Events.SocksRequested, payload); } onSocketData(payload) { const direct = this._directSockets.get(payload.uid); if (direct) { direct.write(payload.data); return; } this.emit(SocksProxy.Events.SocksData, payload); } onSocketClosed(payload) { const direct = this._directSockets.get(payload.uid); if (direct) { direct.destroy(); this._directSockets.delete(payload.uid); return; } this.emit(SocksProxy.Events.SocksClosed, payload); } socketConnected({ uid, host, port }) { this._connections.get(uid)?.socketConnected(host, port); } socketFailed({ uid, errorCode }) { this._connections.get(uid)?.socketFailed(errorCode); } sendSocketData({ uid, data }) { this._connections.get(uid)?.sendData(data); } sendSocketEnd({ uid }) { this._connections.get(uid)?.end(); } sendSocketError({ uid, error }) { this._connections.get(uid)?.error(error); } } class SocksProxyHandler extends import_events.default { constructor(pattern, redirectPortForTest) { super(); this._sockets = /* @__PURE__ */ new Map(); this._patternMatcher = () => false; this._patternMatcher = parsePattern(pattern); this._redirectPortForTest = redirectPortForTest; } static { this.Events = { SocksConnected: "socksConnected", SocksData: "socksData", SocksError: "socksError", SocksFailed: "socksFailed", SocksEnd: "socksEnd" }; } cleanup() { for (const uid of this._sockets.keys()) this.socketClosed({ uid }); } async socketRequested({ uid, host, port }) { import_debugLogger.debugLogger.log("socks", `[${uid}] => request ${host}:${port}`); if (!this._patternMatcher(host, port)) { const payload = { uid, errorCode: "ERULESET" }; import_debugLogger.debugLogger.log("socks", `[${uid}] <= pattern error ${payload.errorCode}`); this.emit(SocksProxyHandler.Events.SocksFailed, payload); return; } if (host === "local.playwright") host = "localhost"; try { if (this._redirectPortForTest) port = this._redirectPortForTest; const socket = await (0, import_happyEyeballs.createSocket)(host, port); socket.on("data", (data) => { const payload2 = { uid, data }; this.emit(SocksProxyHandler.Events.SocksData, payload2); }); socket.on("error", (error) => { const payload2 = { uid, error: error.message }; import_debugLogger.debugLogger.log("socks", `[${uid}] <= network socket error ${payload2.error}`); this.emit(SocksProxyHandler.Events.SocksError, payload2); this._sockets.delete(uid); }); socket.on("end", () => { const payload2 = { uid }; import_debugLogger.debugLogger.log("socks", `[${uid}] <= network socket closed`); this.emit(SocksProxyHandler.Events.SocksEnd, payload2); this._sockets.delete(uid); }); const localAddress = socket.localAddress; const localPort = socket.localPort; this._sockets.set(uid, socket); const payload = { uid, host: localAddress, port: localPort }; import_debugLogger.debugLogger.log("socks", `[${uid}] <= connected to network ${payload.host}:${payload.port}`); this.emit(SocksProxyHandler.Events.SocksConnected, payload); } catch (error) { const payload = { uid, errorCode: error.code }; import_debugLogger.debugLogger.log("socks", `[${uid}] <= connect error ${payload.errorCode}`); this.emit(SocksProxyHandler.Events.SocksFailed, payload); } } sendSocketData({ uid, data }) { this._sockets.get(uid)?.write(data); } socketClosed({ uid }) { import_debugLogger.debugLogger.log("socks", `[${uid}] <= browser socket closed`); this._sockets.get(uid)?.destroy(); this._sockets.delete(uid); } } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { SocksProxy, SocksProxyHandler, parsePattern });