mirror of
https://github.com/coder/code-server.git
synced 2026-05-08 21:37:27 +02:00
Update to VS Code 1.52.1
This commit is contained in:
@@ -7,7 +7,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IExtensionIdentifier, IGlobalExtensionEnablementService, DISABLED_EXTENSIONS_STORAGE_PATH } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, IStorageValueChangeEvent, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
|
||||
export class GlobalExtensionEnablementService extends Disposable implements IGlobalExtensionEnablementService {
|
||||
@@ -96,7 +96,7 @@ export class StorageManager extends Disposable {
|
||||
|
||||
constructor(private storageService: IStorageService) {
|
||||
super();
|
||||
this._register(storageService.onDidChangeStorage(e => this.onDidStorageChange(e)));
|
||||
this._register(storageService.onDidChangeValue(e => this.onDidStorageChange(e)));
|
||||
}
|
||||
|
||||
get(key: string, scope: StorageScope): IExtensionIdentifier[] {
|
||||
@@ -127,14 +127,14 @@ export class StorageManager extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private onDidStorageChange(workspaceStorageChangeEvent: IWorkspaceStorageChangeEvent): void {
|
||||
if (workspaceStorageChangeEvent.scope === StorageScope.GLOBAL) {
|
||||
if (!isUndefinedOrNull(this.storage[workspaceStorageChangeEvent.key])) {
|
||||
const newValue = this._get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
|
||||
if (newValue !== this.storage[workspaceStorageChangeEvent.key]) {
|
||||
const oldValues = this.get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
|
||||
delete this.storage[workspaceStorageChangeEvent.key];
|
||||
const newValues = this.get(workspaceStorageChangeEvent.key, workspaceStorageChangeEvent.scope);
|
||||
private onDidStorageChange(storageChangeEvent: IStorageValueChangeEvent): void {
|
||||
if (storageChangeEvent.scope === StorageScope.GLOBAL) {
|
||||
if (!isUndefinedOrNull(this.storage[storageChangeEvent.key])) {
|
||||
const newValue = this._get(storageChangeEvent.key, storageChangeEvent.scope);
|
||||
if (newValue !== this.storage[storageChangeEvent.key]) {
|
||||
const oldValues = this.get(storageChangeEvent.key, storageChangeEvent.scope);
|
||||
delete this.storage[storageChangeEvent.key];
|
||||
const newValues = this.get(storageChangeEvent.key, storageChangeEvent.scope);
|
||||
const added = oldValues.filter(oldValue => !newValues.some(newValue => areSameExtensions(oldValue, newValue)));
|
||||
const removed = newValues.filter(newValue => !oldValues.some(oldValue => areSameExtensions(oldValue, newValue)));
|
||||
if (added.length || removed.length) {
|
||||
@@ -151,7 +151,8 @@ export class StorageManager extends Disposable {
|
||||
|
||||
private _set(key: string, value: string | undefined, scope: StorageScope): void {
|
||||
if (value) {
|
||||
this.storageService.store(key, value, scope);
|
||||
// Enablement state is synced separately through extensions
|
||||
this.storageService.store(key, value, scope, StorageTarget.MACHINE);
|
||||
} else {
|
||||
this.storageService.remove(key, scope);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import { IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { getServiceMachineId } from 'vs/platform/serviceMachineId/common/serviceMachineId';
|
||||
import { optional } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { joinPath } from 'vs/base/common/resources';
|
||||
@@ -803,7 +803,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService {
|
||||
|
||||
export async function resolveMarketplaceHeaders(version: string, environmentService: IEnvironmentService, fileService: IFileService, storageService: {
|
||||
get: (key: string, scope: StorageScope) => string | undefined,
|
||||
store: (key: string, value: string, scope: StorageScope) => void
|
||||
store: (key: string, value: string, scope: StorageScope, target: StorageTarget) => void
|
||||
} | undefined): Promise<{ [key: string]: string; }> {
|
||||
const headers: IHeaders = {
|
||||
'X-Market-Client-Id': `VSCode ${version}`,
|
||||
|
||||
@@ -201,7 +201,8 @@ export class ExtensionManagementError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean };
|
||||
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean };
|
||||
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };
|
||||
|
||||
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
|
||||
export interface IExtensionManagementService {
|
||||
@@ -218,7 +219,7 @@ export interface IExtensionManagementService {
|
||||
install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension>;
|
||||
canInstall(extension: IGalleryExtension): Promise<boolean>;
|
||||
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
|
||||
uninstall(extension: ILocalExtension, force?: boolean): Promise<void>;
|
||||
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
|
||||
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
|
||||
getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>;
|
||||
getExtensionsReport(): Promise<IReportedExtension[]>;
|
||||
|
||||
@@ -4,12 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { URI, UriComponents } from 'vs/base/common/uri';
|
||||
import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
function transformIncomingURI(uri: UriComponents, transformer: IURITransformer | null): URI {
|
||||
return URI.revive(transformer ? transformer.transformIncoming(uri) : uri);
|
||||
@@ -77,18 +78,31 @@ export class ExtensionManagementChannel implements IServerChannel {
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionManagementChannelClient implements IExtensionManagementService {
|
||||
export class ExtensionManagementChannelClient extends Disposable implements IExtensionManagementService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private readonly _onInstallExtension = this._register(new Emitter<InstallExtensionEvent>());
|
||||
readonly onInstallExtension = this._onInstallExtension.event;
|
||||
|
||||
private readonly _onDidInstallExtension = this._register(new Emitter<DidInstallExtensionEvent>());
|
||||
readonly onDidInstallExtension = this._onDidInstallExtension.event;
|
||||
|
||||
private readonly _onUninstallExtension = this._register(new Emitter<IExtensionIdentifier>());
|
||||
readonly onUninstallExtension = this._onUninstallExtension.event;
|
||||
|
||||
private readonly _onDidUninstallExtension = this._register(new Emitter<DidUninstallExtensionEvent>());
|
||||
readonly onDidUninstallExtension = this._onDidUninstallExtension.event;
|
||||
|
||||
constructor(
|
||||
private readonly channel: IChannel,
|
||||
) { }
|
||||
|
||||
get onInstallExtension(): Event<InstallExtensionEvent> { return this.channel.listen('onInstallExtension'); }
|
||||
get onDidInstallExtension(): Event<DidInstallExtensionEvent> { return Event.map(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension'), i => ({ ...i, local: i.local ? transformIncomingExtension(i.local, null) : i.local })); }
|
||||
get onUninstallExtension(): Event<IExtensionIdentifier> { return this.channel.listen('onUninstallExtension'); }
|
||||
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this.channel.listen('onDidUninstallExtension'); }
|
||||
) {
|
||||
super();
|
||||
this._register(this.channel.listen<InstallExtensionEvent>('onInstallExtension')(e => this._onInstallExtension.fire(e)));
|
||||
this._register(this.channel.listen<DidInstallExtensionEvent>('onDidInstallExtension')(e => this._onDidInstallExtension.fire({ ...e, local: e.local ? transformIncomingExtension(e.local, null) : e.local })));
|
||||
this._register(this.channel.listen<IExtensionIdentifier>('onUninstallExtension')(e => this._onUninstallExtension.fire(e)));
|
||||
this._register(this.channel.listen<DidUninstallExtensionEvent>('onDidUninstallExtension')(e => this._onDidUninstallExtension.fire(e)));
|
||||
}
|
||||
|
||||
zip(extension: ILocalExtension): Promise<URI> {
|
||||
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(<UriComponents>result)));
|
||||
@@ -114,8 +128,8 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
|
||||
return Promise.resolve(this.channel.call<ILocalExtension>('installFromGallery', [extension, installOptions])).then(local => transformIncomingExtension(local, null));
|
||||
}
|
||||
|
||||
uninstall(extension: ILocalExtension, force = false): Promise<void> {
|
||||
return Promise.resolve(this.channel.call('uninstall', [extension!, force]));
|
||||
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
|
||||
return Promise.resolve(this.channel.call('uninstall', [extension!, options]));
|
||||
}
|
||||
|
||||
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export const IExtensionUrlTrustService = createDecorator<IExtensionUrlTrustService>('extensionUrlTrustService');
|
||||
|
||||
export interface IExtensionUrlTrustService {
|
||||
readonly _serviceBrand: undefined;
|
||||
isExtensionUrlTrusted(extensionId: string, url: string): Promise<boolean>;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import { disposableTimeout, timeout } from 'vs/base/common/async';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IExtensionRecommendationNotificationService, RecommendationsNotificationResult, RecommendationSource } from 'vs/platform/extensionRecommendations/common/extensionRecommendations';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
|
||||
type ExeExtensionRecommendationsClassification = {
|
||||
extensionId: { classification: 'PublicNonPersonalData', purpose: 'FeatureInsight' };
|
||||
@@ -241,7 +241,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
|
||||
}
|
||||
|
||||
private updateLastPromptedMediumExeTime(value: number): void {
|
||||
this.storageService.store(lastPromptedMediumImpExeTimeStorageKey, value, StorageScope.GLOBAL);
|
||||
this.storageService.store(lastPromptedMediumImpExeTimeStorageKey, value, StorageScope.GLOBAL, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
private getPromptedExecutableTips(): IStringDictionary<string[]> {
|
||||
@@ -251,7 +251,7 @@ export class ExtensionTipsService extends BaseExtensionTipsService {
|
||||
private addToRecommendedExecutables(exeName: string, tips: IExecutableBasedExtensionTip[]) {
|
||||
const promptedExecutableTips = this.getPromptedExecutableTips();
|
||||
promptedExecutableTips[exeName] = tips.map(({ extensionId }) => extensionId.toLowerCase());
|
||||
this.storageService.store(promptedExecutableTipsStorageKey, JSON.stringify(promptedExecutableTips), StorageScope.GLOBAL);
|
||||
this.storageService.store(promptedExecutableTipsStorageKey, JSON.stringify(promptedExecutableTips), StorageScope.GLOBAL, StorageTarget.USER);
|
||||
}
|
||||
|
||||
private groupByInstalled(recommendationsToSuggest: string[], local: ILocalExtension[]): { installed: string[], uninstalled: string[] } {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { rename } from 'vs/base/node/pfs';
|
||||
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
|
||||
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -13,6 +14,7 @@ import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/ex
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as semver from 'vs/base/common/semver/semver';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
|
||||
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;
|
||||
|
||||
@@ -36,8 +38,21 @@ export class ExtensionsDownloader extends Disposable {
|
||||
|
||||
async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
|
||||
await this.cleanUpPromise;
|
||||
const location = joinPath(this.extensionsDownloadDir, this.getName(extension));
|
||||
await this.download(extension, location, operation);
|
||||
const vsixName = this.getName(extension);
|
||||
const location = joinPath(this.extensionsDownloadDir, vsixName);
|
||||
|
||||
// Download only if vsix does not exist
|
||||
if (!await this.fileService.exists(location)) {
|
||||
// Download to temporary location first only if vsix does not exist
|
||||
const tempLocation = joinPath(this.extensionsDownloadDir, `.${vsixName}`);
|
||||
if (!await this.fileService.exists(tempLocation)) {
|
||||
await this.extensionGalleryService.download(extension, tempLocation, operation);
|
||||
}
|
||||
|
||||
// Rename temp location to original
|
||||
await this.rename(tempLocation, location, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
@@ -45,9 +60,15 @@ export class ExtensionsDownloader extends Disposable {
|
||||
// noop as caching is enabled always
|
||||
}
|
||||
|
||||
private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
|
||||
if (!await this.fileService.exists(location)) {
|
||||
await this.extensionGalleryService.download(extension, location, operation);
|
||||
private async rename(from: URI, to: URI, retryUntil: number): Promise<void> {
|
||||
try {
|
||||
await rename(from.fsPath, to.fsPath);
|
||||
} catch (error) {
|
||||
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
|
||||
this.logService.info(`Failed renaming ${from} to ${to} with 'EPERM' error. Trying again...`);
|
||||
return this.rename(from, to, retryUntil);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,8 @@ import {
|
||||
INSTALL_ERROR_MALICIOUS,
|
||||
INSTALL_ERROR_INCOMPATIBLE,
|
||||
ExtensionManagementError,
|
||||
InstallOptions
|
||||
InstallOptions,
|
||||
UninstallOptions
|
||||
} from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -297,11 +298,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ }
|
||||
|
||||
try {
|
||||
await this.installDependenciesAndPackExtensions(local, existingExtension, options);
|
||||
} catch (error) {
|
||||
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
|
||||
throw error;
|
||||
if (!options.donotIncludePackAndDependencies) {
|
||||
try {
|
||||
await this.installDependenciesAndPackExtensions(local, existingExtension, options);
|
||||
} catch (error) {
|
||||
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) {
|
||||
@@ -471,7 +474,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
await Promise.all(extensionsToUninstall.map(local => this.uninstall(local)));
|
||||
}
|
||||
|
||||
async uninstall(extension: ILocalExtension): Promise<void> {
|
||||
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
|
||||
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
|
||||
const installed = await this.getInstalled(ExtensionType.User);
|
||||
const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier));
|
||||
@@ -480,7 +483,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
}
|
||||
|
||||
try {
|
||||
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed);
|
||||
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed, options);
|
||||
} catch (error) {
|
||||
throw this.joinErrors(error);
|
||||
}
|
||||
@@ -533,15 +536,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
}, new Error(''));
|
||||
}
|
||||
|
||||
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise<void> {
|
||||
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], options: UninstallOptions): Promise<void> {
|
||||
try {
|
||||
await this.preUninstallExtension(extension);
|
||||
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
|
||||
if (packedExtensions.length) {
|
||||
await this.uninstallExtensions(extension, packedExtensions, installed);
|
||||
} else {
|
||||
await this.uninstallExtensions(extension, [], installed);
|
||||
}
|
||||
const packedExtensions = options.donotIncludePack ? [] : this.getAllPackExtensionsToUninstall(extension, installed);
|
||||
await this.uninstallExtensions(extension, packedExtensions, installed, options);
|
||||
} catch (error) {
|
||||
await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
|
||||
throw error;
|
||||
@@ -549,10 +548,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
await this.postUninstallExtension(extension);
|
||||
}
|
||||
|
||||
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise<void> {
|
||||
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], options: UninstallOptions): Promise<void> {
|
||||
const extensionsToUninstall = [extension, ...otherExtensionsToUninstall];
|
||||
for (const e of extensionsToUninstall) {
|
||||
this.checkForDependents(e, extensionsToUninstall, installed, extension);
|
||||
if (!options.donotCheckDependents) {
|
||||
for (const e of extensionsToUninstall) {
|
||||
this.checkForDependents(e, extensionsToUninstall, installed, extension);
|
||||
}
|
||||
}
|
||||
await Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
|
||||
export class ExtensionUrlTrustService implements IExtensionUrlTrustService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private trustedExtensionUrlPublicKeys = new Map<string, (crypto.KeyObject | string | null)[]>();
|
||||
|
||||
constructor(
|
||||
@IProductService private readonly productService: IProductService,
|
||||
@ILogService private readonly logService: ILogService
|
||||
) { }
|
||||
|
||||
async isExtensionUrlTrusted(extensionId: string, url: string): Promise<boolean> {
|
||||
if (!this.productService.trustedExtensionUrlPublicKeys) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'There are no configured trusted keys');
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = /^(.*)#([^#]+)$/.exec(url);
|
||||
|
||||
if (!match) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri has no fragment', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const [, originalUrl, fragment] = match;
|
||||
|
||||
let keys = this.trustedExtensionUrlPublicKeys.get(extensionId);
|
||||
|
||||
if (!keys) {
|
||||
keys = this.productService.trustedExtensionUrlPublicKeys[extensionId];
|
||||
|
||||
if (!keys || keys.length === 0) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Extension doesn\'t have any trusted keys', extensionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.trustedExtensionUrlPublicKeys.set(extensionId, [...keys]);
|
||||
}
|
||||
|
||||
const fragmentBuffer = Buffer.from(decodeURIComponent(fragment), 'base64');
|
||||
|
||||
if (fragmentBuffer.length <= 6) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Uri fragment is not a signature', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const timestampBuffer = fragmentBuffer.slice(0, 6);
|
||||
const timestamp = fragmentBuffer.readUIntBE(0, 6);
|
||||
const diff = Date.now() - timestamp;
|
||||
|
||||
if (diff < 0 || diff > 3_600_000) { // 1 hour
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri has expired', url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const signatureBuffer = fragmentBuffer.slice(6);
|
||||
const verify = crypto.createVerify('SHA256');
|
||||
verify.write(timestampBuffer);
|
||||
verify.write(Buffer.from(originalUrl));
|
||||
verify.end();
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
|
||||
if (key === null) { // failed to be parsed before
|
||||
continue;
|
||||
} else if (typeof key === 'string') { // needs to be parsed
|
||||
try {
|
||||
key = crypto.createPublicKey({ key: Buffer.from(key, 'base64'), format: 'der', type: 'spki' });
|
||||
keys[i] = key;
|
||||
} catch (err) {
|
||||
this.logService.warn('ExtensionUrlTrustService#isExtensionUrlTrusted', `Failed to parse trusted extension uri public key #${i + 1} for ${extensionId}:`, err);
|
||||
keys[i] = null;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (verify.verify(key, signatureBuffer)) {
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri is valid', url);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
this.logService.trace('ExtensionUrlTrustService#isExtensionUrlTrusted', 'Signed uri could not be verified', url);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ export class ExtensionsScanner extends Disposable {
|
||||
) {
|
||||
super();
|
||||
this.systemExtensionsPath = environmentService.builtinExtensionsPath;
|
||||
this.extensionsPath = environmentService.extensionsPath!;
|
||||
this.extensionsPath = environmentService.extensionsPath;
|
||||
this.uninstalledPath = path.join(this.extensionsPath, '.obsolete');
|
||||
this.uninstalledFileLimiter = new Queue();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user