server.ts 31.4 KB
Newer Older
A
Asher 已提交
1
import * as crypto from "crypto";
A
Asher 已提交
2 3
import * as fs from "fs";
import * as http from "http";
A
Asher 已提交
4
import * as https from "https";
A
Asher 已提交
5 6
import * as net from "net";
import * as path from "path";
A
Asher 已提交
7
import * as querystring from "querystring";
A
Asher 已提交
8
import { Readable } from "stream";
A
Asher 已提交
9
import * as tls from "tls";
A
Asher 已提交
10
import * as url from "url";
A
Asher 已提交
11
import * as util from "util";
A
Asher 已提交
12
import { Emitter } from "vs/base/common/event";
A
Asher 已提交
13
import { sanitizeFilePath } from "vs/base/common/extpath";
A
Asher 已提交
14 15
import { Schemas } from "vs/base/common/network";
import { URI, UriComponents } from "vs/base/common/uri";
A
Asher 已提交
16
import { generateUuid } from "vs/base/common/uuid";
A
Asher 已提交
17
import { getMachineId } from 'vs/base/node/id';
A
Asher 已提交
18
import { NLSConfiguration } from "vs/base/node/languagePacks";
A
Asher 已提交
19
import { mkdirp, rimraf } from "vs/base/node/pfs";
A
Asher 已提交
20
import { ClientConnectionEvent, IPCServer, StaticRouter } from "vs/base/parts/ipc/common/ipc";
A
Asher 已提交
21
import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner";
A
Asher 已提交
22 23
import { IConfigurationService } from "vs/platform/configuration/common/configuration";
import { ConfigurationService } from "vs/platform/configuration/node/configurationService";
A
Asher 已提交
24
import { ExtensionHostDebugBroadcastChannel } from "vs/platform/debug/common/extensionHostDebugIpc";
A
Asher 已提交
25 26
import { IDialogService } from "vs/platform/dialogs/common/dialogs";
import { DialogChannelClient } from "vs/platform/dialogs/node/dialogIpc";
A
Asher 已提交
27
import { IEnvironmentService, ParsedArgs } from "vs/platform/environment/common/environment";
A
Asher 已提交
28
import { EnvironmentService } from "vs/platform/environment/node/environmentService";
A
Asher 已提交
29 30 31
import { ExtensionGalleryService } from "vs/platform/extensionManagement/common/extensionGalleryService";
import { IExtensionGalleryService, IExtensionManagementService } from "vs/platform/extensionManagement/common/extensionManagement";
import { ExtensionManagementChannel } from "vs/platform/extensionManagement/common/extensionManagementIpc";
A
Asher 已提交
32
import { ExtensionManagementService } from "vs/platform/extensionManagement/node/extensionManagementService";
A
Asher 已提交
33 34 35
import { IFileService } from "vs/platform/files/common/files";
import { FileService } from "vs/platform/files/common/fileService";
import { DiskFileSystemProvider } from "vs/platform/files/node/diskFileSystemProvider";
A
Asher 已提交
36
import { SyncDescriptor } from "vs/platform/instantiation/common/descriptors";
A
Asher 已提交
37
import { InstantiationService } from "vs/platform/instantiation/common/instantiationService";
A
Asher 已提交
38
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
A
Asher 已提交
39 40
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
import { LocalizationsService } from "vs/platform/localizations/node/localizations";
A
Asher 已提交
41
import { LocalizationsChannel } from "vs/platform/localizations/node/localizationsIpc";
42
import { getLogLevel, ILogService } from "vs/platform/log/common/log";
A
Asher 已提交
43
import { LogLevelSetterChannel } from "vs/platform/log/common/logIpc";
A
Asher 已提交
44
import { SpdLogService } from "vs/platform/log/node/spdlogService";
A
Asher 已提交
45
import { IProductService } from "vs/platform/product/common/product";
A
Asher 已提交
46
import pkg from "vs/platform/product/node/package";
A
Asher 已提交
47
import product from "vs/platform/product/node/product";
48
import { ConnectionType, ConnectionTypeRequest } from "vs/platform/remote/common/remoteAgentConnection";
A
Asher 已提交
49
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/platform/remote/common/remoteAgentFileSystemChannel";
A
Asher 已提交
50 51
import { IRequestService } from "vs/platform/request/common/request";
import { RequestChannel } from "vs/platform/request/common/requestIpc";
A
Asher 已提交
52
import { RequestService } from "vs/platform/request/node/requestService";
A
Asher 已提交
53
import ErrorTelemetry from "vs/platform/telemetry/browser/errorTelemetry";
A
Asher 已提交
54
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
A
Asher 已提交
55 56
import { ITelemetryServiceConfig, TelemetryService } from "vs/platform/telemetry/common/telemetryService";
import { combinedAppender, LogAppender, NullTelemetryService } from "vs/platform/telemetry/common/telemetryUtils";
A
Asher 已提交
57 58
import { AppInsightsAppender } from "vs/platform/telemetry/node/appInsightsAppender";
import { resolveCommonProperties } from "vs/platform/telemetry/node/commonProperties";
A
Asher 已提交
59
import { UpdateChannel } from "vs/platform/update/node/updateIpc";
60 61 62 63
import { ExtensionEnvironmentChannel, FileProviderChannel, NodeProxyService } from "vs/server/src/node/channel";
import { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/src/node/connection";
import { TelemetryClient } from "vs/server/src/node/insights";
import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/src/node/nls";
A
Asher 已提交
64
import { NodeProxyChannel, INodeProxyService } from "vs/server/src/common/nodeProxy";
65 66 67 68
import { Protocol } from "vs/server/src/node/protocol";
import { TelemetryChannel } from "vs/server/src/common/telemetry";
import { UpdateService } from "vs/server/src/node/update";
import { AuthType, getMediaMime, getUriTransformer, localRequire, tmpdir } from "vs/server/src/node/util";
A
Asher 已提交
69 70
import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService";
import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api";
A
Asher 已提交
71

A
Asher 已提交
72 73
const tarFs = localRequire<typeof import("tar-fs")>("tar-fs/index");

A
Asher 已提交
74
export enum HttpCode {
A
Asher 已提交
75
	Ok = 200,
A
Asher 已提交
76
	Redirect = 302,
A
Asher 已提交
77 78
	NotFound = 404,
	BadRequest = 400,
A
Asher 已提交
79 80 81
	Unauthorized = 401,
	LargePayload = 413,
	ServerError = 500,
A
Asher 已提交
82 83
}

A
Asher 已提交
84 85
export interface Options {
	WORKBENCH_WEB_CONGIGURATION: IWorkbenchConstructionOptions;
A
Asher 已提交
86
	REMOTE_USER_DATA_URI: UriComponents | URI;
A
Asher 已提交
87
	NLS_CONFIGURATION: NLSConfiguration;
A
Asher 已提交
88 89
}

90
export interface Response {
A
Asher 已提交
91
	cache?: boolean;
92
	code?: number;
A
Asher 已提交
93 94 95
	content?: string | Buffer;
	filePath?: string;
	headers?: http.OutgoingHttpHeaders;
A
Asher 已提交
96
	mime?: string;
A
Asher 已提交
97
	redirect?: string;
A
Asher 已提交
98
	stream?: Readable;
A
Asher 已提交
99 100 101 102
}

export interface LoginPayload {
	password?: string;
103 104
}

A
Asher 已提交
105
export class HttpError extends Error {
A
Asher 已提交
106 107 108 109 110 111 112 113
	public constructor(message: string, public readonly code: number) {
		super(message);
		// @ts-ignore
		this.name = this.constructor.name;
		Error.captureStackTrace(this, this.constructor);
	}
}

A
Asher 已提交
114
export interface ServerOptions {
A
Asher 已提交
115
	readonly auth?: AuthType;
A
Asher 已提交
116
	readonly basePath?: string;
A
Asher 已提交
117
	readonly connectionToken?: string;
A
Asher 已提交
118 119
	readonly cert?: string;
	readonly certKey?: string;
120
	readonly folderUri?: string;
A
Asher 已提交
121 122 123 124
	readonly host?: string;
	readonly password?: string;
	readonly port?: number;
	readonly socket?: string;
A
Asher 已提交
125 126
}

A
Asher 已提交
127
export abstract class Server {
A
Asher 已提交
128
	protected readonly server: http.Server | https.Server;
129
	protected rootPath = path.resolve(__dirname, "../../../../..");
A
Asher 已提交
130 131
	protected serverRoot = path.join(this.rootPath, "/out/vs/server/src");
	protected readonly allowedRequestPaths: string[] = [this.rootPath];
A
Asher 已提交
132
	private listenPromise: Promise<string> | undefined;
A
Asher 已提交
133
	public readonly protocol: "http" | "https";
A
Asher 已提交
134 135 136 137 138 139
	public readonly options: ServerOptions;

	public constructor(options: ServerOptions) {
		this.options = {
			host: options.auth && options.cert ? "0.0.0.0" : "localhost",
			...options,
A
Asher 已提交
140
			basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "",
A
Asher 已提交
141 142 143
		};
		this.protocol = this.options.cert ? "https" : "http";
		if (this.protocol === "https") {
144
			const httpolyglot = localRequire<typeof import("httpolyglot")>("httpolyglot/lib/index");
A
Asher 已提交
145
			this.server = httpolyglot.createServer({
A
Asher 已提交
146 147
				cert: this.options.cert && fs.readFileSync(this.options.cert),
				key: this.options.certKey && fs.readFileSync(this.options.certKey),
A
Asher 已提交
148 149 150 151
			}, this.onRequest);
		} else {
			this.server = http.createServer(this.onRequest);
		}
A
Asher 已提交
152 153
	}

A
Asher 已提交
154 155 156 157
	public listen(): Promise<string> {
		if (!this.listenPromise) {
			this.listenPromise = new Promise((resolve, reject) => {
				this.server.on("error", reject);
A
Asher 已提交
158
				this.server.on("upgrade", this.onUpgrade);
A
Asher 已提交
159
				const onListen = () => resolve(this.address());
A
Asher 已提交
160 161 162 163 164
				if (this.options.socket) {
					this.server.listen(this.options.socket, onListen);
				} else {
					this.server.listen(this.options.port, this.options.host, onListen);
				}
A
Asher 已提交
165 166 167
			});
		}
		return this.listenPromise;
A
Asher 已提交
168 169
	}

A
Asher 已提交
170
	/**
A
Asher 已提交
171
	 * The *local* address of the server.
A
Asher 已提交
172
	 */
A
Asher 已提交
173
	public address(): string {
A
Asher 已提交
174 175
		const address = this.server.address();
		const endpoint = typeof address !== "string"
A
Asher 已提交
176
			? (address.address === "::" ? "localhost" : address.address) + ":" + address.port
A
Asher 已提交
177
			: address;
A
Asher 已提交
178
		return `${this.protocol}://${endpoint}`;
A
Asher 已提交
179 180
	}

A
Asher 已提交
181 182 183 184 185
	protected abstract handleWebSocket(
		socket: net.Socket,
		parsedUrl: url.UrlWithParsedQuery
	): Promise<void>;

A
Asher 已提交
186 187 188 189 190 191 192
	protected abstract handleRequest(
		base: string,
		requestPath: string,
		parsedUrl: url.UrlWithParsedQuery,
		request: http.IncomingMessage,
	): Promise<Response>;

A
Asher 已提交
193
	protected async getResource(...parts: string[]): Promise<Response> {
A
Asher 已提交
194 195 196 197 198 199 200 201 202 203
		const filePath = this.ensureAuthorizedFilePath(...parts);
		return { content: await util.promisify(fs.readFile)(filePath), filePath };
	}

	protected async getTarredResource(...parts: string[]): Promise<Response> {
		const filePath = this.ensureAuthorizedFilePath(...parts);
		return { stream: tarFs.pack(filePath), filePath, mime: "application/tar" };
	}

	protected ensureAuthorizedFilePath(...parts: string[]): string {
A
Asher 已提交
204
		const filePath = path.join(...parts);
A
Asher 已提交
205 206 207
		if (!this.isAllowedRequestPath(filePath)) {
			throw new HttpError("Unauthorized", HttpCode.Unauthorized);
		}
A
Asher 已提交
208
		return filePath;
A
Asher 已提交
209
	}
A
Asher 已提交
210

A
Asher 已提交
211
	protected withBase(request: http.IncomingMessage, path: string): string {
212 213
		const split = request.url ? request.url.split("?", 2) : [];
		return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}${split.length === 2 ? `?${split[1]}` : ""}`;
A
Asher 已提交
214 215
	}

A
Asher 已提交
216 217 218 219 220 221 222 223 224
	private isAllowedRequestPath(path: string): boolean {
		for (let i = 0; i < this.allowedRequestPaths.length; ++i) {
			if (path.indexOf(this.allowedRequestPaths[i]) === 0) {
				return true;
			}
		}
		return false;
	}

A
Asher 已提交
225
	private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
A
Asher 已提交
226
		try {
A
Asher 已提交
227 228
			const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}};
			const payload = await this.preHandleRequest(request, parsedUrl);
A
Asher 已提交
229
			response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
A
Asher 已提交
230
				"Content-Type": payload.mime || getMediaMime(payload.filePath),
A
Asher 已提交
231
				...(payload.redirect ? { Location: this.withBase(request, payload.redirect) } : {}),
232
				...(request.headers["service-worker"] ? { "Service-Worker-Allowed": this.options.basePath || "/" } : {}),
A
Asher 已提交
233
				...(payload.cache ? { "Cache-Control": "public, max-age=31536000" } : {}),
A
Asher 已提交
234 235
				...payload.headers,
			});
A
Asher 已提交
236 237 238 239 240 241 242 243 244
			if (payload.stream) {
				payload.stream.on("error", (error: NodeJS.ErrnoException) => {
					response.writeHead(error.code === "ENOENT" ? HttpCode.NotFound : HttpCode.ServerError);
					response.end(error.message);
				});
				payload.stream.pipe(response);
			} else {
				response.end(payload.content);
			}
A
Asher 已提交
245 246 247 248 249 250 251 252 253
		} catch (error) {
			if (error.code === "ENOENT" || error.code === "EISDIR") {
				error = new HttpError("Not found", HttpCode.NotFound);
			}
			response.writeHead(typeof error.code === "number" ? error.code : HttpCode.ServerError);
			response.end(error.message);
		}
	}

A
Asher 已提交
254
	private async preHandleRequest(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise<Response> {
A
Asher 已提交
255
		const secure = (request.connection as tls.TLSSocket).encrypted;
A
Asher 已提交
256
		if (this.options.cert && !secure) {
A
Asher 已提交
257
			return { redirect: request.url };
A
Asher 已提交
258 259
		}

A
Asher 已提交
260 261
		const fullPath = decodeURIComponent(parsedUrl.pathname || "/");
		const match = fullPath.match(/^(\/?[^/]*)(.*)$/);
262
		let [/* ignore */, base, requestPath] = match
A
Asher 已提交
263
			? match.map((p) => p.replace(/\/+$/, ""))
A
Asher 已提交
264 265 266 267 268 269 270
			: ["", "", ""];
		if (base.indexOf(".") !== -1) { // Assume it's a file at the root.
			requestPath = base;
			base = "/";
		} else if (base === "") { // Happens if it's a plain `domain.com`.
			base = "/";
		}
A
Asher 已提交
271
		base = path.normalize(base);
A
Asher 已提交
272
		requestPath = path.normalize(requestPath || "/index.html");
A
Asher 已提交
273

A
Asher 已提交
274 275 276 277
		if (base !== "/login" || !this.options.auth || requestPath !== "/index.html") {
			this.ensureGet(request);
		}

A
Asher 已提交
278 279 280 281 282
		// Allow for a versioned static endpoint. This lets us cache every static
		// resource underneath the path based on the version without any work and
		// without adding query parameters which have their own issues.
		// REVIEW: Discuss whether this is the best option; this is sort of a quick
		// hack almost to get caching in the meantime but it does work pretty well.
283
		if (/^\/static-.+/.test(base)) {
A
Asher 已提交
284 285 286
			base = "/static";
		}

A
Asher 已提交
287 288
		switch (base) {
			case "/":
A
Asher 已提交
289 290 291
				switch (requestPath) {
					case "/favicon.ico":
					case "/manifest.json":
A
Asher 已提交
292 293 294
						const response = await this.getResource(this.serverRoot, "media", requestPath);
						response.cache = true;
						return response;
A
Asher 已提交
295 296
				}
				if (!this.authenticate(request)) {
A
Asher 已提交
297
					return { redirect: "/login" };
A
Asher 已提交
298 299
				}
				break;
A
Asher 已提交
300
			case "/static":
A
Asher 已提交
301 302 303
				const response = await this.getResource(this.rootPath, requestPath);
				response.cache = true;
				return response;
A
Asher 已提交
304
			case "/login":
A
Asher 已提交
305
				if (!this.options.auth || requestPath !== "/index.html") {
A
Asher 已提交
306 307
					throw new HttpError("Not found", HttpCode.NotFound);
				}
A
Asher 已提交
308
				return this.tryLogin(request);
A
Asher 已提交
309 310
			default:
				if (!this.authenticate(request)) {
A
Asher 已提交
311
					throw new HttpError("Unauthorized", HttpCode.Unauthorized);
A
Asher 已提交
312 313 314
				}
				break;
		}
A
Asher 已提交
315

A
Asher 已提交
316 317
		return this.handleRequest(base, requestPath, parsedUrl, request);
	}
A
Asher 已提交
318

A
Asher 已提交
319 320 321 322 323
	private onUpgrade = async (request: http.IncomingMessage, socket: net.Socket): Promise<void> => {
		try {
			await this.preHandleWebSocket(request, socket);
		} catch (error) {
			socket.destroy();
A
Asher 已提交
324
			console.error(error.message);
A
Asher 已提交
325 326 327 328 329 330 331
		}
	}

	private preHandleWebSocket(request: http.IncomingMessage, socket: net.Socket): Promise<void> {
		socket.on("error", () => socket.destroy());
		socket.on("end", () => socket.destroy());

A
Asher 已提交
332
		this.ensureGet(request);
A
Asher 已提交
333 334
		if (!this.authenticate(request)) {
			throw new HttpError("Unauthorized", HttpCode.Unauthorized);
335
		} else if (!request.headers.upgrade || request.headers.upgrade.toLowerCase() !== "websocket") {
A
Asher 已提交
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354
			throw new Error("HTTP/1.1 400 Bad Request");
		}

		// This magic value is specified by the websocket spec.
		const magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
		const reply = crypto.createHash("sha1")
			.update(<string>request.headers["sec-websocket-key"] + magic)
			.digest("base64");
		socket.write([
			"HTTP/1.1 101 Switching Protocols",
			"Upgrade: websocket",
			"Connection: Upgrade",
			`Sec-WebSocket-Accept: ${reply}`,
		].join("\r\n") + "\r\n\r\n");

		const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}};
		return this.handleWebSocket(socket, parsedUrl);
	}

A
Asher 已提交
355
	private async tryLogin(request: http.IncomingMessage): Promise<Response> {
A
Asher 已提交
356
		if (this.authenticate(request) && (request.method === "GET" || request.method === "POST")) {
A
Asher 已提交
357
			return { redirect: "/" };
A
Asher 已提交
358 359 360 361 362
		}
		if (request.method === "POST") {
			const data = await this.getData<LoginPayload>(request);
			if (this.authenticate(request, data)) {
				return {
A
Asher 已提交
363
					redirect: "/",
A
Asher 已提交
364
					headers: {"Set-Cookie": `password=${data.password}` }
A
Asher 已提交
365
				};
A
Asher 已提交
366
			}
A
Asher 已提交
367 368 369
			console.error("Failed login attempt", JSON.stringify({
				xForwardedFor: request.headers["x-forwarded-for"],
				remoteAddress: request.connection.remoteAddress,
A
Asher 已提交
370 371
				userAgent: request.headers["user-agent"],
				timestamp: Math.floor(new Date().getTime() / 1000),
A
Asher 已提交
372 373
			}));
			return this.getLogin("Invalid password", data);
A
Asher 已提交
374
		}
A
Asher 已提交
375 376
		this.ensureGet(request);
		return this.getLogin();
A
Asher 已提交
377 378
	}

A
Asher 已提交
379
	private async getLogin(error: string = "", payload?: LoginPayload): Promise<Response> {
A
Asher 已提交
380
		const filePath = path.join(this.serverRoot, "login/index.html");
A
Asher 已提交
381 382 383 384
		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 || ""}"`);
A
Asher 已提交
385 386 387 388 389
		return { content, filePath };
	}

	private ensureGet(request: http.IncomingMessage): void {
		if (request.method !== "GET") {
A
Asher 已提交
390
			throw new HttpError(`Unsupported method ${request.method}`, HttpCode.BadRequest);
A
Asher 已提交
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408
		}
	}

	private getData<T extends object>(request: http.IncomingMessage): Promise<T> {
		return request.method === "POST"
			? new Promise<T>((resolve, reject) => {
				let body = "";
				const onEnd = (): void => {
					off();
					resolve(querystring.parse(body) as T);
				};
				const onError = (error: Error): void => {
					off();
					reject(error);
				};
				const onData = (d: Buffer): void => {
					body += d;
					if (body.length > 1e6) {
A
Asher 已提交
409
						onError(new HttpError("Payload is too large", HttpCode.LargePayload));
A
Asher 已提交
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428
						request.connection.destroy();
					}
				};
				const off = (): void => {
					request.off("error", onError);
					request.off("data", onError);
					request.off("end", onEnd);
				};
				request.on("error", onError);
				request.on("data", onData);
				request.on("end", onEnd);
			})
			: Promise.resolve({} as T);
	}

	private authenticate(request: http.IncomingMessage, payload?: LoginPayload): boolean {
		if (!this.options.auth) {
			return true;
		}
429
		const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index");
A
Asher 已提交
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
		if (typeof payload === "undefined") {
			payload = this.parseCookies<LoginPayload>(request);
		}
		return !!this.options.password && safeCompare(payload.password || "", this.options.password);
	}

	private parseCookies<T extends object>(request: http.IncomingMessage): T {
		const cookies: { [key: string]: string } = {};
		if (request.headers.cookie) {
			request.headers.cookie.split(";").forEach((keyValue) => {
				const [key, value] = keyValue.split("=", 2);
				cookies[key.trim()] = decodeURI(value);
			});
		}
		return cookies as T;
A
Asher 已提交
445
	}
A
Asher 已提交
446 447 448 449 450 451 452
}

export class MainServer extends Server {
	public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
	public readonly onDidClientConnect = this._onDidClientConnect.event;
	private readonly ipc = new IPCServer(this.onDidClientConnect);

453
	private readonly maxExtraOfflineConnections = 0;
A
Asher 已提交
454 455 456
	private readonly connections = new Map<ConnectionType, Map<string, Connection>>();

	private readonly services = new ServiceCollection();
A
Asher 已提交
457
	private readonly servicesPromise: Promise<void>;
A
Asher 已提交
458

A
Asher 已提交
459 460 461 462 463
	public readonly _onProxyConnect = new Emitter<net.Socket>();
	private proxyPipe = path.join(tmpdir, "tls-proxy");
	private _proxyServer?: Promise<net.Server>;
	private readonly proxyTimeout = 5000;

A
Asher 已提交
464
	public constructor(options: ServerOptions, args: ParsedArgs) {
A
Asher 已提交
465
		super(options);
A
Asher 已提交
466
		this.servicesPromise = this.initializeServices(args);
A
Asher 已提交
467
	}
A
Asher 已提交
468

A
Asher 已提交
469 470
	public async listen(): Promise<string> {
		const environment = (this.services.get(IEnvironmentService) as EnvironmentService);
A
Asher 已提交
471 472 473 474
		const [address] = await Promise.all<string>([
			super.listen(), ...[
				environment.extensionsPath,
			].map((p) => mkdirp(p).then(() => p)),
A
Asher 已提交
475 476 477 478
		]);
		return address;
	}

A
Asher 已提交
479
	protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
A
Asher 已提交
480 481 482
		if (!parsedUrl.query.reconnectionToken) {
			throw new Error("Reconnection token is missing from query parameters");
		}
A
Asher 已提交
483
		const protocol = new Protocol(await this.createProxy(socket), {
A
Asher 已提交
484
			reconnectionToken: <string>parsedUrl.query.reconnectionToken,
A
Asher 已提交
485 486 487 488 489 490 491 492 493 494 495 496
			reconnection: parsedUrl.query.reconnection === "true",
			skipWebSocketFrames: parsedUrl.query.skipWebSocketFrames === "true",
		});
		try {
			await this.connect(await protocol.handshake(), protocol);
		} catch (error) {
			protocol.sendMessage({ type: "error", reason: error.message });
			protocol.dispose();
			protocol.getSocket().dispose();
		}
	}

A
Asher 已提交
497
	protected async handleRequest(
498
		base: string,
A
Asher 已提交
499
		requestPath: string,
A
Asher 已提交
500 501
		parsedUrl: url.UrlWithParsedQuery,
		request: http.IncomingMessage,
502 503
	): Promise<Response> {
		switch (base) {
A
Asher 已提交
504
			case "/": return this.getRoot(request, parsedUrl);
A
Asher 已提交
505 506 507 508
			case "/resource":
			case "/vscode-remote-resource":
				if (typeof parsedUrl.query.path === "string") {
					return this.getResource(parsedUrl.query.path);
509
				}
A
Asher 已提交
510
				break;
A
Asher 已提交
511 512 513 514 515
			case "/tar":
				if (typeof parsedUrl.query.path === "string") {
					return this.getTarredResource(parsedUrl.query.path);
				}
				break;
A
Asher 已提交
516
			case "/webview":
517 518 519
				if (requestPath.indexOf("/vscode-resource") === 0) {
					return this.getResource(requestPath.replace(/^\/vscode-resource/, ""));
				}
A
Asher 已提交
520 521 522 523 524
				return this.getResource(
					this.rootPath,
					"out/vs/workbench/contrib/webview/browser/pre",
					requestPath
				);
525
		}
A
Asher 已提交
526
		throw new HttpError("Not found", HttpCode.NotFound);
527
	}
A
Asher 已提交
528

529
	private async getRoot(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise<Response> {
A
Asher 已提交
530
		const filePath = path.join(this.rootPath, "out/vs/code/browser/workbench/workbench.html");
A
Asher 已提交
531 532
		let [content] = await Promise.all([
			util.promisify(fs.readFile)(filePath, "utf8"),
A
Asher 已提交
533 534
			this.servicesPromise,
		]);
A
Asher 已提交
535

536 537
		const logger = this.services.get(ILogService) as ILogService;
		logger.info("request.url", `"${request.url}"`);
A
Asher 已提交
538

539
		const cwd = process.env.VSCODE_CWD || process.cwd();
A
Asher 已提交
540

A
Asher 已提交
541 542
		const remoteAuthority = request.headers.host as string;
		const transformer = getUriTransformer(remoteAuthority);
A
Asher 已提交
543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
		const validatePath = async (filePath: string[] | string | undefined, isDirectory: boolean, unsetFallback?: string): Promise<UriComponents | undefined> => {
			if (!filePath || filePath.length === 0) {
				if (!unsetFallback) {
					return undefined;
				}
				filePath = unsetFallback;
			} else if (Array.isArray(filePath)) {
				filePath = filePath[0];
			}
			const uri = URI.file(sanitizeFilePath(filePath, cwd));
			try {
				const stat = await util.promisify(fs.stat)(uri.fsPath);
				if (isDirectory !== stat.isDirectory()) {
					return undefined;
				}
			} catch (error) {
				return undefined;
			}
			return transformer.transformOutgoing(uri);
		};

		const environment = this.services.get(IEnvironmentService) as IEnvironmentService;
565 566
		const options: Options = {
			WORKBENCH_WEB_CONGIGURATION: {
A
Asher 已提交
567 568
				workspaceUri: await validatePath(parsedUrl.query.workspace, false),
				folderUri: !parsedUrl.query.workspace ? await validatePath(parsedUrl.query.folder, true, this.options.folderUri) : undefined,
569
				remoteAuthority,
A
Asher 已提交
570
				productConfiguration: product,
571
			},
A
Asher 已提交
572 573
			REMOTE_USER_DATA_URI: transformer.transformOutgoing((<EnvironmentService>environment).webUserDataHome),
			NLS_CONFIGURATION: await getNlsConfiguration(environment.args.locale || await getLocaleFromConfig(environment.userDataPath), environment.userDataPath),
574 575
		};

A
Asher 已提交
576
		content = content.replace(/\/static\//g, `/static${product.commit ? `-${product.commit}` : ""}/`).replace("{{WEBVIEW_ENDPOINT}}", "");
A
Asher 已提交
577 578 579
		for (const key in options) {
			content = content.replace(`"{{${key}}}"`, `'${JSON.stringify(options[key as keyof Options])}'`);
		}
580

A
Asher 已提交
581
		return { content, filePath };
A
Asher 已提交
582 583
	}

584
	private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
A
Asher 已提交
585 586 587 588
		if (product.commit && message.commit !== product.commit) {
			throw new Error(`Version mismatch (${message.commit} instead of ${product.commit})`);
		}

589 590 591 592 593 594 595 596
		switch (message.desiredConnectionType) {
			case ConnectionType.ExtensionHost:
			case ConnectionType.Management:
				if (!this.connections.has(message.desiredConnectionType)) {
					this.connections.set(message.desiredConnectionType, new Map());
				}
				const connections = this.connections.get(message.desiredConnectionType)!;

A
Asher 已提交
597 598 599 600 601 602 603
				const ok = async () => {
					return message.desiredConnectionType === ConnectionType.ExtensionHost
						? { debugPort: await this.getDebugPort() }
						: { type: "ok" };
				};

				const token = protocol.options.reconnectionToken;
604
				if (protocol.options.reconnection && connections.has(token)) {
A
Asher 已提交
605
					protocol.sendMessage(await ok());
606 607
					const buffer = protocol.readEntireBuffer();
					protocol.dispose();
A
Asher 已提交
608
					return connections.get(token)!.reconnect(protocol.getSocket(), buffer);
A
Asher 已提交
609
				} else if (protocol.options.reconnection || connections.has(token)) {
610 611 612 613 614 615
					throw new Error(protocol.options.reconnection
						? "Unrecognized reconnection token"
						: "Duplicate reconnection token"
					);
				}

A
Asher 已提交
616
				protocol.sendMessage(await ok());
617 618 619

				let connection: Connection;
				if (message.desiredConnectionType === ConnectionType.Management) {
620
					connection = new ManagementConnection(protocol, token);
621
					this._onDidClientConnect.fire({
A
Asher 已提交
622
						protocol, onDidClientDisconnect: connection.onClose,
623
					});
A
Asher 已提交
624 625 626 627 628
					// NOTE: We can do this because we only have one connection at a
					// time but if that changes we need a way to determine which clients
					// belong to a connection and dispose only those.
					(this.services.get(INodeProxyService) as NodeProxyService)._onUp.fire();
					connection.onClose(() => (this.services.get(INodeProxyService) as NodeProxyService)._onDown.fire());
629
				} else {
A
Asher 已提交
630
					const buffer = protocol.readEntireBuffer();
A
Asher 已提交
631
					connection = new ExtensionHostConnection(
A
Asher 已提交
632
						message.args ? message.args.language : "en",
633
						protocol, buffer, token,
A
Asher 已提交
634 635
						this.services.get(ILogService) as ILogService,
						this.services.get(IEnvironmentService) as IEnvironmentService,
A
Asher 已提交
636
					);
637
				}
A
Asher 已提交
638 639
				connections.set(token, connection);
				connection.onClose(() => connections.delete(token));
640
				this.disposeOldOfflineConnections(connections);
641 642 643 644 645 646
				break;
			case ConnectionType.Tunnel: return protocol.tunnel();
			default: throw new Error("Unrecognized connection type");
		}
	}

647 648 649 650 651 652
	private disposeOldOfflineConnections(connections: Map<string, Connection>): void {
		const offline = Array.from(connections.values())
			.filter((connection) => typeof connection.offline !== "undefined");
		for (let i = 0, max = offline.length - this.maxExtraOfflineConnections; i < max; ++i) {
			offline[i].dispose();
		}
653 654
	}

A
Asher 已提交
655 656 657
	private async initializeServices(args: ParsedArgs): Promise<void> {
		const environmentService = new EnvironmentService(args, process.execPath);
		const logService = new SpdLogService(RemoteExtensionLogFileName, environmentService.logsPath, getLogLevel(environmentService));
A
Asher 已提交
658 659 660
		const fileService = new FileService(logService);
		fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService));

A
Asher 已提交
661 662 663 664 665 666 667 668
		this.allowedRequestPaths.push(
			path.join(environmentService.userDataPath, "clp"), // Language packs.
			environmentService.extensionsPath,
			environmentService.builtinExtensionsPath,
			...environmentService.extraExtensionPaths,
			...environmentService.extraBuiltinExtensionPaths,
		);

A
Asher 已提交
669
		this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService));
A
Asher 已提交
670
		this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());
A
Asher 已提交
671

A
Asher 已提交
672
		const router = new StaticRouter((ctx: any) => ctx.clientId === "renderer");
A
Asher 已提交
673 674 675 676
		this.services.set(ILogService, logService);
		this.services.set(IEnvironmentService, environmentService);
		this.services.set(IConfigurationService, new SyncDescriptor(ConfigurationService, [environmentService.machineSettingsResource]));
		this.services.set(IRequestService, new SyncDescriptor(RequestService));
A
Asher 已提交
677
		this.services.set(IFileService, fileService);
A
Asher 已提交
678
		this.services.set(IProductService, { _serviceBrand: undefined, ...product });
A
Asher 已提交
679
		this.services.set(IDialogService, new DialogChannelClient(this.ipc.getChannel("dialog", router)));
A
Asher 已提交
680
		this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
A
Asher 已提交
681 682
		this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));

A
Asher 已提交
683 684 685 686 687 688 689
		if (!environmentService.args["disable-telemetry"]) {
			this.services.set(ITelemetryService, new SyncDescriptor(TelemetryService, [{
				appender: combinedAppender(
					new AppInsightsAppender("code-server", null, () => new TelemetryClient(), logService),
					new LogAppender(logService),
				),
				commonProperties: resolveCommonProperties(
A
Asher 已提交
690
					product.commit, pkg.codeServerVersion, await getMachineId(),
A
Asher 已提交
691
					[], environmentService.installSourcePath, "code-server",
A
Asher 已提交
692
				),
A
Asher 已提交
693
				piiPaths: this.allowedRequestPaths,
A
Asher 已提交
694 695 696 697 698
			} as ITelemetryServiceConfig]));
		} else {
			this.services.set(ITelemetryService, NullTelemetryService);
		}

A
Asher 已提交
699 700
		await new Promise((resolve) => {
			const instantiationService = new InstantiationService(this.services);
A
Asher 已提交
701 702
			const localizationService = instantiationService.createInstance(LocalizationsService);
			this.services.set(ILocalizationsService, localizationService);
A
Asher 已提交
703 704
			const proxyService = instantiationService.createInstance(NodeProxyService);
			this.services.set(INodeProxyService, proxyService);
A
Asher 已提交
705
			this.ipc.registerChannel("localizations", new LocalizationsChannel(localizationService));
A
Asher 已提交
706 707
			instantiationService.invokeFunction(() => {
				instantiationService.createInstance(LogsDataCleaner);
A
Asher 已提交
708

A
Asher 已提交
709
				const extensionsService = this.services.get(IExtensionManagementService) as IExtensionManagementService;
A
Asher 已提交
710 711
				const telemetryService = this.services.get(ITelemetryService) as ITelemetryService;

A
Asher 已提交
712
				const extensionsChannel = new ExtensionManagementChannel(extensionsService, (context) => getUriTransformer(context.remoteAuthority));
A
Asher 已提交
713
				const extensionsEnvironmentChannel = new ExtensionEnvironmentChannel(environmentService, logService, telemetryService, this.options.connectionToken || "");
A
Asher 已提交
714 715
				const fileChannel = new FileProviderChannel(environmentService, logService);
				const requestChannel = new RequestChannel(this.services.get(IRequestService) as IRequestService);
A
Asher 已提交
716
				const telemetryChannel = new TelemetryChannel(telemetryService);
A
Asher 已提交
717
				const updateChannel = new UpdateChannel(instantiationService.createInstance(UpdateService));
A
Asher 已提交
718
				const nodeProxyChannel = new NodeProxyChannel(proxyService);
A
Asher 已提交
719 720 721 722 723

				this.ipc.registerChannel("extensions", extensionsChannel);
				this.ipc.registerChannel("remoteextensionsenvironment", extensionsEnvironmentChannel);
				this.ipc.registerChannel("request", requestChannel);
				this.ipc.registerChannel("telemetry", telemetryChannel);
724
				this.ipc.registerChannel("nodeProxy", nodeProxyChannel);
A
Asher 已提交
725
				this.ipc.registerChannel("update", updateChannel);
A
Asher 已提交
726
				this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, fileChannel);
A
Asher 已提交
727
				resolve(new ErrorTelemetry(telemetryService));
A
Asher 已提交
728 729 730 731
			});
		});
	}

732 733 734 735 736 737
	/**
	 * TODO: implement.
	 */
	private async getDebugPort(): Promise<number | undefined> {
		return undefined;
	}
A
Asher 已提交
738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812

	/**
	 * Since we can't pass TLS sockets to children, use this to proxy the socket
	 * and pass a non-TLS socket.
	 */
	private createProxy = async (socket: net.Socket): Promise<net.Socket> => {
		if (!(socket instanceof tls.TLSSocket)) {
			return socket;
		}

		await this.startProxyServer();

		return new Promise((resolve, reject) => {
			const timeout = setTimeout(() => {
				listener.dispose();
				socket.destroy();
				proxy.destroy();
				reject(new Error("TLS socket proxy timed out"));
			}, this.proxyTimeout);

			const listener = this._onProxyConnect.event((connection) => {
				connection.once("data", (data) => {
					if (!socket.destroyed && !proxy.destroyed && data.toString() === id) {
						clearTimeout(timeout);
						listener.dispose();
						[[proxy, socket], [socket, proxy]].forEach(([a, b]) => {
							a.pipe(b);
							a.on("error", () => b.destroy());
							a.on("close", () => b.destroy());
							a.on("end", () => b.end());
						});
						resolve(connection);
					}
				});
			});

			const id = generateUuid();
			const proxy = net.connect(this.proxyPipe);
			proxy.once("connect", () => proxy.write(id));
		});
	}

	private async startProxyServer(): Promise<net.Server> {
		if (!this._proxyServer) {
			this._proxyServer = new Promise(async (resolve) => {
				this.proxyPipe = await this.findFreeSocketPath(this.proxyPipe);
				await mkdirp(tmpdir);
				await rimraf(this.proxyPipe);
				const proxyServer = net.createServer((p) => this._onProxyConnect.fire(p));
				proxyServer.once("listening", resolve);
				proxyServer.listen(this.proxyPipe);
			});
		}
		return this._proxyServer;
	}

	private async findFreeSocketPath(basePath: string, maxTries: number = 100): Promise<string> {
		const canConnect = (path: string): Promise<boolean> => {
			return new Promise((resolve) => {
				const socket = net.connect(path);
				socket.once("error", () => resolve(false));
				socket.once("connect", () => {
					socket.destroy();
					resolve(true);
				});
			});
		};

		let i = 0;
		let path = basePath;
		while (await canConnect(path) && i < maxTries) {
			path = `${basePath}-${++i}`;
		}
		return path;
	}
A
Asher 已提交
813
}