@theia/core 1.73.0-next.9 → 1.73.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -16
- package/i18n/nls.cs.json +268 -36
- package/i18n/nls.de.json +268 -36
- package/i18n/nls.es.json +268 -36
- package/i18n/nls.fr.json +268 -36
- package/i18n/nls.hu.json +268 -36
- package/i18n/nls.it.json +268 -36
- package/i18n/nls.ja.json +268 -36
- package/i18n/nls.json +268 -36
- package/i18n/nls.ko.json +268 -36
- package/i18n/nls.pl.json +268 -36
- package/i18n/nls.pt-br.json +268 -36
- package/i18n/nls.ru.json +268 -36
- package/i18n/nls.tr.json +268 -36
- package/i18n/nls.zh-cn.json +268 -36
- package/i18n/nls.zh-tw.json +268 -36
- package/lib/browser/authentication-service.d.ts +2 -0
- package/lib/browser/authentication-service.d.ts.map +1 -1
- package/lib/browser/authentication-service.js +8 -2
- package/lib/browser/authentication-service.js.map +1 -1
- package/lib/browser/catalog.json +251 -72
- package/lib/browser/frontend-application.d.ts +2 -0
- package/lib/browser/frontend-application.d.ts.map +1 -1
- package/lib/browser/frontend-application.js +15 -9
- package/lib/browser/frontend-application.js.map +1 -1
- package/lib/browser/json-schema-store.d.ts +2 -0
- package/lib/browser/json-schema-store.d.ts.map +1 -1
- package/lib/browser/json-schema-store.js +8 -2
- package/lib/browser/json-schema-store.js.map +1 -1
- package/lib/browser/keybinding.js +2 -1
- package/lib/browser/keybinding.js.map +1 -1
- package/lib/browser/markdown-rendering/markdown.d.ts +1 -1
- package/lib/browser/markdown-rendering/markdown.d.ts.map +1 -1
- package/lib/browser/markdown-rendering/markdown.js +1 -1
- package/lib/browser/markdown-rendering/markdown.js.map +1 -1
- package/lib/browser/menu/composite-menu-node.js +4 -4
- package/lib/browser/menu/composite-menu-node.js.map +1 -1
- package/lib/browser/messaging/ws-connection-source.d.ts.map +1 -1
- package/lib/browser/messaging/ws-connection-source.js +1 -6
- package/lib/browser/messaging/ws-connection-source.js.map +1 -1
- package/lib/browser/preferences/frontend-config-preference-contributions.d.ts +2 -0
- package/lib/browser/preferences/frontend-config-preference-contributions.d.ts.map +1 -1
- package/lib/browser/preferences/frontend-config-preference-contributions.js +7 -1
- package/lib/browser/preferences/frontend-config-preference-contributions.js.map +1 -1
- package/lib/browser/preferences/preference-proxy.spec.js +7 -0
- package/lib/browser/preferences/preference-proxy.spec.js.map +1 -1
- package/lib/browser/preferences/preference-schema-provider.spec.js +3 -0
- package/lib/browser/preferences/preference-schema-provider.spec.js.map +1 -1
- package/lib/browser/preferences/preference-validation-service.d.ts +2 -0
- package/lib/browser/preferences/preference-validation-service.d.ts.map +1 -1
- package/lib/browser/preferences/preference-validation-service.js +11 -5
- package/lib/browser/preferences/preference-validation-service.js.map +1 -1
- package/lib/browser/preferences/preference-validation-service.spec.js +2 -0
- package/lib/browser/preferences/preference-validation-service.spec.js.map +1 -1
- package/lib/browser/secondary-window-handler.d.ts +2 -0
- package/lib/browser/secondary-window-handler.d.ts.map +1 -1
- package/lib/browser/secondary-window-handler.js +12 -6
- package/lib/browser/secondary-window-handler.js.map +1 -1
- package/lib/browser/shell/application-shell.d.ts +2 -0
- package/lib/browser/shell/application-shell.d.ts.map +1 -1
- package/lib/browser/shell/application-shell.js +9 -3
- package/lib/browser/shell/application-shell.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js +6 -6
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js +2 -2
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.js.map +1 -1
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js +131 -0
- package/lib/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.js.map +1 -1
- package/lib/browser/status-bar/status-bar.d.ts +2 -2
- package/lib/browser/status-bar/status-bar.d.ts.map +1 -1
- package/lib/browser/status-bar/status-bar.js.map +1 -1
- package/lib/browser/test/jsdom.d.ts +4 -0
- package/lib/browser/test/jsdom.d.ts.map +1 -1
- package/lib/browser/test/jsdom.js +18 -0
- package/lib/browser/test/jsdom.js.map +1 -1
- package/lib/browser/tree/tree-container.spec.js +4 -0
- package/lib/browser/tree/tree-container.spec.js.map +1 -1
- package/lib/browser/tree/tree-model.d.ts +1 -0
- package/lib/browser/tree/tree-model.d.ts.map +1 -1
- package/lib/browser/tree/tree-model.js +3 -0
- package/lib/browser/tree/tree-model.js.map +1 -1
- package/lib/browser/tree/tree-view-welcome-widget.d.ts +1 -0
- package/lib/browser/tree/tree-view-welcome-widget.d.ts.map +1 -1
- package/lib/browser/tree/tree-view-welcome-widget.js +8 -6
- package/lib/browser/tree/tree-view-welcome-widget.js.map +1 -1
- package/lib/browser/tree/tree-view-welcome-widget.spec.d.ts +2 -0
- package/lib/browser/tree/tree-view-welcome-widget.spec.d.ts.map +1 -0
- package/lib/browser/tree/tree-view-welcome-widget.spec.js +68 -0
- package/lib/browser/tree/tree-view-welcome-widget.spec.js.map +1 -0
- package/lib/browser/tree/tree-widget.js +2 -2
- package/lib/browser/tree/tree-widget.js.map +1 -1
- package/lib/browser/tree/tree.d.ts +24 -2
- package/lib/browser/tree/tree.d.ts.map +1 -1
- package/lib/browser/tree/tree.js +20 -2
- package/lib/browser/tree/tree.js.map +1 -1
- package/lib/browser/tree/tree.spec.js +27 -0
- package/lib/browser/tree/tree.spec.js.map +1 -1
- package/lib/browser/window/default-window-service.d.ts +2 -1
- package/lib/browser/window/default-window-service.d.ts.map +1 -1
- package/lib/browser/window/default-window-service.js +10 -5
- package/lib/browser/window/default-window-service.js.map +1 -1
- package/lib/browser/window/default-window-service.spec.js +2 -0
- package/lib/browser/window/default-window-service.spec.js.map +1 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts +2 -0
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.d.ts.map +1 -1
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.js +8 -2
- package/lib/browser-only/messaging/frontend-only-service-connection-provider.js.map +1 -1
- package/lib/common/command.d.ts +2 -0
- package/lib/common/command.d.ts.map +1 -1
- package/lib/common/command.js +10 -4
- package/lib/common/command.js.map +1 -1
- package/lib/common/preferences/injectable-preference-proxy.d.ts +3 -3
- package/lib/common/preferences/injectable-preference-proxy.d.ts.map +1 -1
- package/lib/common/preferences/injectable-preference-proxy.js +6 -1
- package/lib/common/preferences/injectable-preference-proxy.js.map +1 -1
- package/lib/common/preferences/preference-proxy.d.ts.map +1 -1
- package/lib/common/preferences/preference-proxy.js +6 -1
- package/lib/common/preferences/preference-proxy.js.map +1 -1
- package/lib/electron-main/electron-main-application.d.ts.map +1 -1
- package/lib/electron-main/electron-main-application.js +4 -2
- package/lib/electron-main/electron-main-application.js.map +1 -1
- package/lib/electron-node/hosting/electron-ws-origin-validator.d.ts.map +1 -1
- package/lib/electron-node/hosting/electron-ws-origin-validator.js +5 -2
- package/lib/electron-node/hosting/electron-ws-origin-validator.js.map +1 -1
- package/lib/electron-node/hosting/electron-ws-origin-validator.spec.d.ts +2 -0
- package/lib/electron-node/hosting/electron-ws-origin-validator.spec.d.ts.map +1 -0
- package/lib/electron-node/hosting/electron-ws-origin-validator.spec.js +59 -0
- package/lib/electron-node/hosting/electron-ws-origin-validator.spec.js.map +1 -0
- package/lib/electron-node/request/electron-backend-request-service.d.ts +2 -0
- package/lib/electron-node/request/electron-backend-request-service.d.ts.map +1 -1
- package/lib/electron-node/request/electron-backend-request-service.js +7 -1
- package/lib/electron-node/request/electron-backend-request-service.js.map +1 -1
- package/lib/electron-node/token/electron-token-backend-contribution.d.ts +2 -0
- package/lib/electron-node/token/electron-token-backend-contribution.d.ts.map +1 -1
- package/lib/electron-node/token/electron-token-backend-contribution.js +7 -1
- package/lib/electron-node/token/electron-token-backend-contribution.js.map +1 -1
- package/lib/electron-node/token/electron-token-validator.d.ts +2 -0
- package/lib/electron-node/token/electron-token-validator.d.ts.map +1 -1
- package/lib/electron-node/token/electron-token-validator.js +7 -1
- package/lib/electron-node/token/electron-token-validator.js.map +1 -1
- package/lib/node/backend-application-module.d.ts.map +1 -1
- package/lib/node/backend-application-module.js +2 -0
- package/lib/node/backend-application-module.js.map +1 -1
- package/lib/node/backend-application.d.ts +11 -0
- package/lib/node/backend-application.d.ts.map +1 -1
- package/lib/node/backend-application.js +40 -12
- package/lib/node/backend-application.js.map +1 -1
- package/lib/node/backend-application.spec.js +4 -2
- package/lib/node/backend-application.spec.js.map +1 -1
- package/lib/node/hosting/backend-hosting-module.d.ts.map +1 -1
- package/lib/node/hosting/backend-hosting-module.js +7 -0
- package/lib/node/hosting/backend-hosting-module.js.map +1 -1
- package/lib/node/hosting/browser-connection-token.d.ts +46 -0
- package/lib/node/hosting/browser-connection-token.d.ts.map +1 -0
- package/lib/node/hosting/browser-connection-token.js +120 -0
- package/lib/node/hosting/browser-connection-token.js.map +1 -0
- package/lib/node/hosting/browser-connection-token.spec.d.ts +2 -0
- package/lib/node/hosting/browser-connection-token.spec.d.ts.map +1 -0
- package/lib/node/hosting/browser-connection-token.spec.js +128 -0
- package/lib/node/hosting/browser-connection-token.spec.js.map +1 -0
- package/lib/node/hosting/ws-origin-validator.d.ts.map +1 -1
- package/lib/node/hosting/ws-origin-validator.js +21 -4
- package/lib/node/hosting/ws-origin-validator.js.map +1 -1
- package/lib/node/hosting/ws-origin-validator.spec.d.ts +2 -0
- package/lib/node/hosting/ws-origin-validator.spec.d.ts.map +1 -0
- package/lib/node/hosting/ws-origin-validator.spec.js +131 -0
- package/lib/node/hosting/ws-origin-validator.spec.js.map +1 -0
- package/lib/node/index.d.ts +1 -0
- package/lib/node/index.d.ts.map +1 -1
- package/lib/node/index.js +3 -1
- package/lib/node/index.js.map +1 -1
- package/lib/node/logger-cli-contribution.d.ts +6 -0
- package/lib/node/logger-cli-contribution.d.ts.map +1 -1
- package/lib/node/logger-cli-contribution.js +28 -5
- package/lib/node/logger-cli-contribution.js.map +1 -1
- package/lib/node/logger-cli-contribution.spec.js +54 -0
- package/lib/node/logger-cli-contribution.spec.js.map +1 -1
- package/lib/node/messaging/default-messaging-service.d.ts +2 -0
- package/lib/node/messaging/default-messaging-service.d.ts.map +1 -1
- package/lib/node/messaging/default-messaging-service.js +8 -2
- package/lib/node/messaging/default-messaging-service.js.map +1 -1
- package/lib/node/messaging/test/default-messaging-service.spec.js +2 -0
- package/lib/node/messaging/test/default-messaging-service.spec.js.map +1 -1
- package/lib/node/messaging/websocket-endpoint.d.ts +7 -0
- package/lib/node/messaging/websocket-endpoint.d.ts.map +1 -1
- package/lib/node/messaging/websocket-endpoint.js +21 -6
- package/lib/node/messaging/websocket-endpoint.js.map +1 -1
- package/lib/node/process-utils.d.ts +2 -0
- package/lib/node/process-utils.d.ts.map +1 -1
- package/lib/node/process-utils.js +8 -2
- package/lib/node/process-utils.js.map +1 -1
- package/lib/node/process-utils.spec.js +11 -9
- package/lib/node/process-utils.spec.js.map +1 -1
- package/lib/node/request/backend-request-facade.d.ts +19 -0
- package/lib/node/request/backend-request-facade.d.ts.map +1 -1
- package/lib/node/request/backend-request-facade.js +93 -3
- package/lib/node/request/backend-request-facade.js.map +1 -1
- package/lib/node/request/backend-request-facade.spec.d.ts +2 -0
- package/lib/node/request/backend-request-facade.spec.d.ts.map +1 -0
- package/lib/node/request/backend-request-facade.spec.js +171 -0
- package/lib/node/request/backend-request-facade.spec.js.map +1 -0
- package/package.json +29 -20
- package/src/browser/authentication-service.ts +6 -3
- package/src/browser/frontend-application.ts +13 -9
- package/src/browser/json-schema-store.ts +6 -2
- package/src/browser/keybinding.ts +2 -2
- package/src/browser/markdown-rendering/markdown.tsx +2 -2
- package/src/browser/menu/composite-menu-node.ts +4 -4
- package/src/browser/messaging/ws-connection-source.ts +1 -6
- package/src/browser/preferences/frontend-config-preference-contributions.ts +7 -2
- package/src/browser/preferences/preference-proxy.spec.ts +8 -0
- package/src/browser/preferences/preference-schema-provider.spec.ts +5 -0
- package/src/browser/preferences/preference-validation-service.spec.ts +3 -1
- package/src/browser/preferences/preference-validation-service.ts +9 -6
- package/src/browser/secondary-window-handler.ts +11 -7
- package/src/browser/shell/application-shell.ts +8 -4
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-menu-adapters.tsx +6 -6
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar-registry.ts +2 -2
- package/src/browser/shell/tab-bar-toolbar/tab-bar-toolbar.spec.ts +159 -1
- package/src/browser/status-bar/status-bar.tsx +3 -3
- package/src/browser/test/jsdom.ts +18 -0
- package/src/browser/tree/tree-container.spec.ts +6 -0
- package/src/browser/tree/tree-model.ts +4 -0
- package/src/browser/tree/tree-view-welcome-widget.spec.ts +82 -0
- package/src/browser/tree/tree-view-welcome-widget.tsx +10 -11
- package/src/browser/tree/tree-widget.tsx +2 -2
- package/src/browser/tree/tree.spec.ts +30 -0
- package/src/browser/tree/tree.ts +31 -4
- package/src/browser/window/default-window-service.spec.ts +3 -1
- package/src/browser/window/default-window-service.ts +9 -6
- package/src/browser-only/messaging/frontend-only-service-connection-provider.ts +8 -3
- package/src/common/command.ts +8 -4
- package/src/common/i18n/nls.metadata.json +2659 -374
- package/src/common/preferences/injectable-preference-proxy.ts +10 -4
- package/src/common/preferences/preference-proxy.ts +10 -4
- package/src/electron-main/electron-main-application.ts +4 -2
- package/src/electron-node/hosting/electron-ws-origin-validator.spec.ts +69 -0
- package/src/electron-node/hosting/electron-ws-origin-validator.ts +5 -2
- package/src/electron-node/request/electron-backend-request-service.ts +6 -2
- package/src/electron-node/token/electron-token-backend-contribution.ts +6 -2
- package/src/electron-node/token/electron-token-validator.ts +6 -2
- package/src/node/backend-application-module.ts +4 -2
- package/src/node/backend-application.spec.ts +5 -3
- package/src/node/backend-application.ts +33 -11
- package/src/node/hosting/backend-hosting-module.ts +10 -0
- package/src/node/hosting/browser-connection-token.spec.ts +167 -0
- package/src/node/hosting/browser-connection-token.ts +126 -0
- package/src/node/hosting/ws-origin-validator.spec.ts +193 -0
- package/src/node/hosting/ws-origin-validator.ts +23 -4
- package/src/node/index.ts +1 -0
- package/src/node/logger-cli-contribution.spec.ts +65 -0
- package/src/node/logger-cli-contribution.ts +32 -5
- package/src/node/messaging/default-messaging-service.ts +6 -2
- package/src/node/messaging/test/default-messaging-service.spec.ts +3 -1
- package/src/node/messaging/websocket-endpoint.ts +23 -7
- package/src/node/process-utils.spec.ts +12 -9
- package/src/node/process-utils.ts +7 -3
- package/src/node/request/backend-request-facade.spec.ts +203 -0
- package/src/node/request/backend-request-facade.ts +108 -3
|
@@ -22,6 +22,7 @@ import Route = require('route-parser');
|
|
|
22
22
|
import { Channel, ChannelMultiplexer } from '../../common/message-rpc/channel';
|
|
23
23
|
import { FrontendConnectionService } from './frontend-connection-service';
|
|
24
24
|
import { BackendApplicationContribution } from '../backend-application';
|
|
25
|
+
import { ILogger } from '../../common/logger';
|
|
25
26
|
|
|
26
27
|
export const MessagingContainer = Symbol('MessagingContainer');
|
|
27
28
|
export const MainChannel = Symbol('MainChannel');
|
|
@@ -37,6 +38,9 @@ export class DefaultMessagingService implements MessagingService, BackendApplica
|
|
|
37
38
|
@inject(ContributionProvider) @named(ConnectionContainerModule)
|
|
38
39
|
protected readonly connectionModules: ContributionProvider<interfaces.ContainerModule>;
|
|
39
40
|
|
|
41
|
+
@inject(ILogger) @named('core:DefaultMessagingService')
|
|
42
|
+
protected readonly logger: ILogger;
|
|
43
|
+
|
|
40
44
|
@inject(ContributionProvider) @named(MessagingService.Contribution)
|
|
41
45
|
protected readonly contributions: ContributionProvider<MessagingService.Contribution>;
|
|
42
46
|
|
|
@@ -62,8 +66,8 @@ export class DefaultMessagingService implements MessagingService, BackendApplica
|
|
|
62
66
|
const channelHandlers = this.getConnectionChannelHandlers(channel);
|
|
63
67
|
multiplexer.onDidOpenChannel(event => {
|
|
64
68
|
if (channelHandlers.route(event.id, event.channel)) {
|
|
65
|
-
|
|
66
|
-
event.channel.onClose(() =>
|
|
69
|
+
this.logger.debug(`Opening channel for service path '${event.id}'.`);
|
|
70
|
+
event.channel.onClose(() => this.logger.info(`Closing channel on service path '${event.id}'.`));
|
|
67
71
|
}
|
|
68
72
|
});
|
|
69
73
|
}
|
|
@@ -16,13 +16,14 @@
|
|
|
16
16
|
|
|
17
17
|
import { expect } from 'chai';
|
|
18
18
|
import { Container, injectable, preDestroy } from 'inversify';
|
|
19
|
-
import { ConnectionHandler, bindContributionProvider, servicesPath } from '../../../common';
|
|
19
|
+
import { ConnectionHandler, bindContributionProvider, servicesPath, ILogger } from '../../../common';
|
|
20
20
|
import { BasicChannel, Channel } from '../../../common/message-rpc/channel';
|
|
21
21
|
import { Uint8ArrayWriteBuffer } from '../../../common/message-rpc/uint8-array-message-buffer';
|
|
22
22
|
import { ConnectionContainerModule } from '../connection-container-module';
|
|
23
23
|
import { DefaultMessagingService, MessagingContainer } from '../default-messaging-service';
|
|
24
24
|
import { FrontendConnectionService } from '../frontend-connection-service';
|
|
25
25
|
import { MessagingService } from '../messaging-service';
|
|
26
|
+
import { MockLogger } from '../../../common/test/mock-logger';
|
|
26
27
|
|
|
27
28
|
describe('DefaultMessagingService', () => {
|
|
28
29
|
|
|
@@ -63,6 +64,7 @@ describe('DefaultMessagingService', () => {
|
|
|
63
64
|
}
|
|
64
65
|
};
|
|
65
66
|
container.bind(FrontendConnectionService).toConstantValue(frontendConnectionService);
|
|
67
|
+
container.bind(ILogger).to(MockLogger).inSingletonScope();
|
|
66
68
|
|
|
67
69
|
const messagingService = container.get(DefaultMessagingService);
|
|
68
70
|
messagingService.initialize();
|
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
import { MessagingService } from './messaging-service';
|
|
17
17
|
import * as http from 'http';
|
|
18
18
|
import * as https from 'https';
|
|
19
|
-
import { inject, injectable } from 'inversify';
|
|
19
|
+
import { inject, injectable, named } from 'inversify';
|
|
20
20
|
import { Server, Socket } from 'socket.io';
|
|
21
21
|
import { WsRequestValidator } from '../ws-request-validators';
|
|
22
22
|
import { MessagingListener } from './messaging-listeners';
|
|
23
23
|
import { ConnectionHandlers } from './default-messaging-service';
|
|
24
24
|
import { BackendApplicationContribution } from '../backend-application';
|
|
25
|
+
import { ILogger } from '../../common/logger';
|
|
25
26
|
|
|
26
27
|
@injectable()
|
|
27
28
|
export class WebsocketEndpoint implements BackendApplicationContribution {
|
|
@@ -31,6 +32,9 @@ export class WebsocketEndpoint implements BackendApplicationContribution {
|
|
|
31
32
|
@inject(MessagingListener)
|
|
32
33
|
protected readonly messagingListener: MessagingListener;
|
|
33
34
|
|
|
35
|
+
@inject(ILogger) @named('core:WebsocketEndpoint')
|
|
36
|
+
protected readonly logger: ILogger;
|
|
37
|
+
|
|
34
38
|
protected checkAliveTimeout = 30000; // 30 seconds
|
|
35
39
|
protected maxHttpBufferSize = 1e8; // 100 MB
|
|
36
40
|
|
|
@@ -44,14 +48,21 @@ export class WebsocketEndpoint implements BackendApplicationContribution {
|
|
|
44
48
|
const socketServer = new Server(server, {
|
|
45
49
|
pingInterval: this.checkAliveTimeout,
|
|
46
50
|
pingTimeout: this.checkAliveTimeout * 2,
|
|
47
|
-
maxHttpBufferSize: this.maxHttpBufferSize
|
|
51
|
+
maxHttpBufferSize: this.maxHttpBufferSize,
|
|
52
|
+
allowRequest: (req, callback) => {
|
|
53
|
+
// eslint-disable-next-line no-null/no-null
|
|
54
|
+
const noError = null;
|
|
55
|
+
this.wsRequestValidator.allowWsUpgrade(req).then(
|
|
56
|
+
allowed => callback(noError, allowed),
|
|
57
|
+
error => {
|
|
58
|
+
console.error('Error during WebSocket allowRequest validation:', error);
|
|
59
|
+
callback(error?.message ?? 'Validation error', false);
|
|
60
|
+
}
|
|
61
|
+
);
|
|
62
|
+
}
|
|
48
63
|
});
|
|
49
64
|
// Accept every namespace by using /.*/
|
|
50
65
|
socketServer.of(/.*/).on('connection', async socket => {
|
|
51
|
-
const request = socket.request;
|
|
52
|
-
// Socket.io strips the `origin` header of the incoming request
|
|
53
|
-
// We provide a `fix-origin` header in the `WebSocketConnectionProvider`
|
|
54
|
-
request.headers.origin = request.headers['fix-origin'] as string;
|
|
55
66
|
if (await this.allowConnect(socket.request)) {
|
|
56
67
|
await this.handleConnection(socket);
|
|
57
68
|
this.messagingListener.onDidWebSocketUpgrade(socket.request, socket);
|
|
@@ -61,6 +72,11 @@ export class WebsocketEndpoint implements BackendApplicationContribution {
|
|
|
61
72
|
});
|
|
62
73
|
}
|
|
63
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Secondary validation after connection. The primary check happens in the
|
|
77
|
+
* Socket.IO `allowRequest` callback at handshake time; this is kept as
|
|
78
|
+
* defense-in-depth in case a Socket.IO upgrade path bypasses `allowRequest`.
|
|
79
|
+
*/
|
|
64
80
|
protected async allowConnect(request: http.IncomingMessage): Promise<boolean> {
|
|
65
81
|
try {
|
|
66
82
|
return this.wsRequestValidator.allowWsUpgrade(request);
|
|
@@ -72,7 +88,7 @@ export class WebsocketEndpoint implements BackendApplicationContribution {
|
|
|
72
88
|
protected async handleConnection(socket: Socket): Promise<void> {
|
|
73
89
|
const pathname = socket.nsp.name;
|
|
74
90
|
if (pathname && !this.wsHandlers.route(pathname, socket)) {
|
|
75
|
-
|
|
91
|
+
this.logger.error('Cannot find a ws handler for the path: ' + pathname);
|
|
76
92
|
}
|
|
77
93
|
}
|
|
78
94
|
}
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import { expect } from 'chai';
|
|
18
|
+
import * as sinon from 'sinon';
|
|
18
19
|
import { Container } from 'inversify';
|
|
19
20
|
import { ProcessUtils } from './process-utils';
|
|
21
|
+
import { ILogger } from '../common';
|
|
22
|
+
import { MockLogger } from '../common/test/mock-logger';
|
|
20
23
|
|
|
21
24
|
/** PPID, PID */
|
|
22
25
|
const mockPsOutput = `\
|
|
@@ -36,6 +39,8 @@ describe('ProcessUtils', () => {
|
|
|
36
39
|
beforeEach(() => {
|
|
37
40
|
const container = new Container();
|
|
38
41
|
container.bind(ProcessUtils).toSelf().inSingletonScope();
|
|
42
|
+
container.bind(ILogger).to(MockLogger).inSingletonScope();
|
|
43
|
+
|
|
39
44
|
coreProcessManager = container.get(ProcessUtils);
|
|
40
45
|
});
|
|
41
46
|
|
|
@@ -48,8 +53,7 @@ describe('ProcessUtils', () => {
|
|
|
48
53
|
|
|
49
54
|
describe('#unixTerminateProcessTree', () => {
|
|
50
55
|
let originalKill: typeof process.kill;
|
|
51
|
-
let
|
|
52
|
-
let loggedErrors: unknown[];
|
|
56
|
+
let errorStub: sinon.SinonStub;
|
|
53
57
|
|
|
54
58
|
function throwingKill(code: string): typeof process.kill {
|
|
55
59
|
return (() => {
|
|
@@ -61,9 +65,8 @@ describe('ProcessUtils', () => {
|
|
|
61
65
|
|
|
62
66
|
beforeEach(() => {
|
|
63
67
|
originalKill = process.kill;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
console.error = (...args: unknown[]) => { loggedErrors.push(args); };
|
|
68
|
+
const mockLogger = (coreProcessManager as unknown as { logger: ILogger }).logger;
|
|
69
|
+
errorStub = sinon.stub(mockLogger, 'error');
|
|
67
70
|
// One child plus the parent; report the parent as its own group leader so the `kill(-ppid)` branch runs too.
|
|
68
71
|
coreProcessManager['unixGetChildrenRecursive'] = () => new Set([424242]);
|
|
69
72
|
coreProcessManager['unixGetPGID'] = (pid: number) => pid;
|
|
@@ -71,26 +74,26 @@ describe('ProcessUtils', () => {
|
|
|
71
74
|
|
|
72
75
|
afterEach(() => {
|
|
73
76
|
process.kill = originalKill;
|
|
74
|
-
|
|
77
|
+
sinon.restore();
|
|
75
78
|
});
|
|
76
79
|
|
|
77
80
|
it('does not throw or log when processes in the tree are already gone (ESRCH)', () => {
|
|
78
81
|
process.kill = throwingKill('ESRCH');
|
|
79
82
|
expect(() => coreProcessManager['unixTerminateProcessTree'](424243)).to.not.throw();
|
|
80
|
-
expect(
|
|
83
|
+
expect(errorStub.called).to.be.false;
|
|
81
84
|
});
|
|
82
85
|
|
|
83
86
|
it('logs unexpected kill errors (e.g. EPERM) without throwing', () => {
|
|
84
87
|
process.kill = throwingKill('EPERM');
|
|
85
88
|
expect(() => coreProcessManager['unixTerminateProcessTree'](424243)).to.not.throw();
|
|
86
|
-
expect(
|
|
89
|
+
expect(errorStub.called).to.be.true;
|
|
87
90
|
});
|
|
88
91
|
|
|
89
92
|
it('does not throw when a kill rejects with a value that has no code (e.g. undefined)', () => {
|
|
90
93
|
const thrown: unknown = undefined;
|
|
91
94
|
process.kill = (() => { throw thrown; }) as typeof process.kill;
|
|
92
95
|
expect(() => coreProcessManager['unixTerminateProcessTree'](424243)).to.not.throw();
|
|
93
|
-
expect(
|
|
96
|
+
expect(errorStub.called).to.be.true;
|
|
94
97
|
});
|
|
95
98
|
});
|
|
96
99
|
});
|
|
@@ -15,7 +15,8 @@
|
|
|
15
15
|
// *****************************************************************************
|
|
16
16
|
|
|
17
17
|
import * as cp from 'child_process';
|
|
18
|
-
import { injectable } from 'inversify';
|
|
18
|
+
import { injectable, inject, named } from 'inversify';
|
|
19
|
+
import { ILogger } from '../common/logger';
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
22
|
* `@theia/core` service with some process-related utilities.
|
|
@@ -23,6 +24,9 @@ import { injectable } from 'inversify';
|
|
|
23
24
|
@injectable()
|
|
24
25
|
export class ProcessUtils {
|
|
25
26
|
|
|
27
|
+
@inject(ILogger) @named('core:ProcessUtils')
|
|
28
|
+
protected readonly logger: ILogger;
|
|
29
|
+
|
|
26
30
|
terminateProcessTree(ppid: number): void {
|
|
27
31
|
if (process.platform === 'win32') {
|
|
28
32
|
this.winTerminateProcessTree(ppid);
|
|
@@ -39,7 +43,7 @@ export class ProcessUtils {
|
|
|
39
43
|
// taskkill may exit with a non-zero code when some child processes have already exited.
|
|
40
44
|
// This is expected during shutdown — log but don't throw.
|
|
41
45
|
if (result.status !== 0) {
|
|
42
|
-
|
|
46
|
+
this.logger.warn(`taskkill.exe exited with ${result.status} for PID ${ppid}. Output:\n${JSON.stringify(result.output)}`);
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
49
|
|
|
@@ -66,7 +70,7 @@ export class ProcessUtils {
|
|
|
66
70
|
// ESRCH means the process is already gone, which is the goal here. Log
|
|
67
71
|
// anything else but keep going so the rest of the tree is still killed.
|
|
68
72
|
if ((error as NodeJS.ErrnoException | undefined)?.code !== 'ESRCH') {
|
|
69
|
-
|
|
73
|
+
this.logger.error(`[${pid}] failed to kill`, error);
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// *****************************************************************************
|
|
2
|
+
// Copyright (C) 2026 EclipseSource GmbH and others.
|
|
3
|
+
//
|
|
4
|
+
// This program and the accompanying materials are made available under the
|
|
5
|
+
// terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
// http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
//
|
|
8
|
+
// This Source Code may also be made available under the following Secondary
|
|
9
|
+
// Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
// with the GNU Classpath Exception which is available at
|
|
12
|
+
// https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
//
|
|
14
|
+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
// *****************************************************************************
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import * as sinon from 'sinon';
|
|
19
|
+
import { BackendRequestFacade, isUrlAllowed } from './backend-request-facade';
|
|
20
|
+
import { BackendApplicationConfigProvider } from '../backend-application-config-provider';
|
|
21
|
+
|
|
22
|
+
describe('isUrlAllowed', () => {
|
|
23
|
+
|
|
24
|
+
it('should allow a URL that matches an exact origin pattern', () => {
|
|
25
|
+
const patterns = ['https://open-vsx.org'];
|
|
26
|
+
expect(isUrlAllowed('https://open-vsx.org/api/extensions', patterns)).to.be.true;
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('should allow a URL with a path when pattern has no path', () => {
|
|
30
|
+
const patterns = ['https://example.com'];
|
|
31
|
+
expect(isUrlAllowed('https://example.com/some/deep/path?query=1', patterns)).to.be.true;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should allow a URL that exactly matches the origin', () => {
|
|
35
|
+
const patterns = ['https://example.com'];
|
|
36
|
+
expect(isUrlAllowed('https://example.com', patterns)).to.be.true;
|
|
37
|
+
expect(isUrlAllowed('https://example.com/', patterns)).to.be.true;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should reject a URL with a different scheme', () => {
|
|
41
|
+
const patterns = ['https://example.com'];
|
|
42
|
+
expect(isUrlAllowed('http://example.com/path', patterns)).to.be.false;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should reject a URL with a different host', () => {
|
|
46
|
+
const patterns = ['https://example.com'];
|
|
47
|
+
expect(isUrlAllowed('https://evil.com/path', patterns)).to.be.false;
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should reject a URL with a different port', () => {
|
|
51
|
+
const patterns = ['https://example.com'];
|
|
52
|
+
expect(isUrlAllowed('https://example.com:8443/path', patterns)).to.be.false;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should allow a URL when port matches explicitly', () => {
|
|
56
|
+
const patterns = ['https://example.com:8443'];
|
|
57
|
+
expect(isUrlAllowed('https://example.com:8443/path', patterns)).to.be.true;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('should treat default ports as equivalent', () => {
|
|
61
|
+
const patterns = ['https://example.com:443'];
|
|
62
|
+
expect(isUrlAllowed('https://example.com/path', patterns)).to.be.true;
|
|
63
|
+
|
|
64
|
+
const httpPatterns = ['http://example.com:80'];
|
|
65
|
+
expect(isUrlAllowed('http://example.com/path', httpPatterns)).to.be.true;
|
|
66
|
+
|
|
67
|
+
const implicitPatterns = ['http://example.com'];
|
|
68
|
+
expect(isUrlAllowed('http://example.com:80/path', implicitPatterns)).to.be.true;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should match wildcard subdomains', () => {
|
|
72
|
+
const patterns = ['https://*.example.com'];
|
|
73
|
+
expect(isUrlAllowed('https://foo.example.com/path', patterns)).to.be.true;
|
|
74
|
+
expect(isUrlAllowed('https://bar.baz.example.com/path', patterns)).to.be.true;
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should not match the bare domain with a wildcard subdomain pattern', () => {
|
|
78
|
+
const patterns = ['https://*.example.com'];
|
|
79
|
+
expect(isUrlAllowed('https://example.com/path', patterns)).to.be.false;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should reject non-http(s) schemes', () => {
|
|
83
|
+
const patterns = ['file:///etc/passwd'];
|
|
84
|
+
expect(isUrlAllowed('file:///etc/passwd', patterns)).to.be.false;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should reject ftp scheme', () => {
|
|
88
|
+
const patterns = ['ftp://files.example.com'];
|
|
89
|
+
expect(isUrlAllowed('ftp://files.example.com/data', patterns)).to.be.false;
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('should reject a URL when target uses non-http(s) scheme even if patterns exist', () => {
|
|
93
|
+
const patterns = ['https://example.com'];
|
|
94
|
+
expect(isUrlAllowed('file:///etc/passwd', patterns)).to.be.false;
|
|
95
|
+
expect(isUrlAllowed('ftp://example.com/data', patterns)).to.be.false;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should reject all URLs when allowlist is empty', () => {
|
|
99
|
+
expect(isUrlAllowed('https://example.com', [])).to.be.false;
|
|
100
|
+
expect(isUrlAllowed('http://localhost:3000', [])).to.be.false;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should reject invalid URLs', () => {
|
|
104
|
+
const patterns = ['https://example.com'];
|
|
105
|
+
expect(isUrlAllowed('not a url', patterns)).to.be.false;
|
|
106
|
+
expect(isUrlAllowed('', patterns)).to.be.false;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should allow private IPs only when explicitly in the allowlist', () => {
|
|
110
|
+
expect(isUrlAllowed('http://127.0.0.1:8080/secret', [])).to.be.false;
|
|
111
|
+
expect(isUrlAllowed('http://127.0.0.1:8080/secret', ['https://example.com'])).to.be.false;
|
|
112
|
+
|
|
113
|
+
const patternsWithLoopback = ['http://127.0.0.1:8080'];
|
|
114
|
+
expect(isUrlAllowed('http://127.0.0.1:8080/secret', patternsWithLoopback)).to.be.true;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should reject cloud metadata endpoint by default', () => {
|
|
118
|
+
expect(isUrlAllowed('http://169.254.169.254/latest/meta-data/', [])).to.be.false;
|
|
119
|
+
expect(isUrlAllowed('http://169.254.169.254/latest/meta-data/', ['https://example.com'])).to.be.false;
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should merge multiple patterns correctly', () => {
|
|
123
|
+
const patterns = [
|
|
124
|
+
'https://open-vsx.org',
|
|
125
|
+
'https://marketplace.example.com',
|
|
126
|
+
'https://*.cdn.example.com'
|
|
127
|
+
];
|
|
128
|
+
expect(isUrlAllowed('https://open-vsx.org/api/v2/extensions', patterns)).to.be.true;
|
|
129
|
+
expect(isUrlAllowed('https://marketplace.example.com/download', patterns)).to.be.true;
|
|
130
|
+
expect(isUrlAllowed('https://files.cdn.example.com/ext.vsix', patterns)).to.be.true;
|
|
131
|
+
expect(isUrlAllowed('https://evil.com/malware', patterns)).to.be.false;
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should handle patterns with trailing slashes', () => {
|
|
135
|
+
const patterns = ['https://example.com/'];
|
|
136
|
+
expect(isUrlAllowed('https://example.com/path', patterns)).to.be.true;
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('should handle invalid patterns gracefully', () => {
|
|
140
|
+
const patterns = ['not a valid pattern', 'https://example.com'];
|
|
141
|
+
expect(isUrlAllowed('https://example.com/path', patterns)).to.be.true;
|
|
142
|
+
expect(isUrlAllowed('https://other.com/path', patterns)).to.be.false;
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should prevent host spoofing via subdomain prefix', () => {
|
|
146
|
+
const patterns = ['https://example.com'];
|
|
147
|
+
expect(isUrlAllowed('https://example.com.evil.com/path', patterns)).to.be.false;
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
describe('BackendRequestFacade', () => {
|
|
152
|
+
|
|
153
|
+
describe('configure', () => {
|
|
154
|
+
|
|
155
|
+
let configProviderStub: sinon.SinonStub;
|
|
156
|
+
|
|
157
|
+
beforeEach(() => {
|
|
158
|
+
configProviderStub = sinon.stub(BackendApplicationConfigProvider, 'get');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
afterEach(() => {
|
|
162
|
+
configProviderStub.restore();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should be a no-op when configureProxyFromPreferences is false', async () => {
|
|
166
|
+
configProviderStub.returns({
|
|
167
|
+
singleInstance: true,
|
|
168
|
+
frontendConnectionTimeout: 0,
|
|
169
|
+
configurationFolder: '.theia',
|
|
170
|
+
configureProxyFromPreferences: false
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const facade = new BackendRequestFacade();
|
|
174
|
+
const configureStub = sinon.stub().resolves();
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
176
|
+
(facade as any).requestService = {
|
|
177
|
+
configure: configureStub
|
|
178
|
+
};
|
|
179
|
+
await facade.configure({ proxyUrl: 'http://evil.com', strictSSL: false });
|
|
180
|
+
expect(configureStub.called).to.be.false;
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('should pass through to the underlying request service when configureProxyFromPreferences is true', async () => {
|
|
184
|
+
configProviderStub.returns({
|
|
185
|
+
singleInstance: true,
|
|
186
|
+
frontendConnectionTimeout: 0,
|
|
187
|
+
configurationFolder: '.theia',
|
|
188
|
+
configureProxyFromPreferences: true
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const facade = new BackendRequestFacade();
|
|
192
|
+
const configureStub = sinon.stub().resolves();
|
|
193
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
194
|
+
(facade as any).requestService = {
|
|
195
|
+
configure: configureStub
|
|
196
|
+
};
|
|
197
|
+
const config = { proxyUrl: 'http://my-proxy.com:8080', strictSSL: true, proxyAuthorization: 'Bearer token' };
|
|
198
|
+
await facade.configure(config);
|
|
199
|
+
expect(configureStub.calledOnce).to.be.true;
|
|
200
|
+
expect(configureStub.firstCall.args[0]).to.deep.equal(config);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
});
|
|
@@ -14,8 +14,87 @@
|
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
16
|
|
|
17
|
-
import { inject, injectable } from 'inversify';
|
|
17
|
+
import { inject, injectable, named, optional } from 'inversify';
|
|
18
18
|
import { RequestConfiguration, RequestContext, RequestOptions, RequestService } from '@theia/request';
|
|
19
|
+
import { ContributionProvider, MaybePromise } from '../../common';
|
|
20
|
+
import { BackendApplicationConfigProvider } from '../backend-application-config-provider';
|
|
21
|
+
|
|
22
|
+
export const BackendRequestAllowedContribution = Symbol('BackendRequestAllowedContribution');
|
|
23
|
+
export interface BackendRequestAllowedContribution {
|
|
24
|
+
/**
|
|
25
|
+
* Returns URL patterns that should be allowed through the backend request facade.
|
|
26
|
+
* Patterns are matched against request URLs.
|
|
27
|
+
*
|
|
28
|
+
* Supports:
|
|
29
|
+
* - Exact base URLs: `https://open-vsx.org` (allows any path under this origin)
|
|
30
|
+
* - Wildcard subdomains: `https://*.github.com` (allows any subdomain)
|
|
31
|
+
*
|
|
32
|
+
* Only `http:` and `https:` schemes are permitted.
|
|
33
|
+
*/
|
|
34
|
+
getAllowedUrlPatterns(): MaybePromise<string[]>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ALLOWED_SCHEMES = new Set(['http:', 'https:']);
|
|
38
|
+
|
|
39
|
+
export function isUrlAllowed(url: string, allowedPatterns: string[]): boolean {
|
|
40
|
+
let parsed: URL;
|
|
41
|
+
try {
|
|
42
|
+
parsed = new URL(url);
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (!ALLOWED_SCHEMES.has(parsed.protocol)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
for (const pattern of allowedPatterns) {
|
|
50
|
+
if (matchesPattern(parsed, pattern)) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function matchesPattern(target: URL, pattern: string): boolean {
|
|
58
|
+
let patternUrl: URL;
|
|
59
|
+
try {
|
|
60
|
+
patternUrl = new URL(pattern);
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (!ALLOWED_SCHEMES.has(patternUrl.protocol)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (target.protocol !== patternUrl.protocol) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const targetPort = getEffectivePort(target);
|
|
71
|
+
const patternPort = getEffectivePort(patternUrl);
|
|
72
|
+
if (targetPort !== patternPort) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
return matchesHost(target.hostname, patternUrl.hostname);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getEffectivePort(url: URL): string {
|
|
79
|
+
if (url.port) {
|
|
80
|
+
return url.port;
|
|
81
|
+
}
|
|
82
|
+
if (url.protocol === 'https:') {
|
|
83
|
+
return '443';
|
|
84
|
+
}
|
|
85
|
+
if (url.protocol === 'http:') {
|
|
86
|
+
return '80';
|
|
87
|
+
}
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function matchesHost(targetHost: string, patternHost: string): boolean {
|
|
92
|
+
if (patternHost.startsWith('*.')) {
|
|
93
|
+
const suffix = patternHost.substring(1); // e.g. ".example.com"
|
|
94
|
+
return targetHost.endsWith(suffix) && targetHost.length > suffix.length;
|
|
95
|
+
}
|
|
96
|
+
return targetHost === patternHost;
|
|
97
|
+
}
|
|
19
98
|
|
|
20
99
|
@injectable()
|
|
21
100
|
export class BackendRequestFacade implements RequestService {
|
|
@@ -23,11 +102,23 @@ export class BackendRequestFacade implements RequestService {
|
|
|
23
102
|
@inject(RequestService)
|
|
24
103
|
protected readonly requestService: RequestService;
|
|
25
104
|
|
|
26
|
-
|
|
27
|
-
|
|
105
|
+
@inject(ContributionProvider) @named(BackendRequestAllowedContribution) @optional()
|
|
106
|
+
protected readonly allowedContributions?: ContributionProvider<BackendRequestAllowedContribution>;
|
|
107
|
+
|
|
108
|
+
protected cachedAllowedPatterns: string[] | undefined;
|
|
109
|
+
|
|
110
|
+
async configure(config: RequestConfiguration): Promise<void> {
|
|
111
|
+
if (BackendApplicationConfigProvider.get().configureProxyFromPreferences) {
|
|
112
|
+
return this.requestService.configure(config);
|
|
113
|
+
}
|
|
28
114
|
}
|
|
29
115
|
|
|
30
116
|
async request(options: RequestOptions): Promise<RequestContext> {
|
|
117
|
+
const patterns = await this.getAllowedPatterns();
|
|
118
|
+
const url = options.url;
|
|
119
|
+
if (!url || !isUrlAllowed(url, patterns)) {
|
|
120
|
+
throw new Error(`Request to URL '${url}' is not allowed. The URL does not match any allowed pattern.`);
|
|
121
|
+
}
|
|
31
122
|
const context = await this.requestService.request(options);
|
|
32
123
|
return RequestContext.compress(context);
|
|
33
124
|
}
|
|
@@ -36,4 +127,18 @@ export class BackendRequestFacade implements RequestService {
|
|
|
36
127
|
return this.requestService.resolveProxy(url);
|
|
37
128
|
}
|
|
38
129
|
|
|
130
|
+
protected async getAllowedPatterns(): Promise<string[]> {
|
|
131
|
+
if (this.cachedAllowedPatterns) {
|
|
132
|
+
return this.cachedAllowedPatterns;
|
|
133
|
+
}
|
|
134
|
+
const patterns: string[] = [];
|
|
135
|
+
if (this.allowedContributions) {
|
|
136
|
+
for (const contribution of this.allowedContributions.getContributions()) {
|
|
137
|
+
const contributed = await contribution.getAllowedUrlPatterns();
|
|
138
|
+
patterns.push(...contributed);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
this.cachedAllowedPatterns = patterns;
|
|
142
|
+
return patterns;
|
|
143
|
+
}
|
|
39
144
|
}
|