2019-07-26 05:39:43 +07:00
|
|
|
import * as crypto from "crypto";
|
2019-06-28 05:34:33 +07:00
|
|
|
import * as fs from "fs";
|
|
|
|
import * as http from "http";
|
2019-07-12 05:12:52 +07:00
|
|
|
import * as https from "https";
|
2019-06-28 05:34:33 +07:00
|
|
|
import * as net from "net";
|
|
|
|
import * as path from "path";
|
2019-08-10 06:50:05 +07:00
|
|
|
import * as querystring from "querystring";
|
2019-07-12 05:12:52 +07:00
|
|
|
import * as tls from "tls";
|
2019-06-28 05:34:33 +07:00
|
|
|
import * as url from "url";
|
2019-08-10 06:50:05 +07:00
|
|
|
import * as util from "util";
|
2019-06-28 05:34:33 +07:00
|
|
|
import { Emitter } from "vs/base/common/event";
|
2019-07-02 22:59:36 +07:00
|
|
|
import { sanitizeFilePath } from "vs/base/common/extpath";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { Schemas } from "vs/base/common/network";
|
|
|
|
import { URI, UriComponents } from "vs/base/common/uri";
|
2019-07-31 05:20:53 +07:00
|
|
|
import { generateUuid } from "vs/base/common/uuid";
|
2019-07-17 02:57:02 +07:00
|
|
|
import { getMachineId } from 'vs/base/node/id';
|
2019-08-03 07:26:41 +07:00
|
|
|
import { NLSConfiguration } from "vs/base/node/languagePacks";
|
2019-07-31 05:20:53 +07:00
|
|
|
import { mkdirp, rimraf } from "vs/base/node/pfs";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { ClientConnectionEvent, IPCServer, StaticRouter } from "vs/base/parts/ipc/common/ipc";
|
2019-07-03 03:26:40 +07:00
|
|
|
import { LogsDataCleaner } from "vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner";
|
2019-07-08 22:33:58 +07:00
|
|
|
import { IConfigurationService } from "vs/platform/configuration/common/configuration";
|
|
|
|
import { ConfigurationService } from "vs/platform/configuration/node/configurationService";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { ExtensionHostDebugBroadcastChannel } from "vs/platform/debug/common/extensionHostDebugIpc";
|
2019-07-08 22:33:58 +07:00
|
|
|
import { IDialogService } from "vs/platform/dialogs/common/dialogs";
|
|
|
|
import { DialogChannelClient } from "vs/platform/dialogs/node/dialogIpc";
|
2019-07-03 03:26:40 +07:00
|
|
|
import { IEnvironmentService, ParsedArgs } from "vs/platform/environment/common/environment";
|
2019-06-29 05:37:23 +07:00
|
|
|
import { EnvironmentService } from "vs/platform/environment/node/environmentService";
|
2019-08-10 06:50:05 +07:00
|
|
|
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";
|
2019-07-08 22:33:58 +07:00
|
|
|
import { ExtensionManagementService } from "vs/platform/extensionManagement/node/extensionManagementService";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { IFileService } from "vs/platform/files/common/files";
|
|
|
|
import { FileService } from "vs/platform/files/common/fileService";
|
|
|
|
import { DiskFileSystemProvider } from "vs/platform/files/node/diskFileSystemProvider";
|
2019-07-08 22:33:58 +07:00
|
|
|
import { SyncDescriptor } from "vs/platform/instantiation/common/descriptors";
|
2019-06-29 05:37:23 +07:00
|
|
|
import { InstantiationService } from "vs/platform/instantiation/common/instantiationService";
|
2019-07-03 03:26:40 +07:00
|
|
|
import { ServiceCollection } from "vs/platform/instantiation/common/serviceCollection";
|
2019-07-08 22:33:58 +07:00
|
|
|
import { ILocalizationsService } from "vs/platform/localizations/common/localizations";
|
|
|
|
import { LocalizationsService } from "vs/platform/localizations/node/localizations";
|
2019-08-03 07:26:41 +07:00
|
|
|
import { LocalizationsChannel } from "vs/platform/localizations/node/localizationsIpc";
|
2019-07-02 06:01:09 +07:00
|
|
|
import { getLogLevel, ILogService } from "vs/platform/log/common/log";
|
2019-06-29 05:37:23 +07:00
|
|
|
import { LogLevelSetterChannel } from "vs/platform/log/common/logIpc";
|
2019-07-02 02:18:30 +07:00
|
|
|
import { SpdLogService } from "vs/platform/log/node/spdlogService";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { IProductConfiguration, IProductService } from "vs/platform/product/common/product";
|
2019-07-17 02:57:02 +07:00
|
|
|
import pkg from "vs/platform/product/node/package";
|
2019-07-03 04:55:54 +07:00
|
|
|
import product from "vs/platform/product/node/product";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { ProductService } from "vs/platform/product/node/productService";
|
2019-07-02 06:01:09 +07:00
|
|
|
import { ConnectionType, ConnectionTypeRequest } from "vs/platform/remote/common/remoteAgentConnection";
|
2019-06-29 05:37:23 +07:00
|
|
|
import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from "vs/platform/remote/common/remoteAgentFileSystemChannel";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { IRequestService } from "vs/platform/request/common/request";
|
|
|
|
import { RequestChannel } from "vs/platform/request/common/requestIpc";
|
2019-07-08 22:33:58 +07:00
|
|
|
import { RequestService } from "vs/platform/request/node/requestService";
|
2019-07-17 02:57:02 +07:00
|
|
|
import ErrorTelemetry from "vs/platform/telemetry/browser/errorTelemetry";
|
2019-07-08 22:33:58 +07:00
|
|
|
import { ITelemetryService } from "vs/platform/telemetry/common/telemetry";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { ITelemetryServiceConfig, TelemetryService } from "vs/platform/telemetry/common/telemetryService";
|
|
|
|
import { combinedAppender, LogAppender, NullTelemetryService } from "vs/platform/telemetry/common/telemetryUtils";
|
2019-07-17 02:57:02 +07:00
|
|
|
import { AppInsightsAppender } from "vs/platform/telemetry/node/appInsightsAppender";
|
|
|
|
import { resolveCommonProperties } from "vs/platform/telemetry/node/commonProperties";
|
2019-08-08 04:18:17 +07:00
|
|
|
import { UpdateChannel } from "vs/platform/update/node/updateIpc";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { ExtensionEnvironmentChannel, FileProviderChannel } from "vs/server/src/channel";
|
|
|
|
import { Connection, ExtensionHostConnection, ManagementConnection } from "vs/server/src/connection";
|
2019-07-17 02:57:02 +07:00
|
|
|
import { TelemetryClient } from "vs/server/src/insights";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { getLocaleFromConfig, getNlsConfiguration } from "vs/server/src/nls";
|
2019-07-12 05:12:52 +07:00
|
|
|
import { Protocol } from "vs/server/src/protocol";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { TelemetryChannel } from "vs/server/src/telemetry";
|
2019-08-08 04:18:17 +07:00
|
|
|
import { UpdateService } from "vs/server/src/update";
|
2019-08-01 03:22:05 +07:00
|
|
|
import { AuthType, getMediaMime, getUriTransformer, localRequire, tmpdir } from "vs/server/src/util";
|
2019-08-10 06:50:05 +07:00
|
|
|
import { RemoteExtensionLogFileName } from "vs/workbench/services/remote/common/remoteAgentService";
|
|
|
|
import { IWorkbenchConstructionOptions } from "vs/workbench/workbench.web.api";
|
2019-06-28 05:34:33 +07:00
|
|
|
|
2019-06-29 05:37:23 +07:00
|
|
|
export enum HttpCode {
|
2019-06-28 05:34:33 +07:00
|
|
|
Ok = 200,
|
2019-07-13 03:21:00 +07:00
|
|
|
Redirect = 302,
|
2019-06-28 05:34:33 +07:00
|
|
|
NotFound = 404,
|
|
|
|
BadRequest = 400,
|
2019-07-13 03:21:00 +07:00
|
|
|
Unauthorized = 401,
|
|
|
|
LargePayload = 413,
|
|
|
|
ServerError = 500,
|
2019-06-28 05:34:33 +07:00
|
|
|
}
|
|
|
|
|
2019-07-02 02:00:11 +07:00
|
|
|
export interface Options {
|
|
|
|
WORKBENCH_WEB_CONGIGURATION: IWorkbenchConstructionOptions;
|
2019-07-02 22:34:03 +07:00
|
|
|
REMOTE_USER_DATA_URI: UriComponents | URI;
|
2019-07-02 02:00:11 +07:00
|
|
|
PRODUCT_CONFIGURATION: IProductConfiguration | null;
|
|
|
|
CONNECTION_AUTH_TOKEN: string;
|
2019-08-03 07:26:41 +07:00
|
|
|
NLS_CONFIGURATION: NLSConfiguration;
|
2019-07-02 02:00:11 +07:00
|
|
|
}
|
|
|
|
|
2019-07-09 03:27:46 +07:00
|
|
|
export interface Response {
|
|
|
|
code?: number;
|
2019-07-13 03:21:00 +07:00
|
|
|
content?: string | Buffer;
|
|
|
|
filePath?: string;
|
|
|
|
headers?: http.OutgoingHttpHeaders;
|
|
|
|
redirect?: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface LoginPayload {
|
|
|
|
password?: string;
|
2019-07-09 03:27:46 +07:00
|
|
|
}
|
|
|
|
|
2019-06-29 05:37:23 +07:00
|
|
|
export class HttpError extends Error {
|
2019-06-28 05:34:33 +07:00
|
|
|
public constructor(message: string, public readonly code: number) {
|
|
|
|
super(message);
|
|
|
|
// @ts-ignore
|
|
|
|
this.name = this.constructor.name;
|
|
|
|
Error.captureStackTrace(this, this.constructor);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-12 05:12:52 +07:00
|
|
|
export interface ServerOptions {
|
2019-07-24 03:38:00 +07:00
|
|
|
readonly auth?: AuthType;
|
2019-07-24 03:17:25 +07:00
|
|
|
readonly basePath?: string;
|
2019-07-12 05:12:52 +07:00
|
|
|
readonly cert?: string;
|
|
|
|
readonly certKey?: string;
|
2019-07-13 06:41:56 +07:00
|
|
|
readonly folderUri?: string;
|
2019-07-24 03:17:25 +07:00
|
|
|
readonly host?: string;
|
|
|
|
readonly password?: string;
|
|
|
|
readonly port?: number;
|
|
|
|
readonly socket?: string;
|
2019-07-12 05:12:52 +07:00
|
|
|
}
|
|
|
|
|
2019-07-03 04:15:41 +07:00
|
|
|
export abstract class Server {
|
2019-07-12 05:12:52 +07:00
|
|
|
protected readonly server: http.Server | https.Server;
|
2019-07-13 03:21:00 +07:00
|
|
|
protected rootPath = path.resolve(__dirname, "../../../..");
|
2019-07-05 22:54:44 +07:00
|
|
|
private listenPromise: Promise<string> | undefined;
|
2019-08-08 23:37:59 +07:00
|
|
|
public readonly protocol: "http" | "https";
|
2019-07-24 03:38:00 +07:00
|
|
|
public readonly options: ServerOptions;
|
|
|
|
|
|
|
|
public constructor(options: ServerOptions) {
|
|
|
|
this.options = {
|
|
|
|
host: options.auth && options.cert ? "0.0.0.0" : "localhost",
|
|
|
|
...options,
|
2019-07-24 07:06:40 +07:00
|
|
|
basePath: options.basePath ? options.basePath.replace(/\/+$/, "") : "",
|
2019-07-24 03:38:00 +07:00
|
|
|
};
|
|
|
|
this.protocol = this.options.cert ? "https" : "http";
|
|
|
|
if (this.protocol === "https") {
|
2019-08-01 03:22:05 +07:00
|
|
|
const httpolyglot = localRequire<typeof import("httpolyglot")>("httpolyglot/lib/index");
|
2019-07-12 05:12:52 +07:00
|
|
|
this.server = httpolyglot.createServer({
|
2019-07-24 03:38:00 +07:00
|
|
|
cert: this.options.cert && fs.readFileSync(this.options.cert),
|
|
|
|
key: this.options.certKey && fs.readFileSync(this.options.certKey),
|
2019-07-12 05:12:52 +07:00
|
|
|
}, this.onRequest);
|
|
|
|
} else {
|
|
|
|
this.server = http.createServer(this.onRequest);
|
|
|
|
}
|
2019-07-03 04:15:41 +07:00
|
|
|
}
|
|
|
|
|
2019-07-05 22:54:44 +07:00
|
|
|
public listen(): Promise<string> {
|
|
|
|
if (!this.listenPromise) {
|
|
|
|
this.listenPromise = new Promise((resolve, reject) => {
|
|
|
|
this.server.on("error", reject);
|
2019-07-26 05:39:43 +07:00
|
|
|
this.server.on("upgrade", this.onUpgrade);
|
2019-07-13 03:21:00 +07:00
|
|
|
const onListen = () => resolve(this.address());
|
2019-07-12 05:12:52 +07:00
|
|
|
if (this.options.socket) {
|
|
|
|
this.server.listen(this.options.socket, onListen);
|
|
|
|
} else {
|
|
|
|
this.server.listen(this.options.port, this.options.host, onListen);
|
|
|
|
}
|
2019-07-05 22:54:44 +07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
return this.listenPromise;
|
2019-07-03 04:15:41 +07:00
|
|
|
}
|
|
|
|
|
2019-07-13 03:21:00 +07:00
|
|
|
/**
|
2019-08-08 23:37:59 +07:00
|
|
|
* The *local* address of the server.
|
2019-07-13 03:21:00 +07:00
|
|
|
*/
|
2019-08-08 23:37:59 +07:00
|
|
|
public address(): string {
|
2019-07-13 03:21:00 +07:00
|
|
|
const address = this.server.address();
|
|
|
|
const endpoint = typeof address !== "string"
|
2019-08-08 23:37:59 +07:00
|
|
|
? (address.address === "::" ? "localhost" : address.address) + ":" + address.port
|
2019-07-13 03:21:00 +07:00
|
|
|
: address;
|
2019-07-22 22:43:18 +07:00
|
|
|
return `${this.protocol}://${endpoint}`;
|
2019-07-13 03:21:00 +07:00
|
|
|
}
|
|
|
|
|
2019-07-26 05:39:43 +07:00
|
|
|
protected abstract handleWebSocket(
|
|
|
|
socket: net.Socket,
|
|
|
|
parsedUrl: url.UrlWithParsedQuery
|
|
|
|
): Promise<void>;
|
|
|
|
|
2019-07-09 04:44:01 +07:00
|
|
|
protected abstract handleRequest(
|
|
|
|
base: string,
|
|
|
|
requestPath: string,
|
|
|
|
parsedUrl: url.UrlWithParsedQuery,
|
|
|
|
request: http.IncomingMessage,
|
|
|
|
): Promise<Response>;
|
|
|
|
|
2019-07-26 05:39:43 +07:00
|
|
|
protected async getResource(...parts: string[]): Promise<Response> {
|
|
|
|
const filePath = path.join(...parts);
|
2019-07-20 05:43:54 +07:00
|
|
|
return { content: await util.promisify(fs.readFile)(filePath), filePath };
|
2019-07-09 04:44:01 +07:00
|
|
|
}
|
2019-07-12 05:12:52 +07:00
|
|
|
|
2019-08-08 23:37:59 +07:00
|
|
|
protected withBase(request: http.IncomingMessage, path: string): string {
|
|
|
|
return `${this.protocol}://${request.headers.host}${this.options.basePath}${path}`;
|
|
|
|
}
|
|
|
|
|
2019-07-12 05:12:52 +07:00
|
|
|
private onRequest = async (request: http.IncomingMessage, response: http.ServerResponse): Promise<void> => {
|
2019-07-13 03:21:00 +07:00
|
|
|
try {
|
|
|
|
const payload = await this.preHandleRequest(request);
|
|
|
|
response.writeHead(payload.redirect ? HttpCode.Redirect : payload.code || HttpCode.Ok, {
|
|
|
|
"Content-Type": getMediaMime(payload.filePath),
|
2019-08-08 23:37:59 +07:00
|
|
|
...(payload.redirect ? { Location: this.withBase(request, payload.redirect) } : {}),
|
2019-08-10 06:50:05 +07:00
|
|
|
...(request.headers["service-worker"] ? { "Service-Worker-Allowed": this.options.basePath + "/" } : {}),
|
2019-07-13 03:21:00 +07:00
|
|
|
...payload.headers,
|
|
|
|
});
|
|
|
|
response.end(payload.content);
|
|
|
|
} 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private async preHandleRequest(request: http.IncomingMessage): Promise<Response> {
|
2019-07-12 05:12:52 +07:00
|
|
|
const secure = (request.connection as tls.TLSSocket).encrypted;
|
2019-07-24 03:38:00 +07:00
|
|
|
if (this.options.cert && !secure) {
|
2019-07-22 22:43:18 +07:00
|
|
|
return { redirect: request.url };
|
2019-07-12 05:12:52 +07:00
|
|
|
}
|
|
|
|
|
2019-07-26 05:39:43 +07:00
|
|
|
const parsedUrl = request.url ? url.parse(request.url, true) : { query: {}};
|
2019-07-13 03:21:00 +07:00
|
|
|
const fullPath = decodeURIComponent(parsedUrl.pathname || "/");
|
|
|
|
const match = fullPath.match(/^(\/?[^/]*)(.*)$/);
|
2019-08-01 03:22:05 +07:00
|
|
|
let [/* ignore */, base, requestPath] = match
|
2019-07-23 04:00:59 +07:00
|
|
|
? match.map((p) => p.replace(/\/+$/, ""))
|
2019-07-13 03:21:00 +07:00
|
|
|
: ["", "", ""];
|
|
|
|
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 = "/";
|
|
|
|
}
|
2019-07-20 05:43:54 +07:00
|
|
|
base = path.normalize(base);
|
2019-07-26 05:39:43 +07:00
|
|
|
requestPath = path.normalize(requestPath || "/index.html");
|
2019-07-13 03:21:00 +07:00
|
|
|
|
|
|
|
switch (base) {
|
|
|
|
case "/":
|
|
|
|
this.ensureGet(request);
|
2019-07-18 00:37:09 +07:00
|
|
|
if (requestPath === "/favicon.ico") {
|
2019-07-26 05:39:43 +07:00
|
|
|
return this.getResource(this.rootPath, "/out/vs/server/src/favicon", requestPath);
|
2019-07-18 00:37:09 +07:00
|
|
|
} else if (!this.authenticate(request)) {
|
2019-07-22 22:43:18 +07:00
|
|
|
return { redirect: "/login" };
|
2019-07-13 03:21:00 +07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case "/login":
|
|
|
|
if (!this.options.auth) {
|
|
|
|
throw new HttpError("Not found", HttpCode.NotFound);
|
2019-08-03 07:54:56 +07:00
|
|
|
} else if (requestPath === "/index.html") {
|
2019-07-13 03:21:00 +07:00
|
|
|
return this.tryLogin(request);
|
|
|
|
}
|
|
|
|
this.ensureGet(request);
|
2019-07-26 05:39:43 +07:00
|
|
|
return this.getResource(this.rootPath, "/out/vs/server/src/login", requestPath);
|
2019-07-13 03:21:00 +07:00
|
|
|
default:
|
|
|
|
this.ensureGet(request);
|
|
|
|
if (!this.authenticate(request)) {
|
2019-07-26 05:39:43 +07:00
|
|
|
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
|
2019-07-13 03:21:00 +07:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2019-07-12 05:12:52 +07:00
|
|
|
|
2019-07-13 03:21:00 +07:00
|
|
|
return this.handleRequest(base, requestPath, parsedUrl, request);
|
|
|
|
}
|
2019-07-12 05:12:52 +07:00
|
|
|
|
2019-07-26 05:39:43 +07:00
|
|
|
private onUpgrade = async (request: http.IncomingMessage, socket: net.Socket): Promise<void> => {
|
|
|
|
try {
|
|
|
|
await this.preHandleWebSocket(request, socket);
|
|
|
|
} catch (error) {
|
|
|
|
socket.destroy();
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private preHandleWebSocket(request: http.IncomingMessage, socket: net.Socket): Promise<void> {
|
|
|
|
socket.on("error", () => socket.destroy());
|
|
|
|
socket.on("end", () => socket.destroy());
|
|
|
|
|
|
|
|
if (!this.authenticate(request)) {
|
|
|
|
throw new HttpError("Unauthorized", HttpCode.Unauthorized);
|
|
|
|
} else if (request.headers.upgrade !== "websocket") {
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-07-13 03:21:00 +07:00
|
|
|
private async tryLogin(request: http.IncomingMessage): Promise<Response> {
|
|
|
|
if (this.authenticate(request)) {
|
|
|
|
this.ensureGet(request);
|
2019-07-22 22:43:18 +07:00
|
|
|
return { redirect: "/" };
|
2019-07-13 03:21:00 +07:00
|
|
|
}
|
|
|
|
if (request.method === "POST") {
|
|
|
|
const data = await this.getData<LoginPayload>(request);
|
|
|
|
if (this.authenticate(request, data)) {
|
|
|
|
return {
|
2019-07-22 22:43:18 +07:00
|
|
|
redirect: "/",
|
2019-07-20 05:43:54 +07:00
|
|
|
headers: {"Set-Cookie": `password=${data.password}` }
|
2019-07-13 03:21:00 +07:00
|
|
|
};
|
2019-07-12 05:12:52 +07:00
|
|
|
}
|
2019-07-13 03:21:00 +07:00
|
|
|
console.error("Failed login attempt", JSON.stringify({
|
|
|
|
xForwardedFor: request.headers["x-forwarded-for"],
|
|
|
|
remoteAddress: request.connection.remoteAddress,
|
2019-07-20 05:43:54 +07:00
|
|
|
userAgent: request.headers["user-agent"],
|
|
|
|
timestamp: Math.floor(new Date().getTime() / 1000),
|
2019-07-13 03:21:00 +07:00
|
|
|
}));
|
|
|
|
return this.getLogin("Invalid password", data);
|
2019-07-12 05:12:52 +07:00
|
|
|
}
|
2019-07-13 03:21:00 +07:00
|
|
|
this.ensureGet(request);
|
|
|
|
return this.getLogin();
|
2019-07-12 05:12:52 +07:00
|
|
|
}
|
|
|
|
|
2019-07-13 03:21:00 +07:00
|
|
|
private async getLogin(error: string = "", payload?: LoginPayload): Promise<Response> {
|
2019-08-03 07:54:56 +07:00
|
|
|
const filePath = path.join(this.rootPath, "out/vs/server/src/login/index.html");
|
2019-07-20 05:43:54 +07:00
|
|
|
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 || ""}"`);
|
2019-07-13 03:21:00 +07:00
|
|
|
return { content, filePath };
|
|
|
|
}
|
|
|
|
|
|
|
|
private ensureGet(request: http.IncomingMessage): void {
|
|
|
|
if (request.method !== "GET") {
|
2019-07-20 05:43:54 +07:00
|
|
|
throw new HttpError(`Unsupported method ${request.method}`, HttpCode.BadRequest);
|
2019-07-13 03:21:00 +07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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) {
|
2019-07-26 05:39:43 +07:00
|
|
|
onError(new HttpError("Payload is too large", HttpCode.LargePayload));
|
2019-07-13 03:21:00 +07:00
|
|
|
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;
|
|
|
|
}
|
2019-08-01 03:22:05 +07:00
|
|
|
const safeCompare = localRequire<typeof import("safe-compare")>("safe-compare/index");
|
2019-07-13 03:21:00 +07:00
|
|
|
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;
|
2019-07-12 05:12:52 +07:00
|
|
|
}
|
2019-07-03 04:15:41 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
export class MainServer extends Server {
|
|
|
|
public readonly _onDidClientConnect = new Emitter<ClientConnectionEvent>();
|
|
|
|
public readonly onDidClientConnect = this._onDidClientConnect.event;
|
|
|
|
private readonly ipc = new IPCServer(this.onDidClientConnect);
|
|
|
|
|
|
|
|
private readonly connections = new Map<ConnectionType, Map<string, Connection>>();
|
|
|
|
|
|
|
|
private readonly services = new ServiceCollection();
|
2019-07-17 02:57:02 +07:00
|
|
|
private readonly servicesPromise: Promise<void>;
|
2019-07-03 04:15:41 +07:00
|
|
|
|
2019-07-31 05:20:53 +07:00
|
|
|
public readonly _onProxyConnect = new Emitter<net.Socket>();
|
|
|
|
private proxyPipe = path.join(tmpdir, "tls-proxy");
|
|
|
|
private _proxyServer?: Promise<net.Server>;
|
|
|
|
private readonly proxyTimeout = 5000;
|
|
|
|
|
2019-07-24 07:06:40 +07:00
|
|
|
public constructor(options: ServerOptions, args: ParsedArgs) {
|
2019-07-12 05:12:52 +07:00
|
|
|
super(options);
|
2019-07-17 02:57:02 +07:00
|
|
|
this.servicesPromise = this.initializeServices(args);
|
2019-07-03 04:15:41 +07:00
|
|
|
}
|
2019-06-28 05:34:33 +07:00
|
|
|
|
2019-07-11 05:00:36 +07:00
|
|
|
public async listen(): Promise<string> {
|
|
|
|
const environment = (this.services.get(IEnvironmentService) as EnvironmentService);
|
2019-07-20 05:43:54 +07:00
|
|
|
const [address] = await Promise.all<string>([
|
|
|
|
super.listen(), ...[
|
|
|
|
environment.extensionsPath,
|
|
|
|
].map((p) => mkdirp(p).then(() => p)),
|
2019-07-11 05:00:36 +07:00
|
|
|
]);
|
|
|
|
return address;
|
|
|
|
}
|
|
|
|
|
2019-07-26 05:39:43 +07:00
|
|
|
protected async handleWebSocket(socket: net.Socket, parsedUrl: url.UrlWithParsedQuery): Promise<void> {
|
2019-07-31 05:20:53 +07:00
|
|
|
const protocol = new Protocol(await this.createProxy(socket), {
|
2019-07-26 05:39:43 +07:00
|
|
|
reconnectionToken: <string>parsedUrl.query.reconnectionToken || "",
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-03 04:15:41 +07:00
|
|
|
protected async handleRequest(
|
2019-07-09 03:27:46 +07:00
|
|
|
base: string,
|
2019-07-03 04:15:41 +07:00
|
|
|
requestPath: string,
|
2019-07-09 04:44:01 +07:00
|
|
|
parsedUrl: url.UrlWithParsedQuery,
|
|
|
|
request: http.IncomingMessage,
|
2019-07-09 03:27:46 +07:00
|
|
|
): Promise<Response> {
|
|
|
|
switch (base) {
|
2019-07-13 03:21:00 +07:00
|
|
|
case "/": return this.getRoot(request, parsedUrl);
|
2019-08-10 06:50:05 +07:00
|
|
|
case "/vscode-resources": return this.getResource(requestPath);
|
2019-07-24 07:06:40 +07:00
|
|
|
case "/webview":
|
2019-08-01 03:50:25 +07:00
|
|
|
if (requestPath.indexOf("/vscode-resource") === 0) {
|
|
|
|
return this.getResource(requestPath.replace(/^\/vscode-resource/, ""));
|
|
|
|
}
|
2019-07-26 05:39:43 +07:00
|
|
|
return this.getResource(
|
|
|
|
this.rootPath,
|
|
|
|
"out/vs/workbench/contrib/webview/browser/pre",
|
|
|
|
requestPath
|
|
|
|
);
|
|
|
|
default:
|
|
|
|
return this.getResource(this.rootPath, base, requestPath);
|
2019-07-09 03:27:46 +07:00
|
|
|
}
|
|
|
|
}
|
2019-06-28 05:34:33 +07:00
|
|
|
|
2019-07-09 03:27:46 +07:00
|
|
|
private async getRoot(request: http.IncomingMessage, parsedUrl: url.UrlWithParsedQuery): Promise<Response> {
|
2019-07-13 03:21:00 +07:00
|
|
|
const filePath = path.join(this.rootPath, "out/vs/code/browser/workbench/workbench.html");
|
2019-07-20 05:43:54 +07:00
|
|
|
let [content] = await Promise.all([
|
|
|
|
util.promisify(fs.readFile)(filePath, "utf8"),
|
2019-07-17 02:57:02 +07:00
|
|
|
this.servicesPromise,
|
|
|
|
]);
|
2019-08-03 07:26:41 +07:00
|
|
|
const environment = this.services.get(IEnvironmentService) as IEnvironmentService;
|
|
|
|
const locale = environment.args.locale || await getLocaleFromConfig(environment.userDataPath);
|
2019-07-09 03:27:46 +07:00
|
|
|
const cwd = process.env.VSCODE_CWD || process.cwd();
|
|
|
|
const workspacePath = parsedUrl.query.workspace as string | undefined;
|
2019-07-13 06:41:56 +07:00
|
|
|
const folderPath = !workspacePath ? parsedUrl.query.folder as string | undefined || this.options.folderUri || cwd: undefined;
|
2019-07-20 05:43:54 +07:00
|
|
|
const remoteAuthority = request.headers.host as string;
|
2019-08-08 23:37:59 +07:00
|
|
|
const webviewEndpoint = this.withBase(request, "/webview/");
|
2019-07-20 05:43:54 +07:00
|
|
|
const transformer = getUriTransformer(remoteAuthority);
|
2019-07-09 03:27:46 +07:00
|
|
|
const options: Options = {
|
|
|
|
WORKBENCH_WEB_CONGIGURATION: {
|
|
|
|
workspaceUri: workspacePath
|
|
|
|
? transformer.transformOutgoing(URI.file(sanitizeFilePath(workspacePath, cwd)))
|
|
|
|
: undefined,
|
|
|
|
folderUri: folderPath
|
|
|
|
? transformer.transformOutgoing(URI.file(sanitizeFilePath(folderPath, cwd)))
|
|
|
|
: undefined,
|
|
|
|
remoteAuthority,
|
|
|
|
webviewEndpoint,
|
|
|
|
},
|
|
|
|
REMOTE_USER_DATA_URI: transformer.transformOutgoing(
|
|
|
|
(this.services.get(IEnvironmentService) as EnvironmentService).webUserDataHome,
|
|
|
|
),
|
2019-08-08 04:18:17 +07:00
|
|
|
PRODUCT_CONFIGURATION: {
|
|
|
|
...product,
|
|
|
|
// @ts-ignore workaround for getting the VS Code version to the browser.
|
|
|
|
version: pkg.version,
|
|
|
|
},
|
2019-07-09 03:27:46 +07:00
|
|
|
CONNECTION_AUTH_TOKEN: "",
|
2019-08-03 07:26:41 +07:00
|
|
|
NLS_CONFIGURATION: await getNlsConfiguration(locale, environment.userDataPath),
|
2019-07-09 03:27:46 +07:00
|
|
|
};
|
|
|
|
|
2019-08-10 06:50:05 +07:00
|
|
|
for (const key in options) {
|
|
|
|
content = content.replace(`"{{${key}}}"`, `'${JSON.stringify(options[key as keyof Options])}'`);
|
|
|
|
}
|
2019-07-09 03:27:46 +07:00
|
|
|
content = content.replace('{{WEBVIEW_ENDPOINT}}', webviewEndpoint);
|
|
|
|
|
2019-07-13 03:21:00 +07:00
|
|
|
return { content, filePath };
|
2019-06-28 05:34:33 +07:00
|
|
|
}
|
|
|
|
|
2019-07-02 06:01:09 +07:00
|
|
|
private async connect(message: ConnectionTypeRequest, protocol: Protocol): Promise<void> {
|
2019-08-10 06:50:05 +07:00
|
|
|
if (product.commit && message.commit !== product.commit) {
|
|
|
|
throw new Error(`Version mismatch (${message.commit} instead of ${product.commit})`);
|
|
|
|
}
|
|
|
|
|
2019-07-02 06:01:09 +07:00
|
|
|
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)!;
|
|
|
|
|
2019-07-20 05:43:54 +07:00
|
|
|
const ok = async () => {
|
|
|
|
return message.desiredConnectionType === ConnectionType.ExtensionHost
|
|
|
|
? { debugPort: await this.getDebugPort() }
|
|
|
|
: { type: "ok" };
|
|
|
|
};
|
|
|
|
|
|
|
|
const token = protocol.options.reconnectionToken;
|
2019-07-02 06:01:09 +07:00
|
|
|
if (protocol.options.reconnection && connections.has(token)) {
|
2019-07-20 05:43:54 +07:00
|
|
|
protocol.sendMessage(await ok());
|
2019-07-02 06:01:09 +07:00
|
|
|
const buffer = protocol.readEntireBuffer();
|
|
|
|
protocol.dispose();
|
2019-07-16 06:55:30 +07:00
|
|
|
return connections.get(token)!.reconnect(protocol.getSocket(), buffer);
|
2019-07-20 05:43:54 +07:00
|
|
|
} else if (protocol.options.reconnection || connections.has(token)) {
|
2019-07-02 06:01:09 +07:00
|
|
|
throw new Error(protocol.options.reconnection
|
|
|
|
? "Unrecognized reconnection token"
|
|
|
|
: "Duplicate reconnection token"
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-07-20 05:43:54 +07:00
|
|
|
protocol.sendMessage(await ok());
|
2019-07-02 06:01:09 +07:00
|
|
|
|
|
|
|
let connection: Connection;
|
|
|
|
if (message.desiredConnectionType === ConnectionType.Management) {
|
|
|
|
connection = new ManagementConnection(protocol);
|
|
|
|
this._onDidClientConnect.fire({
|
2019-07-20 05:43:54 +07:00
|
|
|
protocol, onDidClientDisconnect: connection.onClose,
|
2019-07-02 06:01:09 +07:00
|
|
|
});
|
|
|
|
} else {
|
2019-07-16 06:55:30 +07:00
|
|
|
const buffer = protocol.readEntireBuffer();
|
2019-07-03 04:15:41 +07:00
|
|
|
connection = new ExtensionHostConnection(
|
2019-08-03 07:26:41 +07:00
|
|
|
message.args ? message.args.language : "en",
|
|
|
|
protocol, buffer,
|
|
|
|
this.services.get(ILogService) as ILogService,
|
|
|
|
this.services.get(IEnvironmentService) as IEnvironmentService,
|
2019-07-03 04:15:41 +07:00
|
|
|
);
|
2019-07-02 06:01:09 +07:00
|
|
|
}
|
2019-07-20 05:43:54 +07:00
|
|
|
connections.set(token, connection);
|
|
|
|
connection.onClose(() => connections.delete(token));
|
2019-07-02 06:01:09 +07:00
|
|
|
break;
|
|
|
|
case ConnectionType.Tunnel: return protocol.tunnel();
|
|
|
|
default: throw new Error("Unrecognized connection type");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-17 02:57:02 +07:00
|
|
|
private async initializeServices(args: ParsedArgs): Promise<void> {
|
|
|
|
const environmentService = new EnvironmentService(args, process.execPath);
|
|
|
|
const logService = new SpdLogService(RemoteExtensionLogFileName, environmentService.logsPath, getLogLevel(environmentService));
|
2019-08-10 06:50:05 +07:00
|
|
|
const fileService = new FileService(logService);
|
|
|
|
fileService.registerProvider(Schemas.file, new DiskFileSystemProvider(logService));
|
|
|
|
|
2019-07-17 02:57:02 +07:00
|
|
|
this.ipc.registerChannel("loglevel", new LogLevelSetterChannel(logService));
|
2019-08-10 06:50:05 +07:00
|
|
|
this.ipc.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel());
|
2019-07-17 02:57:02 +07:00
|
|
|
|
2019-08-10 06:50:05 +07:00
|
|
|
const router = new StaticRouter((ctx: any) => ctx.clientId === "renderer");
|
2019-07-17 02:57:02 +07:00
|
|
|
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));
|
2019-08-10 06:50:05 +07:00
|
|
|
this.services.set(IFileService, fileService);
|
|
|
|
this.services.set(IProductService, new SyncDescriptor(ProductService));
|
|
|
|
this.services.set(IDialogService, new DialogChannelClient(this.ipc.getChannel("dialog", router)));
|
2019-07-17 02:57:02 +07:00
|
|
|
this.services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
|
2019-08-10 06:50:05 +07:00
|
|
|
this.services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
|
|
|
|
|
2019-07-17 02:57:02 +07:00
|
|
|
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(
|
2019-08-08 04:18:17 +07:00
|
|
|
product.commit, pkg.codeServerVersion, await getMachineId(),
|
2019-07-17 02:57:02 +07:00
|
|
|
environmentService.installSourcePath, "code-server",
|
|
|
|
),
|
|
|
|
piiPaths: [
|
|
|
|
environmentService.appRoot,
|
|
|
|
environmentService.extensionsPath,
|
|
|
|
...environmentService.extraExtensionPaths,
|
|
|
|
...environmentService.extraBuiltinExtensionPaths,
|
|
|
|
],
|
|
|
|
} as ITelemetryServiceConfig]));
|
|
|
|
} else {
|
|
|
|
this.services.set(ITelemetryService, NullTelemetryService);
|
|
|
|
}
|
|
|
|
|
2019-07-20 05:43:54 +07:00
|
|
|
await new Promise((resolve) => {
|
|
|
|
const instantiationService = new InstantiationService(this.services);
|
2019-08-03 07:26:41 +07:00
|
|
|
const localizationService = instantiationService.createInstance(LocalizationsService);
|
|
|
|
this.services.set(ILocalizationsService, localizationService);
|
|
|
|
this.ipc.registerChannel("localizations", new LocalizationsChannel(localizationService));
|
2019-07-17 02:57:02 +07:00
|
|
|
instantiationService.invokeFunction(() => {
|
|
|
|
instantiationService.createInstance(LogsDataCleaner);
|
2019-08-10 06:50:05 +07:00
|
|
|
|
2019-07-17 02:57:02 +07:00
|
|
|
const extensionsService = this.services.get(IExtensionManagementService) as IExtensionManagementService;
|
2019-08-10 06:50:05 +07:00
|
|
|
const telemetryService = this.services.get(ITelemetryService) as ITelemetryService;
|
|
|
|
|
2019-07-17 02:57:02 +07:00
|
|
|
const extensionsChannel = new ExtensionManagementChannel(extensionsService, (context) => getUriTransformer(context.remoteAuthority));
|
2019-08-10 06:50:05 +07:00
|
|
|
const extensionsEnvironmentChannel = new ExtensionEnvironmentChannel(environmentService, logService, telemetryService);
|
|
|
|
const fileChannel = new FileProviderChannel(environmentService, logService);
|
|
|
|
const requestChannel = new RequestChannel(this.services.get(IRequestService) as IRequestService);
|
2019-07-17 02:57:02 +07:00
|
|
|
const telemetryChannel = new TelemetryChannel(telemetryService);
|
2019-08-08 04:18:17 +07:00
|
|
|
const updateChannel = new UpdateChannel(instantiationService.createInstance(UpdateService));
|
2019-08-10 06:50:05 +07:00
|
|
|
|
|
|
|
this.ipc.registerChannel("extensions", extensionsChannel);
|
|
|
|
this.ipc.registerChannel("remoteextensionsenvironment", extensionsEnvironmentChannel);
|
|
|
|
this.ipc.registerChannel("request", requestChannel);
|
|
|
|
this.ipc.registerChannel("telemetry", telemetryChannel);
|
2019-08-08 04:18:17 +07:00
|
|
|
this.ipc.registerChannel("update", updateChannel);
|
2019-08-10 06:50:05 +07:00
|
|
|
this.ipc.registerChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME, fileChannel);
|
2019-07-20 05:43:54 +07:00
|
|
|
resolve(new ErrorTelemetry(telemetryService));
|
2019-07-17 02:57:02 +07:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-07-02 06:01:09 +07:00
|
|
|
/**
|
|
|
|
* TODO: implement.
|
|
|
|
*/
|
|
|
|
private async getDebugPort(): Promise<number | undefined> {
|
|
|
|
return undefined;
|
|
|
|
}
|
2019-07-31 05:20:53 +07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
2019-06-28 05:34:33 +07:00
|
|
|
}
|