Vscode merge (#4582)

* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd

* fix issues with merges

* bump node version in azpipe

* replace license headers

* remove duplicate launch task

* fix build errors

* fix build errors

* fix tslint issues

* working through package and linux build issues

* more work

* wip

* fix packaged builds

* working through linux build errors

* wip

* wip

* wip

* fix mac and linux file limits

* iterate linux pipeline

* disable editor typing

* revert series to parallel

* remove optimize vscode from linux

* fix linting issues

* revert testing change

* add work round for new node

* readd packaging for extensions

* fix issue with angular not resolving decorator dependencies
This commit is contained in:
Anthony Dresser
2019-03-19 17:44:35 -07:00
committed by GitHub
parent 833d197412
commit 87765e8673
1879 changed files with 54505 additions and 38058 deletions

View File

@@ -3,10 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { Emitter } from 'vs/base/common/event';
export class DeltaExtensionsResult {
constructor(
public readonly removedDueToLooping: IExtensionDescription[]
) { }
}
export class ExtensionDescriptionRegistry {
private readonly _onDidChange = new Emitter<void>();
public readonly onDidChange = this._onDidChange.event;
@@ -60,19 +65,120 @@ export class ExtensionDescriptionRegistry {
this._onDidChange.fire(undefined);
}
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]) {
this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
const toRemoveSet = new Set<string>();
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId)));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult {
if (toAdd.length > 0) {
this._extensionDescriptions = this._extensionDescriptions.concat(toAdd);
}
// Immediately remove looping extensions!
const looping = ExtensionDescriptionRegistry._findLoopingExtensions(this._extensionDescriptions);
toRemove = toRemove.concat(looping.map(ext => ext.identifier));
if (toRemove.length > 0) {
const toRemoveSet = new Set<string>();
toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId)));
this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier)));
}
this._initialize();
this._onDidChange.fire(undefined);
return new DeltaExtensionsResult(looping);
}
private static _findLoopingExtensions(extensionDescriptions: IExtensionDescription[]): IExtensionDescription[] {
const G = new class {
private _arcs = new Map<string, string[]>();
private _nodesSet = new Set<string>();
private _nodesArr: string[] = [];
addNode(id: string): void {
if (!this._nodesSet.has(id)) {
this._nodesSet.add(id);
this._nodesArr.push(id);
}
}
addArc(from: string, to: string): void {
this.addNode(from);
this.addNode(to);
if (this._arcs.has(from)) {
this._arcs.get(from)!.push(to);
} else {
this._arcs.set(from, [to]);
}
}
getArcs(id: string): string[] {
if (this._arcs.has(id)) {
return this._arcs.get(id)!;
}
return [];
}
hasOnlyGoodArcs(id: string, good: Set<string>): boolean {
const dependencies = G.getArcs(id);
for (let i = 0; i < dependencies.length; i++) {
if (!good.has(dependencies[i])) {
return false;
}
}
return true;
}
getNodes(): string[] {
return this._nodesArr;
}
};
let descs = new Map<string, IExtensionDescription>();
for (let extensionDescription of extensionDescriptions) {
const extensionId = ExtensionIdentifier.toKey(extensionDescription.identifier);
descs.set(extensionId, extensionDescription);
if (extensionDescription.extensionDependencies) {
for (let _depId of extensionDescription.extensionDependencies) {
const depId = ExtensionIdentifier.toKey(_depId);
G.addArc(extensionId, depId);
}
}
}
// initialize with all extensions with no dependencies.
let good = new Set<string>();
G.getNodes().filter(id => G.getArcs(id).length === 0).forEach(id => good.add(id));
// all other extensions will be processed below.
let nodes = G.getNodes().filter(id => !good.has(id));
let madeProgress: boolean;
do {
madeProgress = false;
// find one extension which has only good deps
for (let i = 0; i < nodes.length; i++) {
const id = nodes[i];
if (G.hasOnlyGoodArcs(id, good)) {
nodes.splice(i, 1);
i--;
good.add(id);
madeProgress = true;
}
}
} while (madeProgress);
// The remaining nodes are bad and have loops
return nodes.map(id => descs.get(id)!);
}
public containsActivationEvent(activationEvent: string): boolean {
return this._activationMap.has(activationEvent);
}
public containsExtension(extensionId: ExtensionIdentifier): boolean {
return this._extensionsMap.has(ExtensionIdentifier.toKey(extensionId));
}
public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] {
const extensions = this._activationMap.get(activationEvent);
return extensions ? extensions.slice(0) : [];
@@ -82,8 +188,8 @@ export class ExtensionDescriptionRegistry {
return this._extensionsArr.slice(0);
}
public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | null {
public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined {
const extension = this._extensionsMap.get(ExtensionIdentifier.toKey(extensionId));
return extension ? extension : null;
return extension ? extension : undefined;
}
}

View File

@@ -15,8 +15,8 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
// we don't (yet) throw when extensions parse
// uris that have no scheme
@@ -49,7 +49,6 @@ export class ExtensionHostMain {
private _isTerminating: boolean;
private readonly _environment: IEnvironment;
private readonly _extensionService: ExtHostExtensionService;
private readonly _extHostConfiguration: ExtHostConfiguration;
private readonly _extHostLogService: ExtHostLogService;
private disposables: IDisposable[] = [];
@@ -57,14 +56,14 @@ export class ExtensionHostMain {
constructor(protocol: IMessagePassingProtocol, initData: IInitData) {
this._isTerminating = false;
const uriTransformer: IURITransformer = null;
const uriTransformer: IURITransformer | null = null;
const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer);
// ensure URIs are transformed and revived
initData = this.transform(initData, rpcProtocol);
this._environment = initData.environment;
const allowExit = !!this._environment.extensionTestsPath; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708)
const allowExit = !!this._environment.extensionTestsLocationURI; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708)
patchProcess(allowExit);
this._patchPatchedConsole(rpcProtocol.getProxy(MainContext.MainThreadConsole));
@@ -74,13 +73,13 @@ export class ExtensionHostMain {
this.disposables.push(this._extHostLogService);
this._searchRequestIdProvider = new Counter();
const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, initData.workspace, this._extHostLogService, this._searchRequestIdProvider);
const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, this._extHostLogService, this._searchRequestIdProvider, initData.workspace);
this._extHostLogService.info('extension host started');
this._extHostLogService.trace('initData', initData);
this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace);
this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService);
const extHostConfiguraiton = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace);
this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, extHostConfiguraiton, this._extHostLogService);
// error forwarding and stack trace scanning
Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API)
@@ -88,7 +87,7 @@ export class ExtensionHostMain {
this._extensionService.getExtensionPathIndex().then(map => {
(<any>Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => {
let stackTraceMessage = '';
let extension: IExtensionDescription;
let extension: IExtensionDescription | undefined;
let fileName: string;
for (const call of stackTrace) {
stackTraceMessage += `\n\tat ${call.toString()}`;
@@ -118,7 +117,7 @@ export class ExtensionHostMain {
private _patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void {
// The console is already patched to use `process.send()`
const nativeProcessSend = process.send;
const nativeProcessSend = process.send!;
process.send = (...args: any[]) => {
if (args.length === 0 || !args[0] || args[0].type !== '__$console') {
return nativeProcessSend.apply(process, args);
@@ -154,7 +153,9 @@ export class ExtensionHostMain {
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome));
initData.environment.extensionDevelopmentLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionDevelopmentLocationURI));
initData.environment.extensionTestsLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI));
initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
initData.environment.userHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.userHome));
initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation));
initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace);
return initData;

View File

@@ -9,9 +9,9 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { Event } from 'vs/base/common/event';
import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc';
import { Protocol } from 'vs/base/parts/ipc/node/ipc.net';
import product from 'vs/platform/node/product';
import product from 'vs/platform/product/node/product';
import { IInitData } from 'vs/workbench/api/node/extHost.protocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol';
import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/node/extensionHostProtocol';
import { exit, ExtensionHostMain } from 'vs/workbench/services/extensions/node/extensionHostMain';
// With Electron 2.x and node.js 8.x the "natives" module
@@ -45,7 +45,7 @@ let onTerminate = function () {
function createExtHostProtocol(): Promise<IMessagePassingProtocol> {
const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST;
const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST!;
return new Promise<IMessagePassingProtocol>((resolve, reject) => {
@@ -107,9 +107,14 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise<IRenderer
setTimeout(() => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
unhandledPromises.splice(idx, 1);
console.warn('rejected promise not handled within 1 second');
onUnexpectedError(reason);
promise.catch(e => {
unhandledPromises.splice(idx, 1);
console.warn(`rejected promise not handled within 1 second: ${e}`);
if (e.stack) {
console.warn(`stack trace: ${e.stack}`);
}
onUnexpectedError(reason);
});
}
}, 1000);
});

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export const enum MessageType {
Initialized,
Ready,
Terminate
}
export function createMessageOfType(type: MessageType): Buffer {
const result = Buffer.allocUnsafe(1);
switch (type) {
case MessageType.Initialized: result.writeUInt8(1, 0); break;
case MessageType.Ready: result.writeUInt8(2, 0); break;
case MessageType.Terminate: result.writeUInt8(3, 0); break;
}
return result;
}
export function isMessageOfType(message: Buffer, type: MessageType): boolean {
if (message.length !== 1) {
return false;
}
switch (message.readUInt8(0)) {
case 1: return type === MessageType.Initialized;
case 2: return type === MessageType.Ready;
case 3: return type === MessageType.Terminate;
default: return false;
}
}

View File

@@ -1,45 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Schemas } from 'vs/base/common/network';
import { URI } from 'vs/base/common/uri';
import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc';
import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService';
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
import { IChannel } from 'vs/base/parts/ipc/node/ipc';
const localExtensionManagementServerAuthority: string = 'vscode-local';
export class ExtensionManagementServerService implements IExtensionManagementServerService {
_serviceBrand: any;
readonly localExtensionManagementServer: IExtensionManagementServer;
readonly remoteExtensionManagementServer: IExtensionManagementServer | null = null;
constructor(
localExtensionManagementService: IExtensionManagementService,
@IRemoteAgentService remoteAgentService: IRemoteAgentService
) {
this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, authority: localExtensionManagementServerAuthority, label: localize('local', "Local") };
const remoteAgentConnection = remoteAgentService.getConnection();
if (remoteAgentConnection) {
const extensionManagementService = new ExtensionManagementChannelClient(remoteAgentConnection.getChannel<IChannel>('extensions'));
this.remoteExtensionManagementServer = { authority: remoteAgentConnection.remoteAuthority, extensionManagementService, label: remoteAgentConnection.remoteAuthority };
}
}
getExtensionManagementServer(location: URI): IExtensionManagementServer | null {
if (location.scheme === Schemas.file) {
return this.localExtensionManagementServer;
}
if (location.scheme === REMOTE_HOST_SCHEME) {
return this.remoteExtensionManagementServer;
}
return null;
}
}

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as path from 'path';
import * as path from 'vs/base/common/path';
import * as semver from 'semver';
import * as json from 'vs/base/common/json';
import * as arrays from 'vs/base/common/arrays';
@@ -14,8 +14,7 @@ import { URI } from 'vs/base/common/uri';
import * as pfs from 'vs/base/node/pfs';
import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, ExtensionIdentifierWithVersion, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
const MANIFEST_FILE = 'package.json';
@@ -451,7 +450,7 @@ export class ExtensionScannerInput {
constructor(
public readonly ourVersion: string,
public readonly commit: string | undefined,
public readonly commit: string | null | undefined,
public readonly locale: string | undefined,
public readonly devMode: boolean,
public readonly absoluteFolderPath: string,

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { isUIExtension as _isUIExtension } from 'vs/platform/extensions/node/extensionsUtil';
export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean {
const uiExtensionPoints = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name);
return _isUIExtension(manifest, uiExtensionPoints, configurationService);
}

View File

@@ -5,10 +5,15 @@
import * as http from 'http';
import * as https from 'https';
import * as tls from 'tls';
import * as nodeurl from 'url';
import * as os from 'os';
import * as fs from 'fs';
import * as cp from 'child_process';
import { assign } from 'vs/base/common/objects';
import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace';
import { endsWith } from 'vs/base/common/strings';
import { IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace';
import { ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration';
import { ProxyAgent } from 'vscode-proxy-agent';
import { MainThreadTelemetryShape } from 'vs/workbench/api/node/extHost.protocol';
@@ -16,6 +21,7 @@ import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService';
import { URI } from 'vs/base/common/uri';
import { promisify } from 'util';
interface ConnectionResult {
proxy: string;
@@ -25,34 +31,37 @@ interface ConnectionResult {
}
export function connectProxyResolver(
extHostWorkspace: ExtHostWorkspace,
extHostWorkspace: IExtHostWorkspaceProvider,
configProvider: ExtHostConfigProvider,
extensionService: ExtHostExtensionService,
extHostLogService: ExtHostLogService,
mainThreadTelemetry: MainThreadTelemetryShape
) {
const agents = createProxyAgents(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry);
const lookup = createPatchedModules(configProvider, agents);
const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry);
const lookup = createPatchedModules(configProvider, resolveProxy);
return configureModuleLoading(extensionService, lookup);
}
const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'.
function createProxyAgents(
extHostWorkspace: ExtHostWorkspace,
function setupProxyResolution(
extHostWorkspace: IExtHostWorkspaceProvider,
configProvider: ExtHostConfigProvider,
extHostLogService: ExtHostLogService,
mainThreadTelemetry: MainThreadTelemetryShape
) {
const env = process.env;
let settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http')
.get<string>('proxy'));
configProvider.onDidChangeConfiguration(e => {
settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http')
.get<string>('proxy'));
});
const env = process.env;
let envProxy = proxyFromConfigURL(env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY); // Not standardized.
let envNoProxy = noProxyFromEnv(env.no_proxy || env.NO_PROXY); // Not standardized.
let cacheRolls = 0;
let oldCache = new Map<string, string>();
let cache = new Map<string, string>();
@@ -90,6 +99,7 @@ function createProxyAgents(
let envCount = 0;
let settingsCount = 0;
let localhostCount = 0;
let envNoProxyCount = 0;
let results: ConnectionResult[] = [];
function logEvent() {
timeout = undefined;
@@ -104,19 +114,32 @@ function createProxyAgents(
"envCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"settingsCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"localhostCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"envNoProxyCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"results": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
mainThreadTelemetry.$publicLog('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, results });
count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = 0;
mainThreadTelemetry.$publicLog('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, envNoProxyCount, results });
count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = envNoProxyCount = 0;
results = [];
}
function resolveProxy(req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) {
function resolveProxy(flags: { useProxySettings: boolean, useSystemCertificates: boolean }, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) {
if (!timeout) {
timeout = setTimeout(logEvent, 10 * 60 * 1000);
}
useSystemCertificates(extHostLogService, flags.useSystemCertificates, opts, () => {
useProxySettings(flags.useProxySettings, req, opts, url, callback);
});
}
function useProxySettings(useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) {
if (!useProxySettings) {
callback('DIRECT');
return;
}
const parsedUrl = nodeurl.parse(url); // Coming from Node's URL, sticking with that.
const hostname = parsedUrl.hostname;
@@ -127,6 +150,13 @@ function createProxyAgents(
return;
}
if (envNoProxy(hostname, String(parsedUrl.port || (<any>opts.agent).defaultPort))) {
envNoProxyCount++;
callback('DIRECT');
extHostLogService.trace('ProxyResolver#resolveProxy envNoProxy', url, 'DIRECT');
return;
}
if (settingsProxy) {
settingsCount++;
callback(settingsProxy);
@@ -168,11 +198,7 @@ function createProxyAgents(
});
}
const httpAgent: http.Agent = new ProxyAgent({ resolveProxy });
(<any>httpAgent).defaultPort = 80;
const httpsAgent: http.Agent = new ProxyAgent({ resolveProxy });
(<any>httpsAgent).defaultPort = 443;
return { http: httpAgent, https: httpsAgent };
return resolveProxy;
}
function collectResult(results: ConnectionResult[], resolveProxy: string, connection: string, req: http.ClientRequest) {
@@ -189,7 +215,7 @@ function collectResult(results: ConnectionResult[], resolveProxy: string, connec
});
}
function findOrCreateResult(results: ConnectionResult[], proxy: string, connection: string, code: string): ConnectionResult | undefined {
function findOrCreateResult(results: ConnectionResult[], proxy: string, connection: string, code: string): ConnectionResult {
for (const result of results) {
if (result.proxy === proxy && result.connection === connection && result.code === code) {
return result;
@@ -200,7 +226,7 @@ function findOrCreateResult(results: ConnectionResult[], proxy: string, connecti
return result;
}
function proxyFromConfigURL(configURL: string) {
function proxyFromConfigURL(configURL: string | undefined) {
const url = (configURL || '').trim();
const i = url.indexOf('://');
if (i === -1) {
@@ -218,35 +244,70 @@ function proxyFromConfigURL(configURL: string) {
return undefined;
}
function createPatchedModules(configProvider: ExtHostConfigProvider, agents: { http: http.Agent; https: http.Agent; }) {
const setting = {
function noProxyFromEnv(envValue?: string) {
const value = (envValue || '')
.trim()
.toLowerCase();
if (value === '*') {
return () => true;
}
const filters = value
.split(',')
.map(s => s.trim().split(':', 2))
.map(([name, port]) => ({ name, port }))
.filter(filter => !!filter.name)
.map(({ name, port }) => {
const domain = name[0] === '.' ? name : `.${name}`;
return { domain, port };
});
if (!filters.length) {
return () => false;
}
return (hostname: string, port: string) => filters.some(({ domain, port: filterPort }) => {
return endsWith(`.${hostname.toLowerCase()}`, domain) && (!filterPort || port === filterPort);
});
}
function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProxy: ReturnType<typeof setupProxyResolution>) {
const proxySetting = {
config: configProvider.getConfiguration('http')
.get<string>('proxySupport') || 'off'
};
configProvider.onDidChangeConfiguration(e => {
setting.config = configProvider.getConfiguration('http')
proxySetting.config = configProvider.getConfiguration('http')
.get<string>('proxySupport') || 'off';
});
const certSetting = {
config: !!configProvider.getConfiguration('http')
.get<boolean>('systemCertificates')
};
configProvider.onDidChangeConfiguration(e => {
certSetting.config = !!configProvider.getConfiguration('http')
.get<string>('systemCertificates');
});
return {
http: {
off: assign({}, http, patches(http, agents.http, agents.https, { config: 'off' }, true)),
on: assign({}, http, patches(http, agents.http, agents.https, { config: 'on' }, true)),
override: assign({}, http, patches(http, agents.http, agents.https, { config: 'override' }, true)),
onRequest: assign({}, http, patches(http, agents.http, agents.https, setting, true)),
default: assign(http, patches(http, agents.http, agents.https, setting, false)) // run last
off: assign({}, http, patches(http, resolveProxy, { config: 'off' }, certSetting, true)),
on: assign({}, http, patches(http, resolveProxy, { config: 'on' }, certSetting, true)),
override: assign({}, http, patches(http, resolveProxy, { config: 'override' }, certSetting, true)),
onRequest: assign({}, http, patches(http, resolveProxy, proxySetting, certSetting, true)),
default: assign(http, patches(http, resolveProxy, proxySetting, certSetting, false)) // run last
},
https: {
off: assign({}, https, patches(https, agents.https, agents.http, { config: 'off' }, true)),
on: assign({}, https, patches(https, agents.https, agents.http, { config: 'on' }, true)),
override: assign({}, https, patches(https, agents.https, agents.http, { config: 'override' }, true)),
onRequest: assign({}, https, patches(https, agents.https, agents.http, setting, true)),
default: assign(https, patches(https, agents.https, agents.http, setting, false)) // run last
}
off: assign({}, https, patches(https, resolveProxy, { config: 'off' }, certSetting, true)),
on: assign({}, https, patches(https, resolveProxy, { config: 'on' }, certSetting, true)),
override: assign({}, https, patches(https, resolveProxy, { config: 'override' }, certSetting, true)),
onRequest: assign({}, https, patches(https, resolveProxy, proxySetting, certSetting, true)),
default: assign(https, patches(https, resolveProxy, proxySetting, certSetting, false)) // run last
},
tls: assign(tls, tlsPatches(tls))
};
}
function patches(originals: typeof http | typeof https, agent: http.Agent, otherAgent: http.Agent, setting: { config: string; }, onRequest: boolean) {
function patches(originals: typeof http | typeof https, resolveProxy: ReturnType<typeof setupProxyResolution>, proxySetting: { config: string }, certSetting: { config: boolean }, onRequest: boolean) {
return {
get: patch(originals.get),
request: patch(originals.request)
@@ -265,12 +326,15 @@ function patches(originals: typeof http | typeof https, agent: http.Agent, other
}
options = options || {};
const config = onRequest && ((<any>options)._vscodeProxySupport || /* LS */ (<any>options)._vscodeSystemProxy) || setting.config;
if (config === 'off') {
if (options.socketPath) {
return original.apply(null, arguments as unknown as any[]);
}
if (!options.socketPath && (config === 'override' || config === 'on' && !options.agent) && options.agent !== agent && options.agent !== otherAgent) {
const config = onRequest && ((<any>options)._vscodeProxySupport || /* LS */ (<any>options)._vscodeSystemProxy) || proxySetting.config;
const useProxySettings = (config === 'override' || config === 'on' && !options.agent) && !(options.agent instanceof ProxyAgent);
const useSystemCertificates = certSetting.config && originals === https && !(options as https.RequestOptions).ca;
if (useProxySettings || useSystemCertificates) {
if (url) {
const parsed = typeof url === 'string' ? new nodeurl.URL(url) : url;
const urlOptions = {
@@ -286,7 +350,11 @@ function patches(originals: typeof http | typeof https, agent: http.Agent, other
} else {
options = { ...options };
}
options.agent = agent;
options.agent = new ProxyAgent({
resolveProxy: resolveProxy.bind(undefined, { useProxySettings, useSystemCertificates }),
defaultPort: originals === https ? 443 : 80,
originalAgent: options.agent
});
return original(options, callback);
}
@@ -296,12 +364,35 @@ function patches(originals: typeof http | typeof https, agent: http.Agent, other
}
}
function tlsPatches(originals: typeof tls) {
return {
createSecureContext: patch(originals.createSecureContext)
};
function patch(original: typeof tls.createSecureContext): typeof tls.createSecureContext {
return function (details: tls.SecureContextOptions): ReturnType<typeof tls.createSecureContext> {
const context = original.apply(null, arguments as unknown as any[]);
const certs = (details as any)._vscodeAdditionalCaCerts;
if (certs) {
for (const cert of certs) {
context.context.addCACert(cert);
}
}
return context;
};
}
}
function configureModuleLoading(extensionService: ExtHostExtensionService, lookup: ReturnType<typeof createPatchedModules>): Promise<void> {
return extensionService.getExtensionPathIndex()
.then(extensionPaths => {
const node_module = <any>require.__$__nodeRequire('module');
const original = node_module._load;
node_module._load = function load(request: string, parent: any, isMain: any) {
if (request === 'tls') {
return lookup.tls;
}
if (request !== 'http' && request !== 'https') {
return original.apply(this, arguments);
}
@@ -314,4 +405,120 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku
return modules.default;
};
});
}
}
function useSystemCertificates(extHostLogService: ExtHostLogService, useSystemCertificates: boolean, opts: http.RequestOptions, callback: () => void) {
if (useSystemCertificates) {
getCaCertificates(extHostLogService)
.then(caCertificates => {
if (caCertificates) {
if (caCertificates.append) {
(opts as any)._vscodeAdditionalCaCerts = caCertificates.certs;
} else {
(opts as https.RequestOptions).ca = caCertificates.certs;
}
}
callback();
})
.catch(err => {
extHostLogService.error('ProxyResolver#useSystemCertificates', toErrorMessage(err));
});
} else {
callback();
}
}
let _caCertificates: ReturnType<typeof readCaCertificates> | Promise<undefined>;
async function getCaCertificates(extHostLogService: ExtHostLogService) {
if (!_caCertificates) {
_caCertificates = readCaCertificates()
.then(res => res && res.certs.length ? res : undefined)
.catch(err => {
extHostLogService.error('ProxyResolver#getCertificates', toErrorMessage(err));
return undefined;
});
}
return _caCertificates;
}
async function readCaCertificates() {
if (process.platform === 'win32') {
return readWindowsCaCertificates();
}
if (process.platform === 'darwin') {
return readMacCaCertificates();
}
if (process.platform === 'linux') {
return readLinuxCaCertificates();
}
return undefined;
}
function readWindowsCaCertificates() {
const winCA = require.__$__nodeRequire<any>('win-ca-lib');
let ders = [];
const store = winCA();
try {
let der;
while (der = store.next()) {
ders.push(der);
}
} finally {
store.done();
}
const seen = {};
const certs = ders.map(derToPem)
.filter(pem => !seen[pem] && (seen[pem] = true));
return {
certs,
append: true
};
}
async function readMacCaCertificates() {
const stdout = (await promisify(cp.execFile)('/usr/bin/security', ['find-certificate', '-a', '-p'], { encoding: 'utf8' })).stdout;
const seen = {};
const certs = stdout.split(/(?=-----BEGIN CERTIFICATE-----)/g)
.filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true));
return {
certs,
append: true
};
}
const linuxCaCertificatePaths = [
'/etc/ssl/certs/ca-certificates.crt',
'/etc/ssl/certs/ca-bundle.crt',
];
async function readLinuxCaCertificates() {
for (const certPath of linuxCaCertificatePaths) {
try {
const content = await promisify(fs.readFile)(certPath, { encoding: 'utf8' });
const seen = {};
const certs = content.split(/(?=-----BEGIN CERTIFICATE-----)/g)
.filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true));
return {
certs,
append: false
};
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
}
return undefined;
}
function derToPem(blob) {
const lines = ['-----BEGIN CERTIFICATE-----'];
const der = blob.toString('base64');
for (let i = 0; i < der.length; i += 64) {
lines.push(der.substr(i, 64));
}
lines.push('-----END CERTIFICATE-----', '');
return lines.join(os.EOL);
}