Path: blob/main/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { compareIgnoreCase } from '../../../base/common/strings.js';6import { IExtensionIdentifier, IGalleryExtension, ILocalExtension, MaliciousExtensionInfo, getTargetPlatform } from './extensionManagement.js';7import { ExtensionIdentifier, IExtension, TargetPlatform, UNDEFINED_PUBLISHER } from '../../extensions/common/extensions.js';8import { IFileService } from '../../files/common/files.js';9import { isLinux, platform } from '../../../base/common/platform.js';10import { URI } from '../../../base/common/uri.js';11import { getErrorMessage } from '../../../base/common/errors.js';12import { ILogService } from '../../log/common/log.js';13import { arch } from '../../../base/common/process.js';14import { TelemetryTrustedValue } from '../../telemetry/common/telemetryUtils.js';15import { isString } from '../../../base/common/types.js';1617export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {18if (a.uuid && b.uuid) {19return a.uuid === b.uuid;20}21if (a.id === b.id) {22return true;23}24return compareIgnoreCase(a.id, b.id) === 0;25}2627const ExtensionKeyRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)(-(.+))?$/;2829export class ExtensionKey {3031static create(extension: IExtension | IGalleryExtension): ExtensionKey {32const version = (extension as IExtension).manifest ? (extension as IExtension).manifest.version : (extension as IGalleryExtension).version;33const targetPlatform = (extension as IExtension).manifest ? (extension as IExtension).targetPlatform : (extension as IGalleryExtension).properties.targetPlatform;34return new ExtensionKey(extension.identifier, version, targetPlatform);35}3637static parse(key: string): ExtensionKey | null {38const matches = ExtensionKeyRegex.exec(key);39return matches && matches[1] && matches[2] ? new ExtensionKey({ id: matches[1] }, matches[2], matches[4] as TargetPlatform || undefined) : null;40}4142readonly id: string;4344constructor(45readonly identifier: IExtensionIdentifier,46readonly version: string,47readonly targetPlatform: TargetPlatform = TargetPlatform.UNDEFINED,48) {49this.id = identifier.id;50}5152toString(): string {53return `${this.id}-${this.version}${this.targetPlatform !== TargetPlatform.UNDEFINED ? `-${this.targetPlatform}` : ''}`;54}5556equals(o: any): boolean {57if (!(o instanceof ExtensionKey)) {58return false;59}60return areSameExtensions(this, o) && this.version === o.version && this.targetPlatform === o.targetPlatform;61}62}6364const EXTENSION_IDENTIFIER_WITH_VERSION_REGEX = /^([^.]+\..+)@((prerelease)|(\d+\.\d+\.\d+(-.*)?))$/;65export function getIdAndVersion(id: string): [string, string | undefined] {66const matches = EXTENSION_IDENTIFIER_WITH_VERSION_REGEX.exec(id);67if (matches && matches[1]) {68return [adoptToGalleryExtensionId(matches[1]), matches[2]];69}70return [adoptToGalleryExtensionId(id), undefined];71}7273export function getExtensionId(publisher: string, name: string): string {74return `${publisher}.${name}`;75}7677export function adoptToGalleryExtensionId(id: string): string {78return id.toLowerCase();79}8081export function getGalleryExtensionId(publisher: string | undefined, name: string): string {82return adoptToGalleryExtensionId(getExtensionId(publisher ?? UNDEFINED_PUBLISHER, name));83}8485export function groupByExtension<T>(extensions: T[], getExtensionIdentifier: (t: T) => IExtensionIdentifier): T[][] {86const byExtension: T[][] = [];87const findGroup = (extension: T) => {88for (const group of byExtension) {89if (group.some(e => areSameExtensions(getExtensionIdentifier(e), getExtensionIdentifier(extension)))) {90return group;91}92}93return null;94};95for (const extension of extensions) {96const group = findGroup(extension);97if (group) {98group.push(extension);99} else {100byExtension.push([extension]);101}102}103return byExtension;104}105106export function getLocalExtensionTelemetryData(extension: ILocalExtension) {107return {108id: extension.identifier.id,109name: extension.manifest.name,110galleryId: null,111publisherId: extension.publisherId,112publisherName: extension.manifest.publisher,113publisherDisplayName: extension.publisherDisplayName,114dependencies: extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length > 0115};116}117118119/* __GDPR__FRAGMENT__120"GalleryExtensionTelemetryData" : {121"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },122"name": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },123"extensionVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },124"galleryId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },125"publisherId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },126"publisherName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },127"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },128"isPreReleaseVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },129"dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },130"isSigned": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },131"${include}": [132"${GalleryExtensionTelemetryData2}"133]134}135*/136export function getGalleryExtensionTelemetryData(extension: IGalleryExtension) {137return {138id: new TelemetryTrustedValue(extension.identifier.id),139name: new TelemetryTrustedValue(extension.name),140extensionVersion: extension.version,141galleryId: extension.identifier.uuid,142publisherId: extension.publisherId,143publisherName: extension.publisher,144publisherDisplayName: extension.publisherDisplayName,145isPreReleaseVersion: extension.properties.isPreReleaseVersion,146dependencies: !!(extension.properties.dependencies && extension.properties.dependencies.length > 0),147isSigned: extension.isSigned,148...extension.telemetryData149};150}151152export const BetterMergeId = new ExtensionIdentifier('pprice.better-merge');153154export function getExtensionDependencies(installedExtensions: ReadonlyArray<IExtension>, extension: IExtension): IExtension[] {155const dependencies: IExtension[] = [];156const extensions = extension.manifest.extensionDependencies?.slice(0) ?? [];157158while (extensions.length) {159const id = extensions.shift();160161if (id && dependencies.every(e => !areSameExtensions(e.identifier, { id }))) {162const ext = installedExtensions.filter(e => areSameExtensions(e.identifier, { id }));163if (ext.length === 1) {164dependencies.push(ext[0]);165extensions.push(...ext[0].manifest.extensionDependencies?.slice(0) ?? []);166}167}168}169170return dependencies;171}172173async function isAlpineLinux(fileService: IFileService, logService: ILogService): Promise<boolean> {174if (!isLinux) {175return false;176}177let content: string | undefined;178try {179const fileContent = await fileService.readFile(URI.file('/etc/os-release'));180content = fileContent.value.toString();181} catch (error) {182try {183const fileContent = await fileService.readFile(URI.file('/usr/lib/os-release'));184content = fileContent.value.toString();185} catch (error) {186/* Ignore */187logService.debug(`Error while getting the os-release file.`, getErrorMessage(error));188}189}190return !!content && (content.match(/^ID=([^\u001b\r\n]*)/m) || [])[1] === 'alpine';191}192193export async function computeTargetPlatform(fileService: IFileService, logService: ILogService): Promise<TargetPlatform> {194const alpineLinux = await isAlpineLinux(fileService, logService);195const targetPlatform = getTargetPlatform(alpineLinux ? 'alpine' : platform, arch);196logService.debug('ComputeTargetPlatform:', targetPlatform);197return targetPlatform;198}199200export function isMalicious(identifier: IExtensionIdentifier, malicious: ReadonlyArray<MaliciousExtensionInfo>): boolean {201return findMatchingMaliciousEntry(identifier, malicious) !== undefined;202}203204export function findMatchingMaliciousEntry(identifier: IExtensionIdentifier, malicious: ReadonlyArray<MaliciousExtensionInfo>): MaliciousExtensionInfo | undefined {205return malicious.find(({ extensionOrPublisher }) => {206if (isString(extensionOrPublisher)) {207return compareIgnoreCase(identifier.id.split('.')[0], extensionOrPublisher) === 0;208}209return areSameExtensions(identifier, extensionOrPublisher);210});211}212213214