Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { compareIgnoreCase } from '../../../base/common/strings.js';
7
import { IExtensionIdentifier, IGalleryExtension, ILocalExtension, MaliciousExtensionInfo, getTargetPlatform } from './extensionManagement.js';
8
import { ExtensionIdentifier, IExtension, TargetPlatform, UNDEFINED_PUBLISHER } from '../../extensions/common/extensions.js';
9
import { IFileService } from '../../files/common/files.js';
10
import { isLinux, platform } from '../../../base/common/platform.js';
11
import { URI } from '../../../base/common/uri.js';
12
import { getErrorMessage } from '../../../base/common/errors.js';
13
import { ILogService } from '../../log/common/log.js';
14
import { arch } from '../../../base/common/process.js';
15
import { TelemetryTrustedValue } from '../../telemetry/common/telemetryUtils.js';
16
import { isString } from '../../../base/common/types.js';
17
18
export function areSameExtensions(a: IExtensionIdentifier, b: IExtensionIdentifier): boolean {
19
if (a.uuid && b.uuid) {
20
return a.uuid === b.uuid;
21
}
22
if (a.id === b.id) {
23
return true;
24
}
25
return compareIgnoreCase(a.id, b.id) === 0;
26
}
27
28
const ExtensionKeyRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)(-(.+))?$/;
29
30
export class ExtensionKey {
31
32
static create(extension: IExtension | IGalleryExtension): ExtensionKey {
33
const version = (extension as IExtension).manifest ? (extension as IExtension).manifest.version : (extension as IGalleryExtension).version;
34
const targetPlatform = (extension as IExtension).manifest ? (extension as IExtension).targetPlatform : (extension as IGalleryExtension).properties.targetPlatform;
35
return new ExtensionKey(extension.identifier, version, targetPlatform);
36
}
37
38
static parse(key: string): ExtensionKey | null {
39
const matches = ExtensionKeyRegex.exec(key);
40
return matches && matches[1] && matches[2] ? new ExtensionKey({ id: matches[1] }, matches[2], matches[4] as TargetPlatform || undefined) : null;
41
}
42
43
readonly id: string;
44
45
constructor(
46
readonly identifier: IExtensionIdentifier,
47
readonly version: string,
48
readonly targetPlatform: TargetPlatform = TargetPlatform.UNDEFINED,
49
) {
50
this.id = identifier.id;
51
}
52
53
toString(): string {
54
return `${this.id}-${this.version}${this.targetPlatform !== TargetPlatform.UNDEFINED ? `-${this.targetPlatform}` : ''}`;
55
}
56
57
equals(o: any): boolean {
58
if (!(o instanceof ExtensionKey)) {
59
return false;
60
}
61
return areSameExtensions(this, o) && this.version === o.version && this.targetPlatform === o.targetPlatform;
62
}
63
}
64
65
const EXTENSION_IDENTIFIER_WITH_VERSION_REGEX = /^([^.]+\..+)@((prerelease)|(\d+\.\d+\.\d+(-.*)?))$/;
66
export function getIdAndVersion(id: string): [string, string | undefined] {
67
const matches = EXTENSION_IDENTIFIER_WITH_VERSION_REGEX.exec(id);
68
if (matches && matches[1]) {
69
return [adoptToGalleryExtensionId(matches[1]), matches[2]];
70
}
71
return [adoptToGalleryExtensionId(id), undefined];
72
}
73
74
export function getExtensionId(publisher: string, name: string): string {
75
return `${publisher}.${name}`;
76
}
77
78
export function adoptToGalleryExtensionId(id: string): string {
79
return id.toLowerCase();
80
}
81
82
export function getGalleryExtensionId(publisher: string | undefined, name: string): string {
83
return adoptToGalleryExtensionId(getExtensionId(publisher ?? UNDEFINED_PUBLISHER, name));
84
}
85
86
export function groupByExtension<T>(extensions: T[], getExtensionIdentifier: (t: T) => IExtensionIdentifier): T[][] {
87
const byExtension: T[][] = [];
88
const findGroup = (extension: T) => {
89
for (const group of byExtension) {
90
if (group.some(e => areSameExtensions(getExtensionIdentifier(e), getExtensionIdentifier(extension)))) {
91
return group;
92
}
93
}
94
return null;
95
};
96
for (const extension of extensions) {
97
const group = findGroup(extension);
98
if (group) {
99
group.push(extension);
100
} else {
101
byExtension.push([extension]);
102
}
103
}
104
return byExtension;
105
}
106
107
export function getLocalExtensionTelemetryData(extension: ILocalExtension) {
108
return {
109
id: extension.identifier.id,
110
name: extension.manifest.name,
111
galleryId: null,
112
publisherId: extension.publisherId,
113
publisherName: extension.manifest.publisher,
114
publisherDisplayName: extension.publisherDisplayName,
115
dependencies: extension.manifest.extensionDependencies && extension.manifest.extensionDependencies.length > 0
116
};
117
}
118
119
120
/* __GDPR__FRAGMENT__
121
"GalleryExtensionTelemetryData" : {
122
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
123
"name": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
124
"extensionVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
125
"galleryId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
126
"publisherId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
127
"publisherName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
128
"publisherDisplayName": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
129
"isPreReleaseVersion": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
130
"dependencies": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
131
"isSigned": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
132
"${include}": [
133
"${GalleryExtensionTelemetryData2}"
134
]
135
}
136
*/
137
export function getGalleryExtensionTelemetryData(extension: IGalleryExtension) {
138
return {
139
id: new TelemetryTrustedValue(extension.identifier.id),
140
name: new TelemetryTrustedValue(extension.name),
141
extensionVersion: extension.version,
142
galleryId: extension.identifier.uuid,
143
publisherId: extension.publisherId,
144
publisherName: extension.publisher,
145
publisherDisplayName: extension.publisherDisplayName,
146
isPreReleaseVersion: extension.properties.isPreReleaseVersion,
147
dependencies: !!(extension.properties.dependencies && extension.properties.dependencies.length > 0),
148
isSigned: extension.isSigned,
149
...extension.telemetryData
150
};
151
}
152
153
export const BetterMergeId = new ExtensionIdentifier('pprice.better-merge');
154
155
export function getExtensionDependencies(installedExtensions: ReadonlyArray<IExtension>, extension: IExtension): IExtension[] {
156
const dependencies: IExtension[] = [];
157
const extensions = extension.manifest.extensionDependencies?.slice(0) ?? [];
158
159
while (extensions.length) {
160
const id = extensions.shift();
161
162
if (id && dependencies.every(e => !areSameExtensions(e.identifier, { id }))) {
163
const ext = installedExtensions.filter(e => areSameExtensions(e.identifier, { id }));
164
if (ext.length === 1) {
165
dependencies.push(ext[0]);
166
extensions.push(...ext[0].manifest.extensionDependencies?.slice(0) ?? []);
167
}
168
}
169
}
170
171
return dependencies;
172
}
173
174
async function isAlpineLinux(fileService: IFileService, logService: ILogService): Promise<boolean> {
175
if (!isLinux) {
176
return false;
177
}
178
let content: string | undefined;
179
try {
180
const fileContent = await fileService.readFile(URI.file('/etc/os-release'));
181
content = fileContent.value.toString();
182
} catch (error) {
183
try {
184
const fileContent = await fileService.readFile(URI.file('/usr/lib/os-release'));
185
content = fileContent.value.toString();
186
} catch (error) {
187
/* Ignore */
188
logService.debug(`Error while getting the os-release file.`, getErrorMessage(error));
189
}
190
}
191
return !!content && (content.match(/^ID=([^\u001b\r\n]*)/m) || [])[1] === 'alpine';
192
}
193
194
export async function computeTargetPlatform(fileService: IFileService, logService: ILogService): Promise<TargetPlatform> {
195
const alpineLinux = await isAlpineLinux(fileService, logService);
196
const targetPlatform = getTargetPlatform(alpineLinux ? 'alpine' : platform, arch);
197
logService.debug('ComputeTargetPlatform:', targetPlatform);
198
return targetPlatform;
199
}
200
201
export function isMalicious(identifier: IExtensionIdentifier, malicious: ReadonlyArray<MaliciousExtensionInfo>): boolean {
202
return findMatchingMaliciousEntry(identifier, malicious) !== undefined;
203
}
204
205
export function findMatchingMaliciousEntry(identifier: IExtensionIdentifier, malicious: ReadonlyArray<MaliciousExtensionInfo>): MaliciousExtensionInfo | undefined {
206
return malicious.find(({ extensionOrPublisher }) => {
207
if (isString(extensionOrPublisher)) {
208
return compareIgnoreCase(identifier.id.split('.')[0], extensionOrPublisher) === 0;
209
}
210
return areSameExtensions(identifier, extensionOrPublisher);
211
});
212
}
213
214