Quality check

This commit is contained in:
Asher 2019-07-19 17:43:54 -05:00
parent e8cb6ffaa0
commit 9fdfacb314
No known key found for this signature in database
GPG key ID: D63C1EF81242354A
11 changed files with 193 additions and 428 deletions

View file

@ -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. */ }
}

View file

@ -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 = <T>(id: ServiceIdentifier<T>): T => serviceCollection.get<T>(id) as T;
return {
workbench: {
action: Action,
@ -103,13 +99,8 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
commandRegistry: CommandsRegistry,
actionsRegistry: Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions),
registerView: (viewId, viewName, containerId, containerName, icon): void => {
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewsExtensions.ViewContainersRegistry);
const viewsRegistry = Registry.as<IViewsRegistry>(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<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(
new ViewletDescriptor(CustomViewlet as any, id, containerName, cssClass, undefined, URI.parse(icon)),
);
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(viewletDescriptor);
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(
Registry.as<IWorkbenchActionRegistry>(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<IViewContainersRegistry>(ViewsExtensions.ViewContainersRegistry).registerViewContainer(containerId);
Registry.as<IViewsRegistry>(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>(IFileService).onAfterOperation((e) => {
if (e.operation === FileOperation.CREATE) {
@ -198,7 +177,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
}
});
},
onModelAdded: (cb): void => {
getService<IModelService>(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>(ITerminalService).onInstanceCreated(() => cb());
},
@ -222,7 +199,6 @@ export const coderApi = (serviceCollection: ServiceCollection): typeof coder =>
getService<ITerminalService>(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<void> = Event.None;
public constructor(
private readonly provider: vscode.FileSystemProvider,
) {
public constructor(private readonly provider: vscode.FileSystemProvider) {
this.capabilities = FileSystemProviderCapabilities.Readonly;
}

View file

@ -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<string, Watcher>();
@ -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<string, IExtensionDescription>();
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 = <IExtensionDescription[]>[];
uniqueExtensions.forEach((e) => finalExtensions.push(e));
return finalExtensions;
return Array.from(uniqueExtensions.values());
});
}

View file

@ -92,12 +92,7 @@ const main = async (): Promise<void> => {
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<void> => {
if (shouldSpawnCliProcess()) {
const cli = await new Promise<IMainCli>((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<void> => {
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<void> => {
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}`);
}

View file

@ -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<void>();
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);
}
}

View file

@ -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: "" });
}
/**

View file

@ -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<string> | 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<Response>;
protected async getResource(filePath: string): Promise<Response> {
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<void> => {
@ -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<LoginPayload>(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<Response> {
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<ClientConnectionEvent>();
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<ConnectionType, Map<string, Connection>>();
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<string> {
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<string>([
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<Response> {
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(<string>request.headers["sec-websocket-key"], socket, {
reconnectionToken: <string>query.reconnectionToken || "",
reconnection: query.reconnection === "true",
skipWebSocketFrames: query.skipWebSocketFrames === "true",
});
}
private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
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<void> {
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<Response> {
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"));
}
}

View file

@ -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<string> => {
return new Promise<string>((c, e): void => {
const pack = tarStream.pack();
const chunks: Buffer[] = [];
const ended = new Promise<Buffer>((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<string> => {
const pack = tarStream.pack();
const chunks: Buffer[] = [];
const ended = new Promise<Buffer>((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<void> => {
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<void> => {
return new Promise<void>((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<Buffer> => {
return new Promise<Buffer>((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<void> => {
return new Promise<void>(async (c, e): Promise<void> => {
return new Promise<Buffer>(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<void> => {
const buffer = await util.promisify(fs.readFile)(tarPath);
return new Promise<void>(async (resolve, reject): Promise<void> => {
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<Buffer> => {
return new Promise<Buffer>((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<void> => {
return new Promise<void>(async (c, e): Promise<void> => {
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<void> => {
const buffer = await util.promisify(fs.readFile)(tarPath);
return new Promise<void>(async (resolve, reject): Promise<void> => {
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;

View file

@ -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;
}

View file

@ -1,3 +1 @@
module.exports = (remoteAuthority) => {
return require("./uriTransformerHttp")(remoteAuthority, true);
};
module.exports = (remoteAuthority) => require("./uriTransformerHttp")(remoteAuthority, true);

View file

@ -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<import("pem").CertificateCreationResult>((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<boolean> => {
};
export const open = async (url: string): Promise<void> => {
let command: string;
const args = <string[]>[];
const options = <cp.SpawnOptions>{};
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<void> => {
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");
}