UNPKG

sussudio

Version:

An unofficial VS Code Internal API

238 lines (237 loc) 11.5 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; import * as net from 'net'; import * as os from 'os'; import { BROWSER_RESTRICTED_PORTS, findFreePortFaster } from "../../../base/node/ports.mjs"; import { nodeSocketFactory } from "../../remote/node/nodeSocketFactory.mjs"; import { Barrier } from "../../../base/common/async.mjs"; import { Disposable } from "../../../base/common/lifecycle.mjs"; import { IConfigurationService } from "../../configuration/common/configuration.mjs"; import { ILogService } from "../../log/common/log.mjs"; import { IProductService } from "../../product/common/productService.mjs"; import { connectRemoteAgentTunnel } from "../../remote/common/remoteAgentConnection.mjs"; import { AbstractTunnelService, isAllInterfaces, isLocalhost, isPortPrivileged, TunnelPrivacyId } from "../common/tunnel.mjs"; import { ISignService } from "../../sign/common/sign.mjs"; import { OS } from "../../../base/common/platform.mjs"; async function createRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort) { let readyTunnel; for (let attempts = 3; attempts; attempts--) { readyTunnel?.dispose(); const tunnel = new NodeRemoteTunnel(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, tunnelLocalPort); readyTunnel = await tunnel.waitForReady(); if ((tunnelLocalPort && BROWSER_RESTRICTED_PORTS[tunnelLocalPort]) || !BROWSER_RESTRICTED_PORTS[readyTunnel.tunnelLocalPort]) { break; } } return readyTunnel; } class NodeRemoteTunnel extends Disposable { defaultTunnelHost; suggestedLocalPort; tunnelRemotePort; tunnelLocalPort; tunnelRemoteHost; localAddress; privacy = TunnelPrivacyId.Private; _options; _server; _barrier; _listeningListener; _connectionListener; _errorListener; _socketsDispose = new Map(); constructor(options, defaultTunnelHost, tunnelRemoteHost, tunnelRemotePort, suggestedLocalPort) { super(); this.defaultTunnelHost = defaultTunnelHost; this.suggestedLocalPort = suggestedLocalPort; this._options = options; this._server = net.createServer(); this._barrier = new Barrier(); this._listeningListener = () => this._barrier.open(); this._server.on('listening', this._listeningListener); this._connectionListener = (socket) => this._onConnection(socket); this._server.on('connection', this._connectionListener); // If there is no error listener and there is an error it will crash the whole window this._errorListener = () => { }; this._server.on('error', this._errorListener); this.tunnelRemotePort = tunnelRemotePort; this.tunnelRemoteHost = tunnelRemoteHost; } async dispose() { super.dispose(); this._server.removeListener('listening', this._listeningListener); this._server.removeListener('connection', this._connectionListener); this._server.removeListener('error', this._errorListener); this._server.close(); const disposers = Array.from(this._socketsDispose.values()); disposers.forEach(disposer => { disposer(); }); } async waitForReady() { const startPort = this.suggestedLocalPort ?? this.tunnelRemotePort; const hostname = isAllInterfaces(this.defaultTunnelHost) ? '0.0.0.0' : '127.0.0.1'; // try to get the same port number as the remote port number... let localPort = await findFreePortFaster(startPort, 2, 1000, hostname); // if that fails, the method above returns 0, which works out fine below... let address = null; this._server.listen(localPort, this.defaultTunnelHost); await this._barrier.wait(); address = this._server.address(); // It is possible for findFreePortFaster to return a port that there is already a server listening on. This causes the previous listen call to error out. if (!address) { localPort = 0; this._server.listen(localPort, this.defaultTunnelHost); await this._barrier.wait(); address = this._server.address(); } this.tunnelLocalPort = address.port; this.localAddress = `${this.tunnelRemoteHost === '127.0.0.1' ? '127.0.0.1' : 'localhost'}:${address.port}`; return this; } async _onConnection(localSocket) { // pause reading on the socket until we have a chance to forward its data localSocket.pause(); const tunnelRemoteHost = (isLocalhost(this.tunnelRemoteHost) || isAllInterfaces(this.tunnelRemoteHost)) ? 'localhost' : this.tunnelRemoteHost; const protocol = await connectRemoteAgentTunnel(this._options, tunnelRemoteHost, this.tunnelRemotePort); const remoteSocket = protocol.getSocket().socket; const dataChunk = protocol.readEntireBuffer(); protocol.dispose(); if (dataChunk.byteLength > 0) { localSocket.write(dataChunk.buffer); } localSocket.on('end', () => { if (localSocket.localAddress) { this._socketsDispose.delete(localSocket.localAddress); } remoteSocket.end(); }); localSocket.on('close', () => remoteSocket.end()); localSocket.on('error', () => { if (localSocket.localAddress) { this._socketsDispose.delete(localSocket.localAddress); } remoteSocket.destroy(); }); remoteSocket.on('end', () => localSocket.end()); remoteSocket.on('close', () => localSocket.end()); remoteSocket.on('error', () => { localSocket.destroy(); }); localSocket.pipe(remoteSocket); remoteSocket.pipe(localSocket); if (localSocket.localAddress) { this._socketsDispose.set(localSocket.localAddress, () => { // Need to end instead of unpipe, otherwise whatever is connected locally could end up "stuck" with whatever state it had until manually exited. localSocket.end(); remoteSocket.end(); }); } } } let BaseTunnelService = class BaseTunnelService extends AbstractTunnelService { socketFactory; signService; productService; constructor(socketFactory, logService, signService, productService, configurationService) { super(logService, configurationService); this.socketFactory = socketFactory; this.signService = signService; this.productService = productService; } isPortPrivileged(port) { return isPortPrivileged(port, this.defaultTunnelHost, OS, os.release()); } retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol) { const existing = this.getTunnelFromMap(remoteHost, remotePort); if (existing) { ++existing.refcount; return existing.value; } if (this._tunnelProvider) { return this.createWithProvider(this._tunnelProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); } else { this.logService.trace(`ForwardedPorts: (TunnelService) Creating tunnel without provider ${remoteHost}:${remotePort} on local port ${localPort}.`); const options = { commit: this.productService.commit, quality: this.productService.quality, socketFactory: this.socketFactory, addressProvider, signService: this.signService, logService: this.logService, ipcLogger: null }; const tunnel = createRemoteTunnel(options, this.defaultTunnelHost, remoteHost, remotePort, localPort); this.logService.trace('ForwardedPorts: (TunnelService) Tunnel created without provider.'); this.addTunnelToMap(remoteHost, remotePort, tunnel); return tunnel; } } }; BaseTunnelService = __decorate([ __param(1, ILogService), __param(2, ISignService), __param(3, IProductService), __param(4, IConfigurationService) ], BaseTunnelService); export { BaseTunnelService }; let TunnelService = class TunnelService extends BaseTunnelService { constructor(logService, signService, productService, configurationService) { super(nodeSocketFactory, logService, signService, productService, configurationService); } }; TunnelService = __decorate([ __param(0, ILogService), __param(1, ISignService), __param(2, IProductService), __param(3, IConfigurationService) ], TunnelService); export { TunnelService }; let SharedTunnelsService = class SharedTunnelsService extends Disposable { logService; productService; signService; configurationService; _tunnelServices = new Map(); constructor(logService, productService, signService, configurationService) { super(); this.logService = logService; this.productService = productService; this.signService = signService; this.configurationService = configurationService; } async openTunnel(authority, addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol) { this.logService.trace(`ForwardedPorts: (SharedTunnelService) openTunnel request for ${remoteHost}:${remotePort} on local port ${localPort}.`); if (!this._tunnelServices.has(authority)) { const tunnelService = new TunnelService(this.logService, this.signService, this.productService, this.configurationService); this._register(tunnelService); this._tunnelServices.set(authority, tunnelService); tunnelService.onTunnelClosed(async () => { if ((await tunnelService.tunnels).length === 0) { tunnelService.dispose(); this._tunnelServices.delete(authority); } }); } return this._tunnelServices.get(authority).openTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, privacy, protocol); } }; SharedTunnelsService = __decorate([ __param(0, ILogService), __param(1, IProductService), __param(2, ISignService), __param(3, IConfigurationService) ], SharedTunnelsService); export { SharedTunnelsService };