diff --git a/scripts/nbin-shim.js b/scripts/nbin-shim.js index 99f788ad..de2ffe38 100644 --- a/scripts/nbin-shim.js +++ b/scripts/nbin-shim.js @@ -1,18 +1,14 @@ -/* global require, global, process, __dirname */ if (!global.NBIN_LOADED) { try { const nbin = require("nbin"); nbin.shimNativeFs("{{ROOT_PATH}}"); global.NBIN_LOADED = true; - const path = require("path"); const rg = require("vscode-ripgrep"); rg.binaryRgPath = rg.rgPath; rg.rgPath = path.join( require("os").tmpdir(), - `code-server/${path.basename(rg.binaryRgPath)}`, + `code-server/${path.basename(rg.binaryRgPath)}` ); - } catch (error) { - // Not in the binary. - } + } catch (error) { /* Not in the binary. */ } } diff --git a/src/api.ts b/src/api.ts index 47307d61..129a3ffa 100644 --- a/src/api.ts +++ b/src/api.ts @@ -60,7 +60,6 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode = FileSystemError: extHostTypes.FileSystemError, FileType: FileType, Uri: URI, - commands: { executeCommand: (commandId: string, ...args: any[]): any => { return commandService.executeCommand(commandId, ...args); @@ -69,7 +68,6 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode = return CommandsRegistry.registerCommand(id, command); }, }, - window: { registerTreeDataProvider: (id: string, dataProvider: ITreeViewDataProvider): void => { const view = viewsRegistry.getView(id); @@ -81,7 +79,6 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode = notificationService.error(message); }, }, - workspace: { registerFileSystemProvider: (scheme: string, provider: vscode.FileSystemProvider): IDisposable => { return fileService.registerProvider(scheme, new FileSystemProvider(provider)); @@ -95,7 +92,6 @@ export const vscodeApi = (serviceCollection: ServiceCollection): typeof vscode = */ export const coderApi = (serviceCollection: ServiceCollection): typeof coder => { const getService = (id: ServiceIdentifier): T => serviceCollection.get(id) as T; - return { workbench: { action: Action, @@ -103,13 +99,8 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder => commandRegistry: CommandsRegistry, actionsRegistry: Registry.as(ActionExtensions.WorkbenchActions), registerView: (viewId, viewName, containerId, containerName, icon): void => { - const viewContainersRegistry = Registry.as(ViewsExtensions.ViewContainersRegistry); - const viewsRegistry = Registry.as(ViewsExtensions.ViewsRegistry); - const container = viewContainersRegistry.registerViewContainer(containerId); - const cssClass = `extensionViewlet-${containerId}`; const id = `workbench.view.extension.${containerId}`; - class CustomViewlet extends ViewContainerViewlet { public constructor( @IConfigurationService configurationService: IConfigurationService, @@ -127,44 +118,32 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder => } } - const viewletDescriptor = new ViewletDescriptor( - CustomViewlet as any, - id, - containerName, - cssClass, - undefined, - URI.parse(icon), + Registry.as(ViewletExtensions.Viewlets).registerViewlet( + new ViewletDescriptor(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)), ); - Registry.as(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor); - - const registry = Registry.as(ActionExtensions.WorkbenchActions); - registry.registerWorkbenchAction( + Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction( new SyncActionDescriptor(OpenCustomViewletAction as any, id, localize("showViewlet", "Show {0}", containerName)), "View: Show {0}", localize("view", "View"), ); - // Generate CSS to show the icon in the activity bar + // Generate CSS to show the icon in the activity bar. const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${cssClass}`; createCSSRule(iconClass, `-webkit-mask: url('${icon}') no-repeat 50% 50%`); - const views = [{ + const container = Registry.as(ViewsExtensions.ViewContainersRegistry).registerViewContainer(containerId); + Registry.as(ViewsExtensions.ViewsRegistry).registerViews([{ id: viewId, name: viewName, ctorDescriptor: { ctor: CustomTreeViewPanel }, treeView: getService(IInstantiationService).createInstance(CustomTreeView as any, viewId, container), - }] as ITreeViewDescriptor[]; - viewsRegistry.registerViews(views, container); + }] as ITreeViewDescriptor[], container); }, - // Even though the enums are exactly the same, Typescript says they are - // not assignable to each other, so use `any`. I don't know if there is a - // way around this. menuRegistry: MenuRegistry as any, statusbarService: getService(IStatusbarService) as any, notificationService: getService(INotificationService), terminalService: getService(ITerminalService), - onFileCreate: (cb): void => { getService(IFileService).onAfterOperation((e) => { if (e.operation === FileOperation.CREATE) { @@ -198,7 +177,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder => } }); }, - onModelAdded: (cb): void => { getService(IModelService).onModelAdded((e) => { cb(e.uri.path, e.getLanguageIdentifier().language); @@ -214,7 +192,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder => cb(e.model.uri.path, e.model.getLanguageIdentifier().language, e.oldModeId); }); }, - onTerminalAdded: (cb): void => { getService(ITerminalService).onInstanceCreated(() => cb()); }, @@ -222,7 +199,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder => getService(ITerminalService).onInstanceDisposed(() => cb()); }, }, - // @ts-ignore MenuId: MenuId, Severity: Severity, @@ -250,9 +226,7 @@ class FileSystemProvider implements IFileSystemProvider { public readonly capabilities: FileSystemProviderCapabilities; public readonly onDidChangeCapabilities: Event = Event.None; - public constructor( - private readonly provider: vscode.FileSystemProvider, - ) { + public constructor(private readonly provider: vscode.FileSystemProvider) { this.capabilities = FileSystemProviderCapabilities.Readonly; } diff --git a/src/channel.ts b/src/channel.ts index fbe61b3a..35a4ca4e 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -43,9 +43,6 @@ class Watcher extends DiskFileSystemProvider { } } -/** - * See: src/vs/platform/remote/common/remoteAgentFileSystemChannel.ts. - */ export class FileProviderChannel implements IServerChannel, IDisposable { private readonly provider: DiskFileSystemProvider; private readonly watchers = new Map(); @@ -175,9 +172,6 @@ export class FileProviderChannel implements IServerChannel, IDisposable { } } -/** - * See: src/vs/workbench/services/remote/common/remoteAgentEnvironmentChannel.ts. - */ export class ExtensionEnvironmentChannel implements IServerChannel { public constructor( private readonly environment: IEnvironmentService, @@ -245,7 +239,6 @@ export class ExtensionEnvironmentChannel implements IServerChannel { }; return Promise.all([scanBuiltin(), scanInstalled()]).then((allExtensions) => { - // It's possible to get duplicates. const uniqueExtensions = new Map(); allExtensions.forEach((multipleExtensions) => { multipleExtensions.forEach((extensions) => { @@ -254,18 +247,13 @@ export class ExtensionEnvironmentChannel implements IServerChannel { if (uniqueExtensions.has(id)) { const oldPath = uniqueExtensions.get(id)!.extensionLocation.fsPath; const newPath = extension.extensionLocation.fsPath; - this.log.warn( - `Extension ${id} in ${oldPath} has been overridden ${newPath}`, - ); + this.log.warn(`${oldPath} has been overridden ${newPath}`); } uniqueExtensions.set(id, extension); }); }); }); - - const finalExtensions = []; - uniqueExtensions.forEach((e) => finalExtensions.push(e)); - return finalExtensions; + return Array.from(uniqueExtensions.values()); }); } diff --git a/src/cli.ts b/src/cli.ts index 412cb0fc..8e0dcc59 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -92,12 +92,7 @@ const main = async (): Promise => { const version = `${(pkg as any).codeServerVersion || "development"}-vsc${pkg.version}`; if (args.help) { const executable = `${product.applicationName}${os.platform() === "win32" ? ".exe" : ""}`; - return console.log(buildHelpMessage( - product.nameLong, executable, - version, - undefined, - false, - )); + return console.log(buildHelpMessage(product.nameLong, executable, version, undefined, false)); } if (args.version) { @@ -116,26 +111,22 @@ const main = async (): Promise => { if (shouldSpawnCliProcess()) { const cli = await new Promise((c, e) => require(["vs/code/node/cliProcessMain"], c, e)); await cli.main(args); - // There is some WriteStream instance keeping it open so force an exit. - return process.exit(0); + return process.exit(0); // There is a WriteStream instance keeping it open. } + const extra = args["_"] || []; const options = { - host: args.host, allowHttp: args["allow-http"], + auth: typeof args.auth !== "undefined" ? args.auth : true, cert: args.cert, certKey: args["cert-key"], - auth: typeof args.auth !== "undefined" ? args.auth : true, + folderUri: extra.length > 1 ? extra[extra.length - 1] : undefined, + host: args.host, password: process.env.PASSWORD, - folderUri: args["_"] && args["_"].length > 1 - ? args["_"][args["_"].length - 1] - : undefined, }; if (!options.host) { - options.host = !options.auth || options.allowHttp - ? "localhost" - : "0.0.0.0"; + options.host = !options.auth || options.allowHttp ? "localhost" : "0.0.0.0"; } let usingGeneratedCert = false; @@ -152,18 +143,16 @@ const main = async (): Promise => { usingGeneratedPassword = true; } - const webviewPort = typeof args["webview-port"] !== "undefined" - && parseInt(args["webview-port"], 10) || 8444; + const webviewPort = args["webview-port"]; const webviewServer = new WebviewServer({ ...options, - port: webviewPort, + port: typeof webviewPort !== "undefined" && parseInt(webviewPort, 10) || 8444, socket: args["webview-socket"], }); - const port = typeof args.port !== "undefined" && parseInt(args.port, 10) || 8443; const server = new MainServer({ ...options, - port, + port: typeof args.port !== "undefined" && parseInt(args.port, 10) || 8443, socket: args.socket, }, webviewServer, args); @@ -196,7 +185,7 @@ const main = async (): Promise => { if (!args.socket && args.open) { // The web socket doesn't seem to work if using 0.0.0.0. - const openAddress = `http://localhost:${port}`; + const openAddress = `http://localhost:${server.options.port}`; await open(openAddress).catch(console.error); console.log(` - Opened ${openAddress}`); } diff --git a/src/connection.ts b/src/connection.ts index 0a39202d..60898ef2 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -6,7 +6,7 @@ import { Emitter } from "vs/base/common/event"; import { ISocket } from "vs/base/parts/ipc/common/ipc.net"; import { NodeSocket } from "vs/base/parts/ipc/node/ipc.net"; import { ILogService } from "vs/platform/log/common/log"; -import { IExtHostReadyMessage, IExtHostSocketMessage } from "vs/workbench/services/extensions/common/extensionHostProtocol"; +import { IExtHostReadyMessage } from "vs/workbench/services/extensions/common/extensionHostProtocol"; import { Protocol } from "vs/server/src/protocol"; import { uriTransformerPath } from "vs/server/src/util"; @@ -15,17 +15,11 @@ export abstract class Connection { protected readonly _onClose = new Emitter(); public readonly onClose = this._onClose.event; protected disposed: boolean = false; - public constructor(protected protocol: Protocol) {} - /** * Set up the connection on a new socket. */ public abstract reconnect(socket: ISocket, buffer: VSBuffer): void; - - /** - * Clean up the connection. - */ protected abstract dispose(): void; } @@ -62,16 +56,10 @@ export class ManagementConnection extends Connection { } } -/** - * Manage the extension host process. - */ export class ExtensionHostConnection extends Connection { private process: cp.ChildProcess; - public constructor( - protocol: Protocol, buffer: VSBuffer, - private readonly log: ILogService, - ) { + public constructor(protocol: Protocol, buffer: VSBuffer, private readonly log: ILogService) { super(protocol); protocol.dispose(); this.process = this.spawn(buffer); @@ -96,23 +84,17 @@ export class ExtensionHostConnection extends Connection { private sendInitMessage(buffer: VSBuffer): void { const socket = this.protocol.getUnderlyingSocket(); socket.pause(); - - const initMessage: IExtHostSocketMessage = { + this.process.send({ type: "VSCODE_EXTHOST_IPC_SOCKET", initialDataChunk: (buffer.buffer as Buffer).toString("base64"), skipWebSocketFrames: this.protocol.getSocket() instanceof NodeSocket, - }; - - this.process.send(initMessage, socket); + }, socket); } private spawn(buffer: VSBuffer): cp.ChildProcess { const proc = cp.fork( getPathFromAmdModule(require, "bootstrap-fork"), - [ - "--type=extensionHost", - `--uriTransformerPath=${uriTransformerPath()}` - ], + [ "--type=extensionHost", `--uriTransformerPath=${uriTransformerPath()}` ], { env: { ...process.env, @@ -129,13 +111,8 @@ export class ExtensionHostConnection extends Connection { proc.on("error", () => this.dispose()); proc.on("exit", () => this.dispose()); - - proc.stdout.setEncoding("utf8"); - proc.stderr.setEncoding("utf8"); - - proc.stdout.on("data", (d) => this.log.info("Extension host stdout", d)); - proc.stderr.on("data", (d) => this.log.error("Extension host stderr", d)); - + proc.stdout.setEncoding("utf8").on("data", (d) => this.log.info("Extension host stdout", d)); + proc.stderr.setEncoding("utf8").on("data", (d) => this.log.error("Extension host stderr", d)); proc.on("message", (event) => { if (event && event.type === "__$console") { const severity = this.log[event.severity] ? event.severity : "info"; @@ -149,8 +126,7 @@ export class ExtensionHostConnection extends Connection { this.sendInitMessage(buffer); } }; - proc.on("message", listen); - return proc; + return proc.on("message", listen); } } diff --git a/src/protocol.ts b/src/protocol.ts index 1ecbaed8..8515b2bc 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -73,10 +73,7 @@ export class Protocol extends PersistentProtocol { * TODO: This ignores the authentication process entirely for now. */ private authenticate(_message: AuthRequest): void { - this.sendMessage({ - type: "sign", - data: "", - }); + this.sendMessage({ type: "sign", data: "" }); } /** diff --git a/src/server.ts b/src/server.ts index 15fd4422..dc8df77c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -108,14 +108,11 @@ export interface ServerOptions { } export abstract class Server { - // The underlying web server. protected readonly server: http.Server | https.Server; - protected rootPath = path.resolve(__dirname, "../../../.."); - private listenPromise: Promise | undefined; - public constructor(protected readonly options: ServerOptions) { + public constructor(public readonly options: ServerOptions) { if (this.options.cert && this.options.certKey) { useHttpsTransformer(); const httpolyglot = require.__$__nodeRequire(path.resolve(__dirname, "../node_modules/httpolyglot/lib/index")) as typeof import("httpolyglot"); @@ -167,8 +164,7 @@ export abstract class Server { ): Promise; protected async getResource(filePath: string): Promise { - const content = await util.promisify(fs.readFile)(filePath); - return { content, filePath }; + return { content: await util.promisify(fs.readFile)(filePath), filePath }; } private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise => { @@ -208,12 +204,10 @@ export abstract class Server { } else if (base === "") { // Happens if it's a plain `domain.com`. base = "/"; } - if (requestPath === "/") { // Trailing slash, like `domain.com/login/`. - requestPath = ""; - } else if (requestPath !== "") { // "" will become "." with normalize. + base = path.normalize(base); + if (requestPath !== "") { // "" will become "." with normalize. requestPath = path.normalize(requestPath); } - base = path.normalize(base); switch (base) { case "/": @@ -227,8 +221,7 @@ export abstract class Server { case "/login": if (!this.options.auth) { throw new HttpError("Not found", HttpCode.NotFound); - } - if (requestPath === "") { + } else if (requestPath === "") { return this.tryLogin(request); } this.ensureGet(request); @@ -249,27 +242,19 @@ export abstract class Server { this.ensureGet(request); return { redirect: "https://" + request.headers.host + "/" }; } - if (request.method === "POST") { const data = await this.getData(request); if (this.authenticate(request, data)) { return { redirect: "https://" + request.headers.host + "/", - headers: { - "Set-Cookie": `password=${data.password}`, - } + headers: {"Set-Cookie": `password=${data.password}` } }; } - let userAgent = request.headers["user-agent"]; - const timestamp = Math.floor(new Date().getTime() / 1000); - if (Array.isArray(userAgent)) { - userAgent = userAgent.join(", "); - } console.error("Failed login attempt", JSON.stringify({ xForwardedFor: request.headers["x-forwarded-for"], remoteAddress: request.connection.remoteAddress, - userAgent, - timestamp, + userAgent: request.headers["user-agent"], + timestamp: Math.floor(new Date().getTime() / 1000), })); return this.getLogin("Invalid password", data); } @@ -279,23 +264,16 @@ export abstract class Server { private async getLogin(error: string = "", payload?: LoginPayload): Promise { const filePath = path.join(this.rootPath, "out/vs/server/src/login/login.html"); - let content = await util.promisify(fs.readFile)(filePath, "utf8"); - if (error) { - content = content.replace("{{ERROR}}", error) - .replace("display:none", "display:block"); - } - if (payload && payload.password) { - content = content.replace('value=""', `value="${payload.password}"`); - } + const content = (await util.promisify(fs.readFile)(filePath, "utf8")) + .replace("{{ERROR}}", error) + .replace("display:none", error ? "display:block" : "display:none") + .replace('value=""', `value="${payload && payload.password || ""}"`); return { content, filePath }; } private ensureGet(request: http.IncomingMessage): void { if (request.method !== "GET") { - throw new HttpError( - `Unsupported method ${request.method}`, - HttpCode.BadRequest, - ); + throw new HttpError(`Unsupported method ${request.method}`, HttpCode.BadRequest); } } @@ -357,15 +335,10 @@ export abstract class Server { } export class MainServer extends Server { - // Used to notify the IPC server that there is a new client. public readonly _onDidClientConnect = new Emitter(); public readonly onDidClientConnect = this._onDidClientConnect.event; - - // This is separate instead of just extending this class since we can't - // use properties in the super call. This manages channels. private readonly ipc = new IPCServer(this.onDidClientConnect); - // Persistent connections. These can reconnect within a timeout. private readonly connections = new Map>(); private readonly services = new ServiceCollection(); @@ -377,7 +350,6 @@ export class MainServer extends Server { args: ParsedArgs, ) { super(options); - this.server.on("upgrade", async (request, socket) => { const protocol = this.createProtocol(request, socket); try { @@ -393,12 +365,10 @@ export class MainServer extends Server { public async listen(): Promise { const environment = (this.services.get(IEnvironmentService) as EnvironmentService); - const mkdirs = Promise.all([ - environment.extensionsPath, - ].map((p) => mkdirp(p))); - const [address] = await Promise.all([ - super.listen(), - mkdirs, + const [address] = await Promise.all([ + super.listen(), ...[ + environment.extensionsPath, + ].map((p) => mkdirp(p).then(() => p)), ]); return address; } @@ -426,22 +396,18 @@ export class MainServer extends Server { private async getRoot(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise { const filePath = path.join(this.rootPath, "out/vs/code/browser/workbench/workbench.html"); - let content = await util.promisify(fs.readFile)(filePath, "utf8"); - - const remoteAuthority = request.headers.host as string; - const transformer = getUriTransformer(remoteAuthority); - - await Promise.all([ + let [content] = await Promise.all([ + util.promisify(fs.readFile)(filePath, "utf8"), this.webviewServer.listen(), this.servicesPromise, ]); const webviewEndpoint = this.webviewServer.address(request); - const cwd = process.env.VSCODE_CWD || process.cwd(); const workspacePath = parsedUrl.query.workspace as string | undefined; const folderPath = !workspacePath ? parsedUrl.query.folder as string | undefined || this.options.folderUri || cwd: undefined; - + const remoteAuthority = request.headers.host as string; + const transformer = getUriTransformer(remoteAuthority); const options: Options = { WORKBENCH_WEB_CONGIGURATION: { workspaceUri: workspacePath @@ -463,7 +429,6 @@ export class MainServer extends Server { Object.keys(options).forEach((key) => { content = content.replace(`"{{${key}}}"`, `'${JSON.stringify(options[key])}'`); }); - content = content.replace('{{WEBVIEW_ENDPOINT}}', webviewEndpoint); return { content, filePath }; @@ -473,83 +438,58 @@ export class MainServer extends Server { if (request.headers.upgrade !== "websocket") { throw new Error("HTTP/1.1 400 Bad Request"); } - - const options = { - reconnectionToken: "", - reconnection: false, - skipWebSocketFrames: false, - }; - - if (request.url) { - const query = url.parse(request.url, true).query; - if (query.reconnectionToken) { - options.reconnectionToken = query.reconnectionToken as string; - } - if (query.reconnection === "true") { - options.reconnection = true; - } - if (query.skipWebSocketFrames === "true") { - options.skipWebSocketFrames = true; - } - } - - return new Protocol( - request.headers["sec-websocket-key"] as string, - socket, - options, - ); + const query = request.url ? url.parse(request.url, true).query : {}; + return new Protocol(request.headers["sec-websocket-key"], socket, { + reconnectionToken: query.reconnectionToken || "", + reconnection: query.reconnection === "true", + skipWebSocketFrames: query.skipWebSocketFrames === "true", + }); } private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise { switch (message.desiredConnectionType) { case ConnectionType.ExtensionHost: case ConnectionType.Management: - const debugPort = await this.getDebugPort(); - const ok = message.desiredConnectionType === ConnectionType.ExtensionHost - ? (debugPort ? { debugPort } : {}) - : { type: "ok" }; - if (!this.connections.has(message.desiredConnectionType)) { this.connections.set(message.desiredConnectionType, new Map()); } - const connections = this.connections.get(message.desiredConnectionType)!; - const token = protocol.options.reconnectionToken; + const ok = async () => { + return message.desiredConnectionType === ConnectionType.ExtensionHost + ? { debugPort: await this.getDebugPort() } + : { type: "ok" }; + }; + + const token = protocol.options.reconnectionToken; if (protocol.options.reconnection && connections.has(token)) { - protocol.sendMessage(ok); + protocol.sendMessage(await ok()); const buffer = protocol.readEntireBuffer(); protocol.dispose(); return connections.get(token)!.reconnect(protocol.getSocket(), buffer); - } - - if (protocol.options.reconnection || connections.has(token)) { + } else if (protocol.options.reconnection || connections.has(token)) { throw new Error(protocol.options.reconnection ? "Unrecognized reconnection token" : "Duplicate reconnection token" ); } - protocol.sendMessage(ok); + protocol.sendMessage(await ok()); let connection: Connection; if (message.desiredConnectionType === ConnectionType.Management) { connection = new ManagementConnection(protocol); this._onDidClientConnect.fire({ - protocol, - onDidClientDisconnect: connection.onClose, + protocol, onDidClientDisconnect: connection.onClose, }); } else { const buffer = protocol.readEntireBuffer(); connection = new ExtensionHostConnection( - protocol, buffer, - this.services.get(ILogService) as ILogService, + protocol, buffer, this.services.get(ILogService) as ILogService, ); } - connections.set(protocol.options.reconnectionToken, connection); - connection.onClose(() => { - connections.delete(protocol.options.reconnectionToken); - }); + connections.set(token, connection); + connection.onClose(() => connections.delete(token)); break; case ConnectionType.Tunnel: return protocol.tunnel(); default: throw new Error("Unrecognized connection type"); @@ -557,14 +497,11 @@ export class MainServer extends Server { } private async initializeServices(args: ParsedArgs): Promise { + const router = new StaticRouter((ctx: any) => ctx.clientId === "renderer"); const environmentService = new EnvironmentService(args, process.execPath); const logService = new SpdLogService(RemoteExtensionLogFileName, environmentService.logsPath, getLogLevel(environmentService)); this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService)); - const router = new StaticRouter((context: any) => { - return context.clientId === "renderer"; - }); - this.services.set(ILogService, logService); this.services.set(IEnvironmentService, environmentService); this.services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.machineSettingsResource])); @@ -594,11 +531,9 @@ export class MainServer extends Server { this.services.set(IDialogService, new DialogChannelClient(this.ipc.getChannel("dialog", router))); this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService)); - const instantiationService = new InstantiationService(this.services); - - this.services.set(ILocalizationsService, instantiationService.createInstance(LocalizationsService)); - - return new Promise((resolve) => { + await new Promise((resolve) => { + const instantiationService = new InstantiationService(this.services); + this.services.set(ILocalizationsService, instantiationService.createInstance(LocalizationsService)); instantiationService.invokeFunction(() => { instantiationService.createInstance(LogsDataCleaner); this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, new FileProviderChannel(environmentService, logService)); @@ -612,9 +547,7 @@ export class MainServer extends Server { this.ipc.registerChannel("gallery", galleryChannel); const telemetryChannel = new TelemetryChannel(telemetryService); this.ipc.registerChannel("telemetry", telemetryChannel); - // tslint:disable-next-line no-unused-expression - new ErrorTelemetry(telemetryService); - resolve(); + resolve(new ErrorTelemetry(telemetryService)); }); }); } @@ -633,9 +566,6 @@ export class WebviewServer extends Server { requestPath: string, ): Promise { const webviewPath = path.join(this.rootPath, "out/vs/workbench/contrib/webview/browser/pre"); - if (requestPath === "") { - requestPath = "/index.html"; - } - return this.getResource(path.join(webviewPath, base, requestPath)); + return this.getResource(path.join(webviewPath, base, requestPath || "/index.html")); } } diff --git a/src/tar.ts b/src/tar.ts index b8030143..096846f3 100644 --- a/src/tar.ts +++ b/src/tar.ts @@ -1,7 +1,7 @@ import * as fs from "fs"; import * as path from "path"; import * as tarStream from "tar-stream"; -import { promisify } from "util"; +import * as util from "util"; import * as nls from "vs/nls"; import * as vszip from "vs/base/node/zip"; @@ -14,7 +14,6 @@ const vszipBuffer = vszip.buffer; export interface IExtractOptions { overwrite?: boolean; - /** * Source path within the TAR/ZIP archive. Only the files * contained in this path will be extracted. @@ -28,197 +27,134 @@ export interface IFile { localPath?: string; } -/** - * Override the standard VS Code behavior for zipping extensions to use the TAR - * format instead of ZIP. - */ -export const zip = (tarPath: string, files: IFile[]): Promise => { - return new Promise((c, e): void => { - const pack = tarStream.pack(); - const chunks: Buffer[] = []; - const ended = new Promise((res): void => { - pack.on("end", () => { - res(Buffer.concat(chunks)); - }); - }); - pack.on("data", (chunk) => { - chunks.push(chunk as Buffer); - }); - for (let i = 0; i < files.length; i++) { - const file = files[i]; - pack.entry({ - name: file.path, - }, file.contents); +export const tar = async (tarPath: string, files: IFile[]): Promise => { + const pack = tarStream.pack(); + const chunks: Buffer[] = []; + const ended = new Promise((resolve) => { + pack.on("end", () => resolve(Buffer.concat(chunks))); + }); + pack.on("data", (chunk: Buffer) => chunks.push(chunk)); + for (let i = 0; i < files.length; i++) { + const file = files[i]; + pack.entry({ name: file.path }, file.contents); + } + pack.finalize(); + await util.promisify(fs.writeFile)(tarPath, await ended); + return tarPath; +}; + +export const extract = async (archivePath: string, extractPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise => { + try { + await extractTar(archivePath, extractPath, options, token); + } catch (error) { + if (error.toString().includes("Invalid tar header")) { + await vszipExtract(archivePath, extractPath, options, token); } - pack.finalize(); - - ended.then((buffer) => { - return promisify(fs.writeFile)(tarPath, buffer); - }).then(() => { - c(tarPath); - }).catch((ex) => { - e(ex); - }); - }); + } }; -/** - * Override the standard VS Code behavior for extracting archives to first - * attempt to process the archive as a TAR and then fall back to the original - * implementation for processing ZIPs. - */ -export const extract = (archivePath: string, extractPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise => { - return new Promise((c, e): void => { - extractTar(archivePath, extractPath, options, token).then(c).catch((ex) => { - if (!ex.toString().includes("Invalid tar header")) { - e(ex); - - return; - } - vszipExtract(archivePath, extractPath, options, token).then(c).catch(e); - }); - }); -}; - -/** - * Override the standard VS Code behavior for buffering archives to first - * process the Buffer as a TAR and then fall back to the original - * implementation for processing ZIPs. - */ export const buffer = (targetPath: string, filePath: string): Promise => { - return new Promise((c, e): void => { - let done: boolean = false; - extractAssets(targetPath, new RegExp(filePath), (assetPath: string, data: Buffer) => { - if (path.normalize(assetPath) === path.normalize(filePath)) { - done = true; - c(data); - } - }).then(() => { - if (!done) { - e("couldn't find asset " + filePath); - } - }).catch((ex) => { - if (!ex.toString().includes("Invalid tar header")) { - e(ex); - - return; - } - vszipBuffer(targetPath, filePath).then(c).catch(e); - }); - }); -}; - -/** - * Override the standard VS Code behavior for extracting assets from archive - * Buffers to use the TAR format instead of ZIP. - */ -const extractAssets = (tarPath: string, match: RegExp, callback: (path: string, data: Buffer) => void): Promise => { - return new Promise(async (c, e): Promise => { + return new Promise(async (resolve, reject) => { try { - const buffer = await promisify(fs.readFile)(tarPath); - const extractor = tarStream.extract(); - extractor.once("error", e); - extractor.on("entry", (header, stream, next) => { - const name = header.name; - if (match.test(name)) { - extractData(stream).then((data) => { - callback(name, data); - next(); - }).catch(e); - stream.resume(); - } else { - stream.on("end", () => { - next(); - }); - stream.resume(); + let done: boolean = false; + await extractAssets(targetPath, new RegExp(filePath), (assetPath: string, data: Buffer) => { + if (path.normalize(assetPath) === path.normalize(filePath)) { + done = true; + resolve(data); } }); - extractor.on("finish", () => { - c(); - }); - extractor.write(buffer); - extractor.end(); - } catch (ex) { - e(ex); + if (!done) { + throw new Error("couldn't find asset " + filePath); + } + } catch (error) { + if (error.toString().includes("Invalid tar header")) { + vszipBuffer(targetPath, filePath).then(resolve).catch(reject); + } else { + reject(error); + } } }); }; +const extractAssets = async (tarPath: string, match: RegExp, callback: (path: string, data: Buffer) => void): Promise => { + const buffer = await util.promisify(fs.readFile)(tarPath); + return new Promise(async (resolve, reject): Promise => { + const extractor = tarStream.extract(); + extractor.once("error", reject); + extractor.on("entry", async (header, stream, next) => { + const name = header.name; + if (match.test(name)) { + extractData(stream).then((data) => { + callback(name, data); + next(); + }).catch(reject); + stream.resume(); + } else { + stream.on("end", () => next()); + stream.resume(); + } + }); + extractor.on("finish", resolve); + extractor.write(buffer); + extractor.end(); + }); +}; + const extractData = (stream: NodeJS.ReadableStream): Promise => { - return new Promise((c, e): void => { + return new Promise((resolve, reject): void => { const fileData: Buffer[] = []; stream.on("data", (data) => fileData.push(data)); - stream.on("end", () => { - const fd = Buffer.concat(fileData); - c(fd); - }); - stream.on("error", e); + stream.on("end", () => resolve(Buffer.concat(fileData))); + stream.on("error", reject); }); }; -const extractTar = (tarPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise => { - return new Promise(async (c, e): Promise => { - try { - const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : ""); - const buffer = await promisify(fs.readFile)(tarPath); - const extractor = tarStream.extract(); - extractor.once("error", e); - extractor.on("entry", (header, stream, next) => { - const rawName = path.normalize(header.name); +const extractTar = async (tarPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise => { + const buffer = await util.promisify(fs.readFile)(tarPath); + return new Promise(async (resolve, reject): Promise => { + const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : ""); + const extractor = tarStream.extract(); + extractor.once("error", reject); + extractor.on("entry", async (header, stream, next) => { + const rawName = path.normalize(header.name); - const nextEntry = (): void => { - stream.resume(); - next(); - }; + const nextEntry = (): void => { + stream.resume(); + next(); + }; - if (token.isCancellationRequested) { - return nextEntry(); - } + if (token.isCancellationRequested || !sourcePathRegex.test(rawName)) { + return nextEntry(); + } - if (!sourcePathRegex.test(rawName)) { - return nextEntry(); - } + const fileName = rawName.replace(sourcePathRegex, ""); + const targetFileName = path.join(targetPath, fileName); + if (/\/$/.test(fileName)) { + return mkdirp(targetFileName).then(nextEntry); + } - const fileName = rawName.replace(sourcePathRegex, ""); - const targetFileName = path.join(targetPath, fileName); - if (/\/$/.test(fileName)) { - stream.resume(); - mkdirp(targetFileName).then(() => { - next(); - }, e); + const dirName = path.dirname(fileName); + const targetDirName = path.join(targetPath, dirName); + if (targetDirName.indexOf(targetPath) !== 0) { + return reject(nls.localize("invalid file", "Error extracting {0}. Invalid file.", fileName)); + } - return; - } - - const dirName = path.dirname(fileName); - const targetDirName = path.join(targetPath, dirName); - if (targetDirName.indexOf(targetPath) !== 0) { - e(nls.localize("invalid file", "Error extracting {0}. Invalid file.", fileName)); - - return nextEntry(); - } - - return mkdirp(targetDirName, undefined, token).then(() => { - const fstream = fs.createWriteStream(targetFileName, { mode: header.mode }); - fstream.once("close", () => { - next(); - }); - fstream.once("error", e); - stream.pipe(fstream); - stream.resume(); - }); + return mkdirp(targetDirName, undefined, token).then(() => { + const fstream = fs.createWriteStream(targetFileName, { mode: header.mode }); + fstream.once("close", () => next()); + fstream.once("error", reject); + stream.pipe(fstream); + stream.resume(); }); - extractor.once("finish", c); - extractor.write(buffer); - extractor.end(); - } catch (ex) { - e(ex); - } + }); + extractor.once("finish", resolve); + extractor.write(buffer); + extractor.end(); }); }; // Override original functionality so we can use tar instead of zip. const target = vszip as typeof vszip; -target.zip = zip; +target.zip = tar; target.extract = extract; target.buffer = buffer; diff --git a/src/uriTransformerHttp.js b/src/uriTransformerHttp.js index a65bf089..e3737607 100644 --- a/src/uriTransformerHttp.js +++ b/src/uriTransformerHttp.js @@ -4,8 +4,7 @@ module.exports = (remoteAuthority, https) => { return { transformIncoming: (uri) => { switch (uri.scheme) { - case "https": return { scheme: "file", path: uri.path }; - case "http": return { scheme: "file", path: uri.path }; + case "https": case "http": return { scheme: "file", path: uri.path }; case "file": return { scheme: "vscode-local", path: uri.path }; default: return uri; } diff --git a/src/uriTransformerHttps.js b/src/uriTransformerHttps.js index bddfe646..c5f47fdc 100644 --- a/src/uriTransformerHttps.js +++ b/src/uriTransformerHttps.js @@ -1,3 +1 @@ -module.exports = (remoteAuthority) => { - return require("./uriTransformerHttp")(remoteAuthority, true); -}; +module.exports = (remoteAuthority) => require("./uriTransformerHttp")(remoteAuthority, true); diff --git a/src/util.ts b/src/util.ts index 983a8ce8..dc734c52 100644 --- a/src/util.ts +++ b/src/util.ts @@ -25,8 +25,6 @@ export const generateCertificate = async (): Promise<{ cert: string, certKey: st util.promisify(fs.exists)(paths.certKey), ]); - await mkdirp(tmpdir); - if (!exists[0] || !exists[1]) { const pem = require.__$__nodeRequire(path.resolve(__dirname, "../node_modules/pem/lib/pem")) as typeof import("pem"); const certs = await new Promise((resolve, reject): void => { @@ -37,6 +35,7 @@ export const generateCertificate = async (): Promise<{ cert: string, certKey: st resolve(result); }); }); + await mkdirp(tmpdir); await Promise.all([ util.promisify(fs.writeFile)(paths.cert, certs.certificate), util.promisify(fs.writeFile)(paths.certKey, certs.serviceKey), @@ -46,16 +45,10 @@ export const generateCertificate = async (): Promise<{ cert: string, certKey: st return paths; }; -let secure: boolean; -export const useHttpsTransformer = (): void => { - secure = true; -}; - +let transformer: string = "uriTransformerHttp"; +export const useHttpsTransformer = (): string => transformer = "uriTransformerHttps"; export const uriTransformerPath = (): string => { - return getPathFromAmdModule( - require, - "vs/server/src/uriTransformerHttp" + (secure ? "s": ""), - ); + return getPathFromAmdModule(require, `vs/server/src/${transformer}`); }; export const getUriTransformer = (remoteAuthority: string): URITransformer => { @@ -87,25 +80,16 @@ export const isWsl = async (): Promise => { }; export const open = async (url: string): Promise => { - let command: string; const args = []; const options = {}; const platform = await isWsl() ? "wsl" : process.platform; - switch (platform) { - case "darwin": - command = "open"; - break; - case "win32": - case "wsl": - command = platform === "wsl" ? "cmd.exe" : "cmd"; - args.push("/c", "start", '""', "/b"); - url = url.replace(/&/g, "^&"); - default: - command = "xdg-open"; - break; + let command = platform === "darwin" ? "open" : "xdg-open"; + if (platform === "win32" || platform === "wsl") { + command = platform === "wsl" ? "cmd.exe" : "cmd"; + args.push("/c", "start", '""', "/b"); + url = url.replace(/&/g, "^&"); } - args.push(url); - const proc = cp.spawn(command, args, options); + const proc = cp.spawn(command, [...args, url], options); await new Promise((resolve, reject) => { proc.on("error", reject); proc.on("close", (code) => { @@ -125,8 +109,6 @@ export const unpackExecutables = async (): Promise => { const destination = path.join(tmpdir, path.basename(rgPath || "")); if (rgPath && !(await util.promisify(fs.exists)(destination))) { await mkdirp(tmpdir); - // TODO: I'm not sure why but copyFile doesn't work in the Docker build. - // await util.promisify(fs.copyFile)(rgPath, destination); await util.promisify(fs.writeFile)(destination, await util.promisify(fs.readFile)(rgPath)); await util.promisify(fs.chmod)(destination, "755"); }