Merge remote-tracking branch 'origin/main' into tyriar/116467_2

This commit is contained in:
Daniel Imms 2021-03-08 07:13:39 -08:00
commit 03c8548523
18 changed files with 285 additions and 535 deletions

View file

@ -78,7 +78,7 @@
"tas-client-umd": "0.1.2",
"v8-inspect-profiler": "^0.0.20",
"vscode-oniguruma": "1.3.1",
"vscode-proxy-agent": "^0.5.2",
"vscode-proxy-agent": "^0.6.0",
"vscode-regexpp": "^3.1.0",
"vscode-ripgrep": "^1.11.1",
"vscode-sqlite3": "4.0.10",

View file

@ -18,7 +18,7 @@
"spdlog": "^0.11.1",
"tas-client-umd": "0.1.2",
"vscode-oniguruma": "1.3.1",
"vscode-proxy-agent": "^0.5.2",
"vscode-proxy-agent": "^0.6.0",
"vscode-regexpp": "^3.1.0",
"vscode-ripgrep": "^1.11.1",
"vscode-textmate": "5.2.0",

View file

@ -376,10 +376,10 @@ vscode-oniguruma@1.3.1:
resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.1.tgz#e2383879c3485b19f533ec34efea9d7a2b14be8f"
integrity sha512-gz6ZBofA7UXafVA+m2Yt2zHKgXC2qedArprIsHAPKByTkwq9l5y/izAGckqxYml7mSbYxTRTfdRwsFq3cwF4LQ==
vscode-proxy-agent@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4"
integrity sha512-1cCNPxrWIrmUwS+1XGaXxkh3G1y7z2fpXl1sT74OZvELaryQWYb3NMxMLJJ4Q/CpPLEyuhp/bAN7nzHxxFcQ5Q==
vscode-proxy-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.6.0.tgz#3ea5c3c82f7abe945d690eb34b2c877cf5833f12"
integrity sha512-dvoLmEO/IxkbcNrRHH6ey8ITfvau4wDg01S+iAJ5Pq/FoAl2ZeE4cK5VEnQ2JHqM20kTLhyZfkjdHq6l7/T+xA==
dependencies:
debug "^3.1.0"
http-proxy-agent "^2.1.0"

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle';
import { isWindows, OperatingSystem } from 'vs/base/common/platform';
@ -42,6 +43,23 @@ export interface ITunnelProvider {
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined;
}
export enum ProvidedOnAutoForward {
Notify = 1,
OpenBrowser = 2,
OpenPreview = 3,
Silent = 4,
Ignore = 5
}
export interface ProvidedPortAttributes {
port: number;
autoForwardAction: ProvidedOnAutoForward;
}
export interface PortAttributesProvider {
providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): Promise<ProvidedPortAttributes[]>;
}
export interface ITunnel {
remoteAddress: { port: number, host: string };

View file

@ -2846,4 +2846,37 @@ declare module 'vscode' {
readonly triggerKind: CodeActionTriggerKind;
}
//#endregion
//#region https://github.com/microsoft/vscode/issues/115616 @alexr00
export enum PortAutoForwardAction {
Notify = 1,
OpenBrowser = 2,
OpenPreview = 3,
Silent = 4,
Ignore = 5
}
export interface PortAttributes {
port: number;
autoForwardAction: PortAutoForwardAction
}
export interface PortAttributesProvider {
providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): ProviderResult<PortAttributes[]>;
}
export namespace workspace {
/**
* If your extension listens on ports, consider registering a PortAttributesProvider to provide information
* about the ports. For example, a debug extension may know about debug ports in it's debuggee. By providing
* this information with a PortAttributesProvider the extension can tell VS Code that these ports should be
* ignored, since they don't need to be user facing.
*
* @param portSelector If registerPortAttributesProvider is called after you start your process then you may already
* know the range of ports or the pid of your process.
* @param provider The PortAttributesProvider
*/
export function registerPortAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: PortAttributesProvider): Disposable;
}
//#endregion
}

View file

@ -4,22 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource } from 'vs/workbench/api/common/extHost.protocol';
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape, CandidatePortSource, PortAttributesProviderSelector } from 'vs/workbench/api/common/extHost.protocol';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { CandidatePort, IRemoteExplorerService, makeAddress, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged } from 'vs/platform/remote/common/tunnel';
import { ITunnelProvider, ITunnelService, TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, RemoteTunnel, isPortPrivileged, ProvidedPortAttributes, PortAttributesProvider } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { CancellationToken } from 'vs/base/common/cancellation';
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape {
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape, PortAttributesProvider {
private readonly _proxy: ExtHostTunnelServiceShape;
private elevateionRetry: boolean = false;
private portsAttributesProviders: Map<number, PortAttributesProviderSelector> = new Map();
constructor(
extHostContext: IExtHostContext,
@ -50,6 +52,39 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun
}));
}
private _alreadyRegistered: boolean = false;
async $registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise<void> {
this.portsAttributesProviders.set(providerHandle, selector);
if (!this._alreadyRegistered) {
this.remoteExplorerService.tunnelModel.addAttributesProvider(this);
this._alreadyRegistered = true;
}
}
async $unregisterPortsAttributesProvider(providerHandle: number): Promise<void> {
this.portsAttributesProviders.delete(providerHandle);
}
async providePortAttributes(ports: number[], pid: number | undefined, commandLine: string | undefined, token: CancellationToken): Promise<ProvidedPortAttributes[]> {
if (this.portsAttributesProviders.size === 0) {
return [];
}
// Check all the selectors to make sure it's worth going to the extension host.
const appropriateHandles = Array.from(this.portsAttributesProviders.entries()).filter(entry => {
const selector = entry[1];
const portRange = selector.portRange;
const portInRange = portRange ? ports.some(port => portRange[0] <= port && port < portRange[1]) : true;
const pidMatches = !selector.pid || (selector.pid === pid);
return portInRange || pidMatches;
}).map(entry => entry[0]);
if (appropriateHandles.length === 0) {
return [];
}
return this._proxy.$providePortAttributes(appropriateHandles, ports, pid, commandLine, token);
}
async $openTunnel(tunnelOptions: TunnelOptions, source: string): Promise<TunnelDto | undefined> {
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remoteAddress, tunnelOptions.localAddressPort, tunnelOptions.label, source, false);
if (tunnel) {

View file

@ -901,6 +901,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostTunnelService.onDidChangeTunnels(listener, thisArg, disposables);
},
registerPortAttributesProvider: (portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider) => {
checkProposedApiEnabled(extension);
return extHostTunnelService.registerPortsAttributesProvider(portSelector, provider);
},
registerTimelineProvider: (scheme: string | string[], provider: vscode.TimelineProvider) => {
checkProposedApiEnabled(extension);
return extHostTimeline.registerTimelineProvider(scheme, provider, extension.identifier, extHostCommands.converter);
@ -1173,6 +1177,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
MarkdownString: extHostTypes.MarkdownString,
OverviewRulerLane: OverviewRulerLane,
ParameterInformation: extHostTypes.ParameterInformation,
PortAutoForwardAction: extHostTypes.PortAutoForwardAction,
Position: extHostTypes.Position,
ProcessExecution: extHostTypes.ProcessExecution,
ProgressLocation: extHostTypes.ProgressLocation,

View file

@ -47,7 +47,7 @@ import * as search from 'vs/workbench/services/search/common/search';
import { EditorGroupColumn, SaveReason } from 'vs/workbench/common/editor';
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, ProvidedPortAttributes } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@ -1048,6 +1048,11 @@ export enum CandidatePortSource {
Output = 2
}
export interface PortAttributesProviderSelector {
pid?: number;
portRange?: [number, number];
}
export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions, source: string | undefined): Promise<TunnelDto | undefined>;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
@ -1057,6 +1062,8 @@ export interface MainThreadTunnelServiceShape extends IDisposable {
$setCandidateFilter(): Promise<void>;
$onFoundNewCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<void>;
$setCandidatePortSource(source: CandidatePortSource): Promise<void>;
$registerPortsAttributesProvider(selector: PortAttributesProviderSelector, providerHandle: number): Promise<void>;
$unregisterPortsAttributesProvider(providerHandle: number): Promise<void>;
}
export interface MainThreadTimelineShape extends IDisposable {
@ -1885,6 +1892,7 @@ export interface ExtHostTunnelServiceShape {
$onDidTunnelsChange(): Promise<void>;
$registerCandidateFinder(enable: boolean): Promise<void>;
$applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]>;
$providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: CancellationToken): Promise<ProvidedPortAttributes[]>;
}
export interface ExtHostTimelineShape {

View file

@ -6,7 +6,7 @@
import { ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as vscode from 'vscode';
import { RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { ProvidedPortAttributes, RemoteTunnel, TunnelCreationOptions, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
@ -46,6 +46,7 @@ export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
getTunnels(): Promise<vscode.TunnelDescription[]>;
onDidChangeTunnels: vscode.Event<void>;
setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider): IDisposable;
}
export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');
@ -71,6 +72,14 @@ export class ExtHostTunnelService implements IExtHostTunnelService {
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
return { dispose: () => { } };
}
registerPortsAttributesProvider(portSelector: { pid?: number, portRange?: [number, number] }, provider: vscode.PortAttributesProvider) {
return { dispose: () => { } };
}
async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise<ProvidedPortAttributes[]> {
return [];
}
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> { return undefined; }
async $closeTunnel(remote: { host: string, port: number }): Promise<void> { }
async $onDidTunnelsChange(): Promise<void> { }

View file

@ -3369,3 +3369,11 @@ export enum WorkspaceTrustState {
Trusted = 1,
Unknown = 2
}
export enum PortAutoForwardAction {
Notify = 1,
OpenBrowser = 2,
OpenPreview = 3,
Silent = 4,
Ignore = 5
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { MainThreadTunnelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { MainThreadTunnelServiceShape, MainContext, PortAttributesProviderSelector } from 'vs/workbench/api/common/extHost.protocol';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import type * as vscode from 'vscode';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
@ -13,14 +13,16 @@ import { exec } from 'child_process';
import * as resources from 'vs/base/common/resources';
import * as fs from 'fs';
import * as pfs from 'vs/base/node/pfs';
import * as types from 'vs/workbench/api/common/extHostTypes';
import { isLinux } from 'vs/base/common/platform';
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { Event, Emitter } from 'vs/base/common/event';
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
import { TunnelOptions, TunnelCreationOptions, ProvidedPortAttributes, ProvidedOnAutoForward } from 'vs/platform/remote/common/tunnel';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { MovingAverage } from 'vs/base/common/numbers';
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ILogService } from 'vs/platform/log/common/log';
import { flatten } from 'vs/base/common/arrays';
class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter();
@ -139,6 +141,9 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
onDidChangeTunnels: vscode.Event<void> = this._onDidChangeTunnels.event;
private _candidateFindingEnabled: boolean = false;
private _providerHandleCounter: number = 0;
private _portAttributesProviders: Map<number, { provider: vscode.PortAttributesProvider, selector: PortAttributesProviderSelector }> = new Map();
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService,
@ -173,6 +178,40 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
return Math.max(movingAverage * 20, 2000);
}
private nextPortAttributesProviderHandle(): number {
return this._providerHandleCounter++;
}
registerPortsAttributesProvider(portSelector: PortAttributesProviderSelector, provider: vscode.PortAttributesProvider): vscode.Disposable {
const providerHandle = this.nextPortAttributesProviderHandle();
this._portAttributesProviders.set(providerHandle, { selector: portSelector, provider });
this._proxy.$registerPortsAttributesProvider(portSelector, providerHandle);
return new types.Disposable(() => {
this._portAttributesProviders.delete(providerHandle);
this._proxy.$unregisterPortsAttributesProvider(providerHandle);
});
}
async $providePortAttributes(handles: number[], ports: number[], pid: number | undefined, commandline: string | undefined, cancellationToken: vscode.CancellationToken): Promise<ProvidedPortAttributes[]> {
const providedAttributes = await Promise.all(handles.map(handle => {
const provider = this._portAttributesProviders.get(handle);
if (!provider) {
return [];
}
return provider.provider.providePortAttributes(ports, pid, commandline, cancellationToken);
}));
const allAttributes = <vscode.PortAttributes[][]>providedAttributes.filter(attribute => !!attribute && attribute.length > 0);
return (allAttributes.length > 0) ? flatten(allAttributes).map(attributes => {
return {
autoForwardAction: <ProvidedOnAutoForward><unknown>attributes.autoForwardAction,
port: attributes.port
};
}) : [];
}
async $registerCandidateFinder(enable: boolean): Promise<void> {
if (enable && this._candidateFindingEnabled) {
// already enabled

View file

@ -6,7 +6,7 @@ import * as nls from 'vs/nls';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { Extensions, IViewContainersRegistry, IViewsRegistry, IViewsService, ViewContainer, ViewContainerLocation } from 'vs/workbench/common/views';
import { IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PortsAttributes, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { IRemoteExplorerService, makeAddress, mapHasAddressLocalhostOrAllInterfaces, OnPortForward, PORT_AUTO_FORWARD_SETTING, PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_OUTPUT, PORT_AUTO_SOURCE_SETTING_PROCESS, TUNNEL_VIEW_CONTAINER_ID, TUNNEL_VIEW_ID } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { forwardedPortsViewEnabled, ForwardPortAction, OpenPortInBrowserAction, TunnelPanel, TunnelPanelDescriptor, TunnelViewModel, OpenPortInPreviewAction } from 'vs/workbench/contrib/remote/browser/tunnelView';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
@ -222,8 +222,7 @@ class OnAutoForwardedAction extends Disposable {
private readonly openerService: IOpenerService,
private readonly externalOpenerService: IExternalUriOpenerService,
private readonly tunnelService: ITunnelService,
private readonly hostService: IHostService,
private readonly portsAttributes: PortsAttributes) {
private readonly hostService: IHostService) {
super();
this.lastNotifyTime = new Date();
this.lastNotifyTime.setFullYear(this.lastNotifyTime.getFullYear() - 1);
@ -233,7 +232,7 @@ class OnAutoForwardedAction extends Disposable {
this.doActionTunnels = tunnels;
const tunnel = await this.portNumberHeuristicDelay();
if (tunnel) {
switch (this.portsAttributes.getAttributes(tunnel.tunnelRemotePort)?.onAutoForward) {
switch ((await this.remoteExplorerService.tunnelModel.getAttributes([tunnel.tunnelRemotePort]))?.get(tunnel.tunnelRemotePort)?.onAutoForward) {
case OnPortForward.OpenBrowser: {
const address = makeAddress(tunnel.tunnelRemoteHost, tunnel.tunnelRemotePort);
await OpenPortInBrowserAction.run(this.remoteExplorerService.tunnelModel, this.openerService, address);
@ -380,7 +379,6 @@ class OutputAutomaticPortForwarding extends Disposable {
private portsFeatures?: IDisposable;
private urlFinder?: UrlFinder;
private notifier: OnAutoForwardedAction;
private portsAttributes: PortsAttributes;
constructor(
private readonly terminalService: ITerminalService,
@ -396,8 +394,7 @@ class OutputAutomaticPortForwarding extends Disposable {
readonly privilegedOnly: boolean
) {
super();
this.portsAttributes = new PortsAttributes(configurationService);
this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService, this.portsAttributes);
this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService);
this._register(configurationService.onDidChangeConfiguration((e) => {
if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) {
this.tryStartStopUrlFinder();
@ -430,7 +427,7 @@ class OutputAutomaticPortForwarding extends Disposable {
if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.detected, localUrl.host, localUrl.port)) {
return;
}
if (this.portsAttributes.getAttributes(localUrl.port)?.onAutoForward === OnPortForward.Ignore) {
if ((await this.remoteExplorerService.tunnelModel.getAttributes([localUrl.port]))?.get(localUrl.port)?.onAutoForward === OnPortForward.Ignore) {
return;
}
if (this.privilegedOnly && !isPortPrivileged(localUrl.port, (await this.remoteAgentService.getEnvironment())?.os)) {
@ -458,7 +455,6 @@ class ProcAutomaticPortForwarding extends Disposable {
private notifier: OnAutoForwardedAction;
private initialCandidates: Set<string> = new Set();
private portsFeatures: IDisposable | undefined;
private portsAttributes: PortsAttributes;
constructor(
private readonly configurationService: IConfigurationService,
@ -470,8 +466,7 @@ class ProcAutomaticPortForwarding extends Disposable {
readonly hostService: IHostService
) {
super();
this.portsAttributes = new PortsAttributes(configurationService);
this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService, this.portsAttributes);
this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService);
this._register(configurationService.onDidChangeConfiguration(async (e) => {
if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING)) {
await this.startStopCandidateListener();
@ -531,6 +526,7 @@ class ProcAutomaticPortForwarding extends Disposable {
}
private async forwardCandidates(): Promise<RemoteTunnel[] | undefined> {
const attributes = await this.remoteExplorerService.tunnelModel.getAttributes(this.remoteExplorerService.tunnelModel.candidates.map(candidate => candidate.port));
const allTunnels = <RemoteTunnel[]>(await Promise.all(this.remoteExplorerService.tunnelModel.candidates.map(async (value) => {
const address = makeAddress(value.host, value.port);
if (this.initialCandidates.has(address)) {
@ -543,7 +539,7 @@ class ProcAutomaticPortForwarding extends Disposable {
if (mapHasAddressLocalhostOrAllInterfaces(this.remoteExplorerService.tunnelModel.detected, value.host, value.port)) {
return undefined;
}
if (this.portsAttributes.getAttributes(value.port)?.onAutoForward === OnPortForward.Ignore) {
if (attributes?.get(value.port)?.onAutoForward === OnPortForward.Ignore) {
return undefined;
}
const forwarded = await this.remoteExplorerService.forward(value, undefined, undefined, undefined, undefined, undefined, false);

View file

@ -183,13 +183,10 @@ export class TerminalService implements ITerminalService {
const layoutInfo = await this._localTerminalService.getTerminalLayoutInfo();
if (layoutInfo && layoutInfo.tabs.length > 0) {
this._recreateTerminalTabs(layoutInfo);
// now that terminals have been restored,
// attach listeners to update local state when terminals are changed
this.attachProcessLayoutListeners(false);
} else {
this.createTerminal();
this.attachProcessLayoutListeners(false);
}
// now that terminals have been restored,
// attach listeners to update local state when terminals are changed
this.attachProcessLayoutListeners(false);
this._connectionState = TerminalConnectionState.Connected;
this._onDidChangeConnectionState.fire();
}

View file

@ -1,477 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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 { ProxyAgent } from 'vscode-proxy-agent';
export enum LogLevel {
Trace = 1,
Debug = 2,
Info = 3,
Warning = 4,
Error = 5,
Critical = 6,
Off = 7
}
export type ResolveProxyEvent = {
count: number;
duration: number;
errorCount: number;
cacheCount: number;
cacheSize: number;
cacheRolls: number;
envCount: number;
settingsCount: number;
localhostCount: number;
envNoProxyCount: number;
results: ConnectionResult[];
};
interface ConnectionResult {
proxy: string;
connection: string;
code: string;
count: number;
}
const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'.
export interface ProxyAgentParams {
resolveProxy(url: string): Promise<string | undefined>;
getHttpProxySetting(): string | undefined;
log(level: LogLevel, message: string, ...args: any[]): void;
getLogLevel(): LogLevel;
proxyResolverTelemetry(event: ResolveProxyEvent): void;
useHostProxy: boolean;
env: NodeJS.ProcessEnv;
}
export function setupProxyResolution(params: ProxyAgentParams) {
const { getHttpProxySetting, log, getLogLevel, proxyResolverTelemetry, useHostProxy, env } = params;
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>();
function getCacheKey(url: nodeurl.UrlWithStringQuery) {
// Expecting proxies to usually be the same per scheme://host:port. Assuming that for performance.
return nodeurl.format({ ...url, ...{ pathname: undefined, search: undefined, hash: undefined } });
}
function getCachedProxy(key: string) {
let proxy = cache.get(key);
if (proxy) {
return proxy;
}
proxy = oldCache.get(key);
if (proxy) {
oldCache.delete(key);
cacheProxy(key, proxy);
}
return proxy;
}
function cacheProxy(key: string, proxy: string) {
cache.set(key, proxy);
if (cache.size >= maxCacheEntries) {
oldCache = cache;
cache = new Map();
cacheRolls++;
log(LogLevel.Debug, 'ProxyResolver#cacheProxy cacheRolls', cacheRolls);
}
}
let timeout: NodeJS.Timer | undefined;
let count = 0;
let duration = 0;
let errorCount = 0;
let cacheCount = 0;
let envCount = 0;
let settingsCount = 0;
let localhostCount = 0;
let envNoProxyCount = 0;
let results: ConnectionResult[] = [];
function logEvent() {
timeout = undefined;
proxyResolverTelemetry({ 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(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);
}
const stackText = getLogLevel() === LogLevel.Trace ? '\n' + new Error('Error for stack trace').stack : '';
useSystemCertificates(params, flags.useSystemCertificates, opts, () => {
useProxySettings(useHostProxy, flags.useProxySettings, req, opts, url, stackText, callback);
});
}
function useProxySettings(useHostProxy: boolean, useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, stackText: 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;
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1' || hostname === '::ffff:127.0.0.1') {
localhostCount++;
callback('DIRECT');
log(LogLevel.Debug, 'ProxyResolver#resolveProxy localhost', url, 'DIRECT', stackText);
return;
}
if (typeof hostname === 'string' && envNoProxy(hostname, String(parsedUrl.port || (<any>opts.agent).defaultPort))) {
envNoProxyCount++;
callback('DIRECT');
log(LogLevel.Debug, 'ProxyResolver#resolveProxy envNoProxy', url, 'DIRECT', stackText);
return;
}
let settingsProxy = proxyFromConfigURL(getHttpProxySetting());
if (settingsProxy) {
settingsCount++;
callback(settingsProxy);
log(LogLevel.Debug, 'ProxyResolver#resolveProxy settings', url, settingsProxy, stackText);
return;
}
if (envProxy) {
envCount++;
callback(envProxy);
log(LogLevel.Debug, 'ProxyResolver#resolveProxy env', url, envProxy, stackText);
return;
}
const key = getCacheKey(parsedUrl);
const proxy = getCachedProxy(key);
if (proxy) {
cacheCount++;
collectResult(results, proxy, parsedUrl.protocol === 'https:' ? 'HTTPS' : 'HTTP', req);
callback(proxy);
log(LogLevel.Debug, 'ProxyResolver#resolveProxy cached', url, proxy, stackText);
return;
}
if (!useHostProxy) {
callback('DIRECT');
log(LogLevel.Debug, 'ProxyResolver#resolveProxy unconfigured', url, 'DIRECT', stackText);
return;
}
const start = Date.now();
params.resolveProxy(url) // Use full URL to ensure it is an actually used one.
.then(proxy => {
if (proxy) {
cacheProxy(key, proxy);
collectResult(results, proxy, parsedUrl.protocol === 'https:' ? 'HTTPS' : 'HTTP', req);
}
callback(proxy);
log(LogLevel.Debug, 'ProxyResolver#resolveProxy', url, proxy, stackText);
}).then(() => {
count++;
duration = Date.now() - start + duration;
}, err => {
errorCount++;
callback();
log(LogLevel.Error, 'ProxyResolver#resolveProxy', toErrorMessage(err), stackText);
});
}
return resolveProxy;
}
function collectResult(results: ConnectionResult[], resolveProxy: string, connection: string, req: http.ClientRequest) {
const proxy = resolveProxy ? String(resolveProxy).trim().split(/\s+/, 1)[0] : 'EMPTY';
req.on('response', res => {
const code = `HTTP_${res.statusCode}`;
const result = findOrCreateResult(results, proxy, connection, code);
result.count++;
});
req.on('error', err => {
const code = err && typeof (<any>err).code === 'string' && (<any>err).code || 'UNKNOWN_ERROR';
const result = findOrCreateResult(results, proxy, connection, code);
result.count++;
});
}
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;
}
}
const result = { proxy, connection, code, count: 0 };
results.push(result);
return result;
}
function proxyFromConfigURL(configURL: string | undefined) {
if (!configURL) {
return undefined;
}
const url = (configURL || '').trim();
const i = url.indexOf('://');
if (i === -1) {
return undefined;
}
const scheme = url.substr(0, i).toLowerCase();
const proxy = url.substr(i + 3);
if (scheme === 'http') {
return 'PROXY ' + proxy;
} else if (scheme === 'https') {
return 'HTTPS ' + proxy;
} else if (scheme === 'socks') {
return 'SOCKS ' + proxy;
}
return undefined;
}
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 `.${hostname.toLowerCase()}`.endsWith(domain) && (!filterPort || port === filterPort);
});
}
export 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)
};
function patch(original: typeof http.get) {
function patched(url?: string | URL | null, options?: http.RequestOptions | null, callback?: (res: http.IncomingMessage) => void): http.ClientRequest {
if (typeof url !== 'string' && !(url && (<any>url).searchParams)) {
callback = <any>options;
options = url;
url = null;
}
if (typeof options === 'function') {
callback = options;
options = null;
}
options = options || {};
if (options.socketPath) {
return original.apply(null, arguments as any);
}
const originalAgent = options.agent;
if (originalAgent === true) {
throw new Error('Unexpected agent option: true');
}
const optionsPatched = originalAgent instanceof ProxyAgent;
const config = onRequest && ((<any>options)._vscodeProxySupport || /* LS */ (<any>options)._vscodeSystemProxy) || proxySetting.config;
const useProxySettings = !optionsPatched && (config === 'override' || config === 'on' && originalAgent === undefined);
const useSystemCertificates = !optionsPatched && 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 = {
protocol: parsed.protocol,
hostname: parsed.hostname.lastIndexOf('[', 0) === 0 ? parsed.hostname.slice(1, -1) : parsed.hostname,
port: parsed.port,
path: `${parsed.pathname}${parsed.search}`
};
if (parsed.username || parsed.password) {
options.auth = `${parsed.username}:${parsed.password}`;
}
options = { ...urlOptions, ...options };
} else {
options = { ...options };
}
options.agent = new ProxyAgent({
resolveProxy: resolveProxy.bind(undefined, { useProxySettings, useSystemCertificates }),
defaultPort: originals === https ? 443 : 80,
originalAgent
});
return original(options, callback);
}
return original.apply(null, arguments as any);
}
return patched;
}
}
export 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 any);
const certs = (details as any)._vscodeAdditionalCaCerts;
if (certs) {
for (const cert of certs) {
context.context.addCACert(cert);
}
}
return context;
};
}
}
function useSystemCertificates(params: ProxyAgentParams, useSystemCertificates: boolean, opts: http.RequestOptions, callback: () => void) {
if (useSystemCertificates) {
getCaCertificates(params)
.then(caCertificates => {
if (caCertificates) {
if (caCertificates.append) {
(opts as any)._vscodeAdditionalCaCerts = caCertificates.certs;
} else {
(opts as https.RequestOptions).ca = caCertificates.certs;
}
}
callback();
})
.catch(err => {
params.log(LogLevel.Error, 'ProxyResolver#useSystemCertificates', toErrorMessage(err));
});
} else {
callback();
}
}
let _caCertificates: ReturnType<typeof readCaCertificates> | Promise<undefined>;
async function getCaCertificates({ log }: ProxyAgentParams) {
if (!_caCertificates) {
_caCertificates = readCaCertificates()
.then(res => {
log(LogLevel.Debug, 'ProxyResolver#getCaCertificates count', res && res.certs.length);
return res && res.certs.length ? res : undefined;
})
.catch(err => {
log(LogLevel.Error, 'ProxyResolver#getCaCertificates error', 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;
}
async function readWindowsCaCertificates() {
// @ts-ignore Windows only
const winCA = await import('vscode-windows-ca-certs');
let ders: any[] = [];
const store = new winCA.Crypt32();
try {
let der: any;
while (der = store.next()) {
ders.push(der);
}
} finally {
store.done();
}
const certs = new Set(ders.map(derToPem));
return {
certs: Array.from(certs),
append: true
};
}
async function readMacCaCertificates() {
const stdout = await new Promise<string>((resolve, reject) => {
const child = cp.spawn('/usr/bin/security', ['find-certificate', '-a', '-p']);
const stdout: string[] = [];
child.stdout.setEncoding('utf8');
child.stdout.on('data', str => stdout.push(str));
child.on('error', reject);
child.on('exit', code => code ? reject(code) : resolve(stdout.join('')));
});
const certs = new Set(stdout.split(/(?=-----BEGIN CERTIFICATE-----)/g)
.filter(pem => !!pem.length));
return {
certs: Array.from(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 fs.promises.readFile(certPath, { encoding: 'utf8' });
const certs = new Set(content.split(/(?=-----BEGIN CERTIFICATE-----)/g)
.filter(pem => !!pem.length));
return {
certs: Array.from(certs),
append: false
};
} catch (err) {
if (err.code !== 'ENOENT') {
throw err;
}
}
}
return undefined;
}
function derToPem(blob: Buffer) {
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);
}
function toErrorMessage(err: any) {
return err && (err.stack || err.message) || String(err);
}

View file

@ -14,7 +14,7 @@ import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionS
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { LogLevel, patches, ResolveProxyEvent, setupProxyResolution, tlsPatches } from 'vs/workbench/services/extensions/node/proxyAgent';
import { LogLevel, createHttpPatch, ProxyResolveEvent, createProxyResolver, createTlsPatch } from 'vscode-proxy-agent';
export function connectProxyResolver(
extHostWorkspace: IExtHostWorkspaceProvider,
@ -26,7 +26,7 @@ export function connectProxyResolver(
) {
const useHostProxy = initData.environment.useHostProxy;
const doUseHostProxy = typeof useHostProxy === 'boolean' ? useHostProxy : !initData.remote.isRemote;
const resolveProxy = setupProxyResolution({
const resolveProxy = createProxyResolver({
resolveProxy: url => extHostWorkspace.resolveProxy(url),
getHttpProxySetting: () => configProvider.getConfiguration('http').get('proxy'),
log: (level, message, ...args) => {
@ -46,7 +46,7 @@ export function connectProxyResolver(
}
},
getLogLevel: () => extHostLogService.getLevel(),
proxyResolverTelemetry: event => {
proxyResolveTelemetry: event => {
type ResolveProxyClassification = {
count: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
duration: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
@ -60,7 +60,7 @@ export function connectProxyResolver(
envNoProxyCount: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth', isMeasurement: true };
results: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
};
mainThreadTelemetry.$publicLog2<ResolveProxyEvent, ResolveProxyClassification>('resolveProxy', event);
mainThreadTelemetry.$publicLog2<ProxyResolveEvent, ResolveProxyClassification>('resolveProxy', event);
},
useHostProxy: doUseHostProxy,
env: process.env,
@ -69,7 +69,7 @@ export function connectProxyResolver(
return configureModuleLoading(extensionService, lookup);
}
function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProxy: ReturnType<typeof setupProxyResolution>) {
function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProxy: ReturnType<typeof createProxyResolver>) {
const proxySetting = {
config: configProvider.getConfiguration('http')
.get<string>('proxySupport') || 'off'
@ -89,20 +89,20 @@ function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProx
return {
http: {
off: Object.assign({}, http, patches(http, resolveProxy, { config: 'off' }, certSetting, true)),
on: Object.assign({}, http, patches(http, resolveProxy, { config: 'on' }, certSetting, true)),
override: Object.assign({}, http, patches(http, resolveProxy, { config: 'override' }, certSetting, true)),
onRequest: Object.assign({}, http, patches(http, resolveProxy, proxySetting, certSetting, true)),
default: Object.assign(http, patches(http, resolveProxy, proxySetting, certSetting, false)) // run last
off: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'off' }, certSetting, true)),
on: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'on' }, certSetting, true)),
override: Object.assign({}, http, createHttpPatch(http, resolveProxy, { config: 'override' }, certSetting, true)),
onRequest: Object.assign({}, http, createHttpPatch(http, resolveProxy, proxySetting, certSetting, true)),
default: Object.assign(http, createHttpPatch(http, resolveProxy, proxySetting, certSetting, false)) // run last
} as Record<string, typeof http>,
https: {
off: Object.assign({}, https, patches(https, resolveProxy, { config: 'off' }, certSetting, true)),
on: Object.assign({}, https, patches(https, resolveProxy, { config: 'on' }, certSetting, true)),
override: Object.assign({}, https, patches(https, resolveProxy, { config: 'override' }, certSetting, true)),
onRequest: Object.assign({}, https, patches(https, resolveProxy, proxySetting, certSetting, true)),
default: Object.assign(https, patches(https, resolveProxy, proxySetting, certSetting, false)) // run last
off: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'off' }, certSetting, true)),
on: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'on' }, certSetting, true)),
override: Object.assign({}, https, createHttpPatch(https, resolveProxy, { config: 'override' }, certSetting, true)),
onRequest: Object.assign({}, https, createHttpPatch(https, resolveProxy, proxySetting, certSetting, true)),
default: Object.assign(https, createHttpPatch(https, resolveProxy, proxySetting, certSetting, false)) // run last
} as Record<string, typeof https>,
tls: Object.assign(tls, tlsPatches(tls))
tls: Object.assign(tls, createTlsPatch(tls))
};
}

View file

@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { ALL_INTERFACES_ADDRESSES, isAllInterfaces, isLocalhost, ITunnelService, LOCALHOST_ADDRESSES, PortAttributesProvider, ProvidedOnAutoForward, ProvidedPortAttributes, RemoteTunnel } from 'vs/platform/remote/common/tunnel';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IEditableData } from 'vs/workbench/common/views';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@ -19,6 +19,8 @@ import { isNumber, isObject, isString } from 'vs/base/common/types';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { hash } from 'vs/base/common/hash';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { flatten } from 'vs/base/common/arrays';
export const IRemoteExplorerService = createDecorator<IRemoteExplorerService>('remoteExplorerService');
export const REMOTE_EXPLORER_TYPE_KEY: string = 'remote.explorerType';
@ -241,6 +243,17 @@ export class PortsAttributes extends Disposable {
});
return attributes;
}
static providedActionToAction(providedAction: ProvidedOnAutoForward | undefined) {
switch (providedAction) {
case ProvidedOnAutoForward.Notify: return OnPortForward.Notify;
case ProvidedOnAutoForward.OpenBrowser: return OnPortForward.OpenBrowser;
case ProvidedOnAutoForward.OpenPreview: return OnPortForward.OpenPreview;
case ProvidedOnAutoForward.Silent: return OnPortForward.Silent;
case ProvidedOnAutoForward.Ignore: return OnPortForward.Ignore;
default: return undefined;
}
}
}
export class TunnelModel extends Disposable {
@ -262,7 +275,9 @@ export class TunnelModel extends Disposable {
private _onEnvironmentTunnelsSet: Emitter<void> = new Emitter();
public onEnvironmentTunnelsSet: Event<void> = this._onEnvironmentTunnelsSet.event;
private _environmentTunnelsSet: boolean = false;
private portsAttributes: PortsAttributes;
private configPortsAttributes: PortsAttributes;
private portAttributesProviders: PortAttributesProvider[] = [];
constructor(
@ITunnelService private readonly tunnelService: ITunnelService,
@ -274,7 +289,7 @@ export class TunnelModel extends Disposable {
@ILogService private readonly logService: ILogService
) {
super();
this.portsAttributes = new PortsAttributes(configurationService);
this.configPortsAttributes = new PortsAttributes(configurationService);
this.tunnelRestoreValue = this.getTunnelRestoreValue();
this.forwarded = new Map();
this.remoteTunnels = new Map();
@ -328,7 +343,7 @@ export class TunnelModel extends Disposable {
}
}));
this._register(this.portsAttributes.onDidChangeAttributes(this.updateAttributes, this));
this._register(this.configPortsAttributes.onDidChangeAttributes(this.updateAttributes, this));
}
private makeTunnelPrivacy(isPublic: boolean) {
@ -391,7 +406,8 @@ export class TunnelModel extends Disposable {
getAddress: async () => { return (await this.remoteAuthorityResolverService.resolveAuthority(authority)).authority; }
} : undefined;
const attributes = this.portsAttributes.getAttributes(local !== undefined ? local : remote.port);
const port = local !== undefined ? local : remote.port;
const attributes = (await this.getAttributes([port]))?.get(port);
const tunnel = await this.tunnelService.openTunnel(addressProvider, remote.host, remote.port, local, (!elevateIfNeeded) ? attributes?.elevateIfNeeded : elevateIfNeeded, isPublic);
if (tunnel && tunnel.localAddress) {
@ -541,12 +557,75 @@ export class TunnelModel extends Disposable {
private async updateAttributes() {
// If the label changes in the attributes, we should update it.
for (let forwarded of this.forwarded.values()) {
const attributes = this.portsAttributes.getAttributes(forwarded.remotePort);
const attributes = (await this.getAttributes([forwarded.remotePort], false))?.get(forwarded.remotePort);
if (attributes && attributes.label && attributes.label !== forwarded.name) {
await this.name(forwarded.remoteHost, forwarded.remotePort, attributes.label);
}
}
}
async getAttributes(ports: number[], checkProviders: boolean = true): Promise<Map<number, Attributes> | undefined> {
const configAttributes: Map<number, Attributes> = new Map();
ports.forEach(port => {
const attributes = this.configPortsAttributes.getAttributes(port);
if (attributes) {
configAttributes.set(port, attributes);
}
});
if ((this.portAttributesProviders.length === 0) || !checkProviders) {
return (configAttributes.size > 0) ? configAttributes : undefined;
}
const matchingCandidates: Map<number, CandidatePort> = new Map();
const pidToPortsMapping: Map<number, number[]> = new Map();
ports.forEach(port => {
const matchingCandidate = mapHasAddressLocalhostOrAllInterfaces<CandidatePort>(this._candidates ?? new Map(), LOCALHOST_ADDRESSES[0], port);
if (matchingCandidate) {
matchingCandidates.set(port, matchingCandidate);
if (!pidToPortsMapping.has(matchingCandidate.pid)) {
pidToPortsMapping.set(matchingCandidate.pid, []);
}
pidToPortsMapping.get(matchingCandidate.pid)?.push(port);
}
});
// Group calls to provide attributes by pid.
const allProviderResults = await Promise.all(flatten(this.portAttributesProviders.map(provider => {
return Array.from(pidToPortsMapping.entries()).map(entry => {
const portGroup = entry[1];
const matchingCandidate = matchingCandidates.get(portGroup[1]);
return provider.providePortAttributes(portGroup,
matchingCandidate?.pid, matchingCandidate?.detail, new CancellationTokenSource().token);
});
})));
const providedAttributes: Map<number, ProvidedPortAttributes> = new Map();
allProviderResults.forEach(attributes => attributes.forEach(attribute => {
if (attribute) {
providedAttributes.set(attribute.port, attribute);
}
}));
if (!configAttributes && !providedAttributes) {
return undefined;
}
// Merge. The config wins.
const mergedAttributes: Map<number, Attributes> = new Map();
ports.forEach(port => {
const config = configAttributes.get(port);
const provider = providedAttributes.get(port);
mergedAttributes.set(port, {
elevateIfNeeded: config?.elevateIfNeeded,
label: config?.label,
onAutoForward: config?.onAutoForward ?? PortsAttributes.providedActionToAction(provider?.autoForwardAction)
});
});
return mergedAttributes;
}
addAttributesProvider(provider: PortAttributesProvider) {
this.portAttributesProviders.push(provider);
}
}
export interface CandidatePort {

View file

@ -10,7 +10,7 @@ export function setup() {
it(`install and activate vscode-smoketest-check extension`, async function () {
const app = this.app as Application;
if (app.quality === Quality.Dev) {
if (app.quality === Quality.Dev || app.web /* https://github.com/microsoft/vscode/issues/118443 */) {
this.skip();
return;
}

View file

@ -10096,10 +10096,10 @@ vscode-oniguruma@1.3.1:
resolved "https://registry.yarnpkg.com/vscode-oniguruma/-/vscode-oniguruma-1.3.1.tgz#e2383879c3485b19f533ec34efea9d7a2b14be8f"
integrity sha512-gz6ZBofA7UXafVA+m2Yt2zHKgXC2qedArprIsHAPKByTkwq9l5y/izAGckqxYml7mSbYxTRTfdRwsFq3cwF4LQ==
vscode-proxy-agent@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.5.2.tgz#0c90d24d353957b841d741da7b2701e3f0a044c4"
integrity sha512-1cCNPxrWIrmUwS+1XGaXxkh3G1y7z2fpXl1sT74OZvELaryQWYb3NMxMLJJ4Q/CpPLEyuhp/bAN7nzHxxFcQ5Q==
vscode-proxy-agent@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.6.0.tgz#3ea5c3c82f7abe945d690eb34b2c877cf5833f12"
integrity sha512-dvoLmEO/IxkbcNrRHH6ey8ITfvau4wDg01S+iAJ5Pq/FoAl2ZeE4cK5VEnQ2JHqM20kTLhyZfkjdHq6l7/T+xA==
dependencies:
debug "^3.1.0"
http-proxy-agent "^2.1.0"