Path: blob/main/src/vs/platform/extensionManagement/common/extensionGalleryService.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 { distinct } from '../../../base/common/arrays.js';6import { CancellationToken } from '../../../base/common/cancellation.js';7import * as semver from '../../../base/common/semver/semver.js';8import { IStringDictionary } from '../../../base/common/collections.js';9import { CancellationError, getErrorMessage, isCancellationError } from '../../../base/common/errors.js';10import { IPager } from '../../../base/common/paging.js';11import { isWeb, platform } from '../../../base/common/platform.js';12import { arch } from '../../../base/common/process.js';13import { isBoolean, isString } from '../../../base/common/types.js';14import { URI } from '../../../base/common/uri.js';15import { IHeaders, IRequestContext, IRequestOptions, isOfflineError } from '../../../base/parts/request/common/request.js';16import { IConfigurationService } from '../../configuration/common/configuration.js';17import { IEnvironmentService } from '../../environment/common/environment.js';18import { getTargetPlatform, IExtensionGalleryService, IExtensionIdentifier, IExtensionInfo, IGalleryExtension, IGalleryExtensionAsset, IGalleryExtensionAssets, IGalleryExtensionVersion, InstallOperation, IQueryOptions, IExtensionsControlManifest, isNotWebExtensionInWebTargetPlatform, isTargetPlatformCompatible, ITranslation, SortOrder, StatisticType, toTargetPlatform, WEB_EXTENSION_TAG, IExtensionQueryOptions, IDeprecationInfo, ISearchPrefferedResults, ExtensionGalleryError, ExtensionGalleryErrorCode, IProductVersion, IAllowedExtensionsService, EXTENSION_IDENTIFIER_REGEX, SortBy, FilterType, MaliciousExtensionInfo } from './extensionManagement.js';19import { adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, getGalleryExtensionTelemetryData } from './extensionManagementUtil.js';20import { IExtensionManifest, TargetPlatform } from '../../extensions/common/extensions.js';21import { areApiProposalsCompatible, isEngineValid } from '../../extensions/common/extensionValidator.js';22import { IFileService } from '../../files/common/files.js';23import { ILogService } from '../../log/common/log.js';24import { IProductService } from '../../product/common/productService.js';25import { asJson, asTextOrError, IRequestService, isClientError, isServerError, isSuccess } from '../../request/common/request.js';26import { resolveMarketplaceHeaders } from '../../externalServices/common/marketplace.js';27import { IStorageService } from '../../storage/common/storage.js';28import { ITelemetryService } from '../../telemetry/common/telemetry.js';29import { StopWatch } from '../../../base/common/stopwatch.js';30import { format2 } from '../../../base/common/strings.js';31import { IAssignmentService } from '../../assignment/common/assignment.js';32import { ExtensionGalleryResourceType, Flag, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService, ExtensionGalleryManifestStatus } from './extensionGalleryManifest.js';33import { TelemetryTrustedValue } from '../../telemetry/common/telemetryUtils.js';3435const CURRENT_TARGET_PLATFORM = isWeb ? TargetPlatform.WEB : getTargetPlatform(platform, arch);36const SEARCH_ACTIVITY_HEADER_NAME = 'X-Market-Search-Activity-Id';37const ACTIVITY_HEADER_NAME = 'Activityid';38const SERVER_HEADER_NAME = 'Server';39const END_END_ID_HEADER_NAME = 'X-Vss-E2eid';40const REQUEST_TIME_OUT = 10_000;4142interface IRawGalleryExtensionFile {43readonly assetType: string;44readonly source: string;45}4647interface IRawGalleryExtensionProperty {48readonly key: string;49readonly value: string;50}5152export interface IRawGalleryExtensionVersion {53readonly version: string;54readonly lastUpdated: string;55readonly assetUri: string;56readonly fallbackAssetUri: string;57readonly files: IRawGalleryExtensionFile[];58readonly properties?: IRawGalleryExtensionProperty[];59readonly targetPlatform?: string;60}6162interface IRawGalleryExtensionStatistics {63readonly statisticName: string;64readonly value: number;65}6667interface IRawGalleryExtensionPublisher {68readonly displayName: string;69readonly publisherId: string;70readonly publisherName: string;71readonly domain?: string | null;72readonly isDomainVerified?: boolean;73readonly linkType?: string;74}7576interface IRawGalleryExtension {77readonly extensionId: string;78readonly extensionName: string;79readonly displayName: string;80readonly shortDescription?: string;81readonly publisher: IRawGalleryExtensionPublisher;82readonly versions: IRawGalleryExtensionVersion[];83readonly statistics: IRawGalleryExtensionStatistics[];84readonly tags: string[] | undefined;85readonly releaseDate: string;86readonly publishedDate: string;87readonly lastUpdated: string;88readonly categories: string[] | undefined;89readonly flags: string;90readonly linkType?: string;91readonly ratingLinkType?: string;92}9394interface IRawGalleryExtensionsResult {95readonly galleryExtensions: IRawGalleryExtension[];96readonly total: number;97readonly context?: IStringDictionary<string>;98}99100interface IRawGalleryQueryResult {101readonly results: {102readonly extensions: IRawGalleryExtension[];103readonly resultMetadata: {104readonly metadataType: string;105readonly metadataItems: {106readonly name: string;107readonly count: number;108}[];109}[];110}[];111}112113const AssetType = {114Icon: 'Microsoft.VisualStudio.Services.Icons.Default',115Details: 'Microsoft.VisualStudio.Services.Content.Details',116Changelog: 'Microsoft.VisualStudio.Services.Content.Changelog',117Manifest: 'Microsoft.VisualStudio.Code.Manifest',118VSIX: 'Microsoft.VisualStudio.Services.VSIXPackage',119License: 'Microsoft.VisualStudio.Services.Content.License',120Repository: 'Microsoft.VisualStudio.Services.Links.Source',121Signature: 'Microsoft.VisualStudio.Services.VsixSignature'122};123124const PropertyType = {125Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies',126ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack',127Engine: 'Microsoft.VisualStudio.Code.Engine',128PreRelease: 'Microsoft.VisualStudio.Code.PreRelease',129EnabledApiProposals: 'Microsoft.VisualStudio.Code.EnabledApiProposals',130LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages',131WebExtension: 'Microsoft.VisualStudio.Code.WebExtension',132SponsorLink: 'Microsoft.VisualStudio.Code.SponsorLink',133SupportLink: 'Microsoft.VisualStudio.Services.Links.Support',134ExecutesCode: 'Microsoft.VisualStudio.Code.ExecutesCode',135Private: 'PrivateMarketplace',136};137138interface ICriterium {139readonly filterType: FilterType;140readonly value?: string;141}142143const DefaultPageSize = 10;144145interface IQueryState {146readonly pageNumber: number;147readonly pageSize: number;148readonly sortBy: SortBy;149readonly sortOrder: SortOrder;150readonly flags: Flag[];151readonly criteria: ICriterium[];152readonly assetTypes: string[];153readonly source?: string;154}155156const DefaultQueryState: IQueryState = {157pageNumber: 1,158pageSize: DefaultPageSize,159sortBy: SortBy.NoneOrRelevance,160sortOrder: SortOrder.Default,161flags: [],162criteria: [],163assetTypes: []164};165166type GalleryServiceQueryClassification = {167owner: 'sandy081';168comment: 'Information about Marketplace query and its response';169readonly filterTypes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Filter types used in the query.' };170readonly flags: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flags passed in the query.' };171readonly sortBy: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'sorted by option passed in the query' };172readonly sortOrder: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'sort order option passed in the query' };173readonly pageNumber: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'requested page number in the query' };174readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true; comment: 'amount of time taken by the query request' };175readonly success: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'whether the query request is success or not' };176readonly requestBodySize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'size of the request body' };177readonly responseBodySize?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'size of the response body' };178readonly statusCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'status code of the response' };179readonly errorCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code of the response' };180readonly count?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'total number of extensions matching the query' };181readonly source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'source that requested this query, eg., recommendations, viewlet' };182readonly searchTextLength?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'length of the search text in the query' };183readonly server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'server that handled the query' };184readonly endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'end to end operation id' };185readonly activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'activity id' };186};187188type QueryTelemetryData = {189readonly filterTypes: string[];190readonly flags: string[];191readonly sortBy: string;192readonly sortOrder: string;193readonly pageNumber: string;194readonly source?: string;195readonly searchTextLength?: number;196};197198type GalleryServiceQueryEvent = QueryTelemetryData & {199readonly duration: number;200readonly success: boolean;201readonly requestBodySize: string;202readonly responseBodySize?: string;203readonly statusCode?: string;204readonly errorCode?: string;205readonly count?: string;206readonly server?: TelemetryTrustedValue<string>;207readonly endToEndId?: TelemetryTrustedValue<string>;208readonly activityId?: TelemetryTrustedValue<string>;209};210211type GalleryServiceAdditionalQueryClassification = {212owner: 'sandy081';213comment: 'Response information about the additional query to the Marketplace for fetching all versions to get release version';214readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true; comment: 'Amount of time taken by the additional query' };215readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Total number of extensions returned by this additional query' };216};217218type GalleryServiceAdditionalQueryEvent = {219readonly duration: number;220readonly count: number;221};222223type ExtensionsCriteria = {224readonly productVersion: IProductVersion;225readonly targetPlatform: TargetPlatform;226readonly compatible: boolean;227readonly includePreRelease: boolean | (IExtensionIdentifier & { includePreRelease: boolean })[];228readonly versions?: (IExtensionIdentifier & { version: string })[];229readonly isQueryForReleaseVersionFromPreReleaseVersion?: boolean;230};231232const enum VersionKind {233Release,234Prerelease,235Latest236}237238type ExtensionVersionCriteria = {239readonly productVersion: IProductVersion;240readonly targetPlatform: TargetPlatform;241readonly compatible: boolean;242readonly version: VersionKind | string;243};244245class Query {246247constructor(private state = DefaultQueryState) { }248249get pageNumber(): number { return this.state.pageNumber; }250get pageSize(): number { return this.state.pageSize; }251get sortBy(): SortBy { return this.state.sortBy; }252get sortOrder(): number { return this.state.sortOrder; }253get flags(): Flag[] { return this.state.flags; }254get criteria(): ICriterium[] { return this.state.criteria; }255get assetTypes(): string[] { return this.state.assetTypes; }256get source(): string | undefined { return this.state.source; }257get searchText(): string {258const criterium = this.state.criteria.filter(criterium => criterium.filterType === FilterType.SearchText)[0];259return criterium && criterium.value ? criterium.value : '';260}261262263withPage(pageNumber: number, pageSize: number = this.state.pageSize): Query {264return new Query({ ...this.state, pageNumber, pageSize });265}266267withFilter(filterType: FilterType, ...values: string[]): Query {268const criteria = [269...this.state.criteria,270...values.length ? values.map(value => ({ filterType, value })) : [{ filterType }]271];272273return new Query({ ...this.state, criteria });274}275276withSortBy(sortBy: SortBy): Query {277return new Query({ ...this.state, sortBy });278}279280withSortOrder(sortOrder: SortOrder): Query {281return new Query({ ...this.state, sortOrder });282}283284withFlags(...flags: Flag[]): Query {285return new Query({ ...this.state, flags: distinct(flags) });286}287288withAssetTypes(...assetTypes: string[]): Query {289return new Query({ ...this.state, assetTypes });290}291292withSource(source: string): Query {293return new Query({ ...this.state, source });294}295}296297function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string): number {298const result = (statistics || []).filter(s => s.statisticName === name)[0];299return result ? result.value : 0;300}301302function getCoreTranslationAssets(version: IRawGalleryExtensionVersion): [string, IGalleryExtensionAsset][] {303const coreTranslationAssetPrefix = 'Microsoft.VisualStudio.Code.Translation.';304const result = version.files.filter(f => f.assetType.indexOf(coreTranslationAssetPrefix) === 0);305return result.reduce<[string, IGalleryExtensionAsset][]>((result, file) => {306const asset = getVersionAsset(version, file.assetType);307if (asset) {308result.push([file.assetType.substring(coreTranslationAssetPrefix.length), asset]);309}310return result;311}, []);312}313314function getRepositoryAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset | null {315if (version.properties) {316const results = version.properties.filter(p => p.key === AssetType.Repository);317const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\\w.]+))(:(//)?)([\\w.@:/\\-~]+)(.git)(/)?');318319const uri = results.filter(r => gitRegExp.test(r.value))[0];320return uri ? { uri: uri.value, fallbackUri: uri.value } : null;321}322return getVersionAsset(version, AssetType.Repository);323}324325function getDownloadAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset {326return {327// always use fallbackAssetUri for download asset to hit the Marketplace API so that downloads are counted328uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true${version.targetPlatform ? `&targetPlatform=${version.targetPlatform}` : ''}`,329fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`330};331}332333function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset | null {334const result = version.files.filter(f => f.assetType === type)[0];335return result ? {336uri: `${version.assetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`,337fallbackUri: `${version.fallbackAssetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`338} : null;339}340341function getExtensions(version: IRawGalleryExtensionVersion, property: string): string[] {342const values = version.properties ? version.properties.filter(p => p.key === property) : [];343const value = values.length > 0 && values[0].value;344return value ? value.split(',').map(v => adoptToGalleryExtensionId(v)) : [];345}346347function getEngine(version: IRawGalleryExtensionVersion): string {348const values = version.properties ? version.properties.filter(p => p.key === PropertyType.Engine) : [];349return (values.length > 0 && values[0].value) || '';350}351352function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean {353const values = version.properties ? version.properties.filter(p => p.key === PropertyType.PreRelease) : [];354return values.length > 0 && values[0].value === 'true';355}356357function hasPreReleaseForExtension(id: string, productService: IProductService): boolean | undefined {358return productService.extensionProperties?.[id.toLowerCase()]?.hasPrereleaseVersion;359}360361function getExcludeVersionRangeForExtension(id: string, productService: IProductService): string | undefined {362return productService.extensionProperties?.[id.toLowerCase()]?.excludeVersionRange;363}364365function isPrivateExtension(version: IRawGalleryExtensionVersion): boolean {366const values = version.properties ? version.properties.filter(p => p.key === PropertyType.Private) : [];367return values.length > 0 && values[0].value === 'true';368}369370function executesCode(version: IRawGalleryExtensionVersion): boolean | undefined {371const values = version.properties ? version.properties.filter(p => p.key === PropertyType.ExecutesCode) : [];372return values.length > 0 ? values[0].value === 'true' : undefined;373}374375function getEnabledApiProposals(version: IRawGalleryExtensionVersion): string[] {376const values = version.properties ? version.properties.filter(p => p.key === PropertyType.EnabledApiProposals) : [];377const value = (values.length > 0 && values[0].value) || '';378return value ? value.split(',') : [];379}380381function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] {382const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : [];383const value = (values.length > 0 && values[0].value) || '';384return value ? value.split(',') : [];385}386387function getSponsorLink(version: IRawGalleryExtensionVersion): string | undefined {388return version.properties?.find(p => p.key === PropertyType.SponsorLink)?.value;389}390391function getSupportLink(version: IRawGalleryExtensionVersion): string | undefined {392return version.properties?.find(p => p.key === PropertyType.SupportLink)?.value;393}394395function getIsPreview(flags: string): boolean {396return flags.indexOf('preview') !== -1;397}398399function getTargetPlatformForExtensionVersion(version: IRawGalleryExtensionVersion): TargetPlatform {400return version.targetPlatform ? toTargetPlatform(version.targetPlatform) : TargetPlatform.UNDEFINED;401}402403function getAllTargetPlatforms(rawGalleryExtension: IRawGalleryExtension): TargetPlatform[] {404const allTargetPlatforms = distinct(rawGalleryExtension.versions.map(getTargetPlatformForExtensionVersion));405406// Is a web extension only if it has WEB_EXTENSION_TAG407const isWebExtension = !!rawGalleryExtension.tags?.includes(WEB_EXTENSION_TAG);408409// Include Web Target Platform only if it is a web extension410const webTargetPlatformIndex = allTargetPlatforms.indexOf(TargetPlatform.WEB);411if (isWebExtension) {412if (webTargetPlatformIndex === -1) {413// Web extension but does not has web target platform -> add it414allTargetPlatforms.push(TargetPlatform.WEB);415}416} else {417if (webTargetPlatformIndex !== -1) {418// Not a web extension but has web target platform -> remove it419allTargetPlatforms.splice(webTargetPlatformIndex, 1);420}421}422423return allTargetPlatforms;424}425426export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], preferredTargetPlatform: TargetPlatform): IRawGalleryExtensionVersion[] {427/* It is expected that versions from Marketplace are sorted by version. So we are just sorting by preferred targetPlatform */428for (let index = 0; index < versions.length; index++) {429const version = versions[index];430if (version.version === versions[index - 1]?.version) {431let insertionIndex = index;432const versionTargetPlatform = getTargetPlatformForExtensionVersion(version);433/* put it at the beginning */434if (versionTargetPlatform === preferredTargetPlatform) {435while (insertionIndex > 0 && versions[insertionIndex - 1].version === version.version) { insertionIndex--; }436}437if (insertionIndex !== index) {438versions.splice(index, 1);439versions.splice(insertionIndex, 0, version);440}441}442}443return versions;444}445446function setTelemetry(extension: IGalleryExtension, index: number, querySource?: string): void {447/* __GDPR__FRAGMENT__448"GalleryExtensionTelemetryData2" : {449"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },450"querySource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },451"queryActivityId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }452}453*/454extension.telemetryData = { index, querySource, queryActivityId: extension.queryContext?.[SEARCH_ACTIVITY_HEADER_NAME] };455}456457function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], extensionGalleryManifest: IExtensionGalleryManifest, productService: IProductService, queryContext?: IStringDictionary<any>): IGalleryExtension {458const latestVersion = galleryExtension.versions[0];459const assets: IGalleryExtensionAssets = {460manifest: getVersionAsset(version, AssetType.Manifest),461readme: getVersionAsset(version, AssetType.Details),462changelog: getVersionAsset(version, AssetType.Changelog),463license: getVersionAsset(version, AssetType.License),464repository: getRepositoryAsset(version),465download: getDownloadAsset(version),466icon: getVersionAsset(version, AssetType.Icon),467signature: getVersionAsset(version, AssetType.Signature),468coreTranslations: getCoreTranslationAssets(version)469};470471const detailsViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.linkType ?? ExtensionGalleryResourceType.ExtensionDetailsViewUri);472const publisherViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.publisher.linkType ?? ExtensionGalleryResourceType.PublisherViewUri);473const ratingViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.ratingLinkType ?? ExtensionGalleryResourceType.ExtensionRatingViewUri);474const id = getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName);475476return {477type: 'gallery',478identifier: {479id,480uuid: galleryExtension.extensionId481},482name: galleryExtension.extensionName,483version: version.version,484displayName: galleryExtension.displayName,485publisherId: galleryExtension.publisher.publisherId,486publisher: galleryExtension.publisher.publisherName,487publisherDisplayName: galleryExtension.publisher.displayName,488publisherDomain: galleryExtension.publisher.domain ? { link: galleryExtension.publisher.domain, verified: !!galleryExtension.publisher.isDomainVerified } : undefined,489publisherSponsorLink: getSponsorLink(latestVersion),490description: galleryExtension.shortDescription ?? '',491installCount: getStatistic(galleryExtension.statistics, 'install'),492rating: getStatistic(galleryExtension.statistics, 'averagerating'),493ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'),494categories: galleryExtension.categories || [],495tags: galleryExtension.tags || [],496releaseDate: Date.parse(galleryExtension.releaseDate),497lastUpdated: Date.parse(galleryExtension.lastUpdated),498allTargetPlatforms,499assets,500properties: {501dependencies: getExtensions(version, PropertyType.Dependency),502extensionPack: getExtensions(version, PropertyType.ExtensionPack),503engine: getEngine(version),504enabledApiProposals: getEnabledApiProposals(version),505localizedLanguages: getLocalizedLanguages(version),506targetPlatform: getTargetPlatformForExtensionVersion(version),507isPreReleaseVersion: isPreReleaseVersion(version),508executesCode: executesCode(version)509},510hasPreReleaseVersion: hasPreReleaseForExtension(id, productService) ?? isPreReleaseVersion(latestVersion),511hasReleaseVersion: true,512private: isPrivateExtension(latestVersion),513preview: getIsPreview(galleryExtension.flags),514isSigned: !!assets.signature,515queryContext,516supportLink: getSupportLink(latestVersion),517detailsLink: detailsViewUri ? format2(detailsViewUri, { publisher: galleryExtension.publisher.publisherName, name: galleryExtension.extensionName }) : undefined,518publisherLink: publisherViewUri ? format2(publisherViewUri, { publisher: galleryExtension.publisher.publisherName }) : undefined,519ratingLink: ratingViewUri ? format2(ratingViewUri, { publisher: galleryExtension.publisher.publisherName, name: galleryExtension.extensionName }) : undefined,520};521}522523interface IRawExtensionsControlManifest {524malicious: string[];525learnMoreLinks?: IStringDictionary<string>;526migrateToPreRelease?: IStringDictionary<{527id: string;528displayName: string;529migrateStorage?: boolean;530engine?: string;531}>;532deprecated?: IStringDictionary<boolean | {533disallowInstall?: boolean;534extension?: {535id: string;536displayName: string;537};538settings?: string[];539additionalInfo?: string;540}>;541search?: ISearchPrefferedResults[];542autoUpdate?: IStringDictionary<string>;543}544545export abstract class AbstractExtensionGalleryService implements IExtensionGalleryService {546547declare readonly _serviceBrand: undefined;548549private readonly extensionsControlUrl: string | undefined;550private readonly unpkgResourceApi: string | undefined;551552private readonly commonHeadersPromise: Promise<IHeaders>;553private readonly extensionsEnabledWithApiProposalVersion: string[];554555constructor(556storageService: IStorageService | undefined,557private readonly assignmentService: IAssignmentService | undefined,558@IRequestService private readonly requestService: IRequestService,559@ILogService private readonly logService: ILogService,560@IEnvironmentService private readonly environmentService: IEnvironmentService,561@ITelemetryService private readonly telemetryService: ITelemetryService,562@IFileService private readonly fileService: IFileService,563@IProductService private readonly productService: IProductService,564@IConfigurationService private readonly configurationService: IConfigurationService,565@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,566@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,567) {568this.extensionsControlUrl = productService.extensionsGallery?.controlUrl;569this.unpkgResourceApi = productService.extensionsGallery?.extensionUrlTemplate;570this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? [];571this.commonHeadersPromise = resolveMarketplaceHeaders(572productService.version,573productService,574this.environmentService,575this.configurationService,576this.fileService,577storageService,578this.telemetryService);579}580581isEnabled(): boolean {582return this.extensionGalleryManifestService.extensionGalleryManifestStatus === ExtensionGalleryManifestStatus.Available;583}584585getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, token: CancellationToken): Promise<IGalleryExtension[]>;586getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, token: CancellationToken): Promise<IGalleryExtension[]>;587async getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, arg1: any, arg2?: any): Promise<IGalleryExtension[]> {588const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();589if (!extensionGalleryManifest) {590throw new Error('No extension gallery service configured.');591}592593const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions;594const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken;595596const resourceApi = await this.getResourceApi(extensionGalleryManifest);597const result = resourceApi598? await this.getExtensionsUsingResourceApi(extensionInfos, options, resourceApi, extensionGalleryManifest, token)599: await this.getExtensionsUsingQueryApi(extensionInfos, options, extensionGalleryManifest, token);600601const uuids = result.map(r => r.identifier.uuid);602const extensionInfosByName: IExtensionInfo[] = [];603for (const e of extensionInfos) {604if (e.uuid && !uuids.includes(e.uuid)) {605extensionInfosByName.push({ ...e, uuid: undefined });606}607}608609if (extensionInfosByName.length) {610// report telemetry data for additional query611this.telemetryService.publicLog2<612{ count: number },613{614owner: 'sandy081';615comment: 'Report the query to the Marketplace for fetching extensions by name';616readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to fetch' };617}>('galleryService:additionalQueryByName', {618count: extensionInfosByName.length619});620621const extensions = await this.getExtensionsUsingQueryApi(extensionInfosByName, options, extensionGalleryManifest, token);622result.push(...extensions);623}624625return result;626}627628private async getResourceApi(extensionGalleryManifest: IExtensionGalleryManifest): Promise<{ uri: string; fallback?: string } | undefined> {629const latestVersionResource = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionLatestVersionUri);630if (latestVersionResource) {631return {632uri: latestVersionResource,633fallback: this.unpkgResourceApi634};635}636return undefined;637}638639private async getExtensionsUsingQueryApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {640const names: string[] = [],641ids: string[] = [],642includePreRelease: (IExtensionIdentifier & { includePreRelease: boolean })[] = [],643versions: (IExtensionIdentifier & { version: string })[] = [];644let isQueryForReleaseVersionFromPreReleaseVersion = true;645646for (const extensionInfo of extensionInfos) {647if (extensionInfo.uuid) {648ids.push(extensionInfo.uuid);649} else {650names.push(extensionInfo.id);651}652if (extensionInfo.version) {653versions.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, version: extensionInfo.version });654} else {655includePreRelease.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, includePreRelease: !!extensionInfo.preRelease });656}657isQueryForReleaseVersionFromPreReleaseVersion = isQueryForReleaseVersionFromPreReleaseVersion && (!!extensionInfo.hasPreRelease && !extensionInfo.preRelease);658}659660if (!ids.length && !names.length) {661return [];662}663664let query = new Query().withPage(1, extensionInfos.length);665if (ids.length) {666query = query.withFilter(FilterType.ExtensionId, ...ids);667}668if (names.length) {669query = query.withFilter(FilterType.ExtensionName, ...names);670}671if (options.queryAllVersions) {672query = query.withFlags(...query.flags, Flag.IncludeVersions);673}674if (options.source) {675query = query.withSource(options.source);676}677678const { extensions } = await this.queryGalleryExtensions(679query,680{681targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM,682includePreRelease,683versions,684compatible: !!options.compatible,685productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date },686isQueryForReleaseVersionFromPreReleaseVersion687},688extensionGalleryManifest,689token);690691if (options.source) {692extensions.forEach((e, index) => setTelemetry(e, index, options.source));693}694695return extensions;696}697698private async getExtensionsUsingResourceApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {699700const result: IGalleryExtension[] = [];701const toQuery: IExtensionInfo[] = [];702const toFetchLatest: IExtensionInfo[] = [];703704for (const extensionInfo of extensionInfos) {705if (!EXTENSION_IDENTIFIER_REGEX.test(extensionInfo.id)) {706continue;707}708if (extensionInfo.version) {709toQuery.push(extensionInfo);710} else {711toFetchLatest.push(extensionInfo);712}713}714715await Promise.all(toFetchLatest.map(async extensionInfo => {716let galleryExtension: IGalleryExtension | null | 'NOT_FOUND';717try {718galleryExtension = await this.getLatestGalleryExtension(extensionInfo, options, resourceApi, extensionGalleryManifest, token);719if (galleryExtension === 'NOT_FOUND') {720if (extensionInfo.uuid) {721// Fallback to query if extension with UUID is not found. Probably extension is renamed.722toQuery.push(extensionInfo);723}724return;725}726if (galleryExtension) {727result.push(galleryExtension);728}729} catch (error) {730if (error instanceof ExtensionGalleryError) {731switch (error.code) {732case ExtensionGalleryErrorCode.Offline:733case ExtensionGalleryErrorCode.Cancelled:734case ExtensionGalleryErrorCode.Timeout:735throw error;736}737}738739// fallback to query740this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id}.`, getErrorMessage(error));741this.telemetryService.publicLog2<742{743extension: string;744preRelease: boolean;745compatible: boolean;746errorCode: string;747},748{749owner: 'sandy081';750comment: 'Report the fallback to the Marketplace query for fetching extensions';751extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };752preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' };753compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' };754errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code' };755}>('galleryService:fallbacktoquery', {756extension: extensionInfo.id,757preRelease: !!extensionInfo.preRelease,758compatible: !!options.compatible,759errorCode: error instanceof ExtensionGalleryError ? error.code : 'Unknown'760});761toQuery.push(extensionInfo);762}763764}));765766if (toQuery.length) {767const extensions = await this.getExtensionsUsingQueryApi(toQuery, options, extensionGalleryManifest, token);768result.push(...extensions);769}770771return result;772}773774private async getLatestGalleryExtension(extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension | null | 'NOT_FOUND'> {775const rawGalleryExtension = await this.getLatestRawGalleryExtensionWithFallback(extensionInfo, resourceApi, token);776777if (!rawGalleryExtension) {778return 'NOT_FOUND';779}780781const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);782const rawGalleryExtensionVersion = await this.getRawGalleryExtensionVersion(783rawGalleryExtension,784{785targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM,786compatible: !!options.compatible,787productVersion: options.productVersion ?? {788version: this.productService.version,789date: this.productService.date790},791version: extensionInfo.preRelease ? VersionKind.Latest : VersionKind.Release792}, allTargetPlatforms);793794if (rawGalleryExtensionVersion) {795return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService);796}797798return null;799}800801async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<IGalleryExtension | null> {802if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) {803return null;804}805if (await this.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {806return extension;807}808if (this.allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) !== true) {809return null;810}811const result = await this.getExtensions([{812...extension.identifier,813preRelease: includePreRelease,814hasPreRelease: extension.hasPreReleaseVersion,815}], {816compatible: true,817productVersion,818queryAllVersions: true,819targetPlatform,820}, CancellationToken.None);821822return result[0] ?? null;823}824825async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<boolean> {826return this.isValidVersion(827{828id: extension.identifier.id,829version: extension.version,830isPreReleaseVersion: extension.properties.isPreReleaseVersion,831targetPlatform: extension.properties.targetPlatform,832manifestAsset: extension.assets.manifest,833engine: extension.properties.engine,834enabledApiProposals: extension.properties.enabledApiProposals835},836{837targetPlatform,838compatible: true,839productVersion,840version: includePreRelease ? VersionKind.Latest : VersionKind.Release841},842extension.publisherDisplayName,843extension.allTargetPlatforms844);845}846847private async isValidVersion(848extension: { id: string; version: string; isPreReleaseVersion: boolean; targetPlatform: TargetPlatform; manifestAsset: IGalleryExtensionAsset | null; engine: string | undefined; enabledApiProposals: string[] | undefined },849{ targetPlatform, compatible, productVersion, version }: Omit<ExtensionVersionCriteria, 'targetPlatform'> & { targetPlatform: TargetPlatform | undefined },850publisherDisplayName: string,851allTargetPlatforms: TargetPlatform[]852): Promise<boolean> {853854const hasPreRelease = hasPreReleaseForExtension(extension.id, this.productService);855const excludeVersionRange = getExcludeVersionRangeForExtension(extension.id, this.productService);856857if (extension.isPreReleaseVersion && hasPreRelease === false /* Skip if hasPreRelease is not defined for this extension */) {858return false;859}860861if (excludeVersionRange && semver.satisfies(extension.version, excludeVersionRange)) {862return false;863}864865// Specific version866if (isString(version)) {867if (extension.version !== version) {868return false;869}870}871872// Prerelease or release version kind873else if (version === VersionKind.Release || version === VersionKind.Prerelease) {874if (extension.isPreReleaseVersion !== (version === VersionKind.Prerelease)) {875return false;876}877}878879if (targetPlatform && !isTargetPlatformCompatible(extension.targetPlatform, allTargetPlatforms, targetPlatform)) {880return false;881}882883if (compatible) {884if (this.allowedExtensionsService.isAllowed({ id: extension.id, publisherDisplayName, version: extension.version, prerelease: extension.isPreReleaseVersion, targetPlatform: extension.targetPlatform }) !== true) {885return false;886}887888if (!this.areApiProposalsCompatible(extension.id, extension.enabledApiProposals)) {889return false;890}891892if (!(await this.isEngineValid(extension.id, extension.version, extension.engine, extension.manifestAsset, productVersion))) {893return false;894}895}896897return true;898}899900private areApiProposalsCompatible(extensionId: string, enabledApiProposals: string[] | undefined): boolean {901if (!enabledApiProposals) {902return true;903}904if (!this.extensionsEnabledWithApiProposalVersion.includes(extensionId.toLowerCase())) {905return true;906}907return areApiProposalsCompatible(enabledApiProposals);908}909910private async isEngineValid(extensionId: string, version: string, engine: string | undefined, manifestAsset: IGalleryExtensionAsset | null, productVersion: IProductVersion): Promise<boolean> {911if (!engine) {912if (!manifestAsset) {913this.logService.error(`Missing engine and manifest asset for the extension ${extensionId} with version ${version}`);914return false;915}916try {917type GalleryServiceEngineFallbackClassification = {918owner: 'sandy081';919comment: 'Fallback request when engine is not found in properties of an extension version';920extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };921extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };922};923type GalleryServiceEngineFallbackEvent = {924extension: string;925extensionVersion: string;926};927this.telemetryService.publicLog2<GalleryServiceEngineFallbackEvent, GalleryServiceEngineFallbackClassification>('galleryService:engineFallback', { extension: extensionId, extensionVersion: version });928929const headers = { 'Accept-Encoding': 'gzip' };930const context = await this.getAsset(extensionId, manifestAsset, AssetType.Manifest, version, { headers });931const manifest = await asJson<IExtensionManifest>(context);932if (!manifest) {933this.logService.error(`Manifest was not found for the extension ${extensionId} with version ${version}`);934return false;935}936engine = manifest.engines.vscode;937} catch (error) {938this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));939return false;940}941}942943return isEngineValid(engine, productVersion.version, productVersion.date);944}945946async query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>> {947const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();948949if (!extensionGalleryManifest) {950throw new Error('No extension gallery service configured.');951}952953let text = options.text || '';954const pageSize = options.pageSize ?? 50;955956let query = new Query()957.withPage(1, pageSize);958959if (text) {960// Use category filter instead of "category:themes"961text = text.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => {962query = query.withFilter(FilterType.Category, category || quotedCategory);963return '';964});965966// Use tag filter instead of "tag:debuggers"967text = text.replace(/\btag:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedTag, tag) => {968query = query.withFilter(FilterType.Tag, tag || quotedTag);969return '';970});971972// Use featured filter973text = text.replace(/\bfeatured(\s+|\b|$)/g, () => {974query = query.withFilter(FilterType.Featured);975return '';976});977978text = text.trim();979980if (text) {981text = text.length < 200 ? text : text.substring(0, 200);982query = query.withFilter(FilterType.SearchText, text);983}984985if (extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === SortBy.NoneOrRelevance)) {986query = query.withSortBy(SortBy.NoneOrRelevance);987}988} else {989if (extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === SortBy.InstallCount)) {990query = query.withSortBy(SortBy.InstallCount);991}992}993994if (options.sortBy && extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === options.sortBy)) {995query = query.withSortBy(options.sortBy);996}997998if (typeof options.sortOrder === 'number') {999query = query.withSortOrder(options.sortOrder);1000}10011002if (options.source) {1003query = query.withSource(options.source);1004}10051006const runQuery = async (query: Query, token: CancellationToken) => {1007const { extensions, total } = await this.queryGalleryExtensions(query, { targetPlatform: CURRENT_TARGET_PLATFORM, compatible: false, includePreRelease: !!options.includePreRelease, productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date } }, extensionGalleryManifest, token);1008extensions.forEach((e, index) => setTelemetry(e, ((query.pageNumber - 1) * query.pageSize) + index, options.source));1009return { extensions, total };1010};1011const { extensions, total } = await runQuery(query, token);1012const getPage = async (pageIndex: number, ct: CancellationToken) => {1013if (ct.isCancellationRequested) {1014throw new CancellationError();1015}1016const { extensions } = await runQuery(query.withPage(pageIndex + 1), ct);1017return extensions;1018};10191020return { firstPage: extensions, total, pageSize: query.pageSize, getPage };1021}10221023private async queryGalleryExtensions(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> {1024if (1025this.productService.quality !== 'stable'1026&& (await this.assignmentService?.getTreatment<boolean>('useLatestPrereleaseAndStableVersionFlag'))1027) {1028return this.queryGalleryExtensionsUsingIncludeLatestPrereleaseAndStableVersionFlag(query, criteria, extensionGalleryManifest, token);1029}10301031return this.queryGalleryExtensionsWithAllVersionsAsFallback(query, criteria, extensionGalleryManifest, token);1032}10331034private async queryGalleryExtensionsWithAllVersionsAsFallback(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> {1035const flags = query.flags;10361037/**1038* If both version flags (IncludeLatestVersionOnly and IncludeVersions) are included, then only include latest versions (IncludeLatestVersionOnly) flag.1039*/1040if (query.flags.includes(Flag.IncludeLatestVersionOnly) && query.flags.includes(Flag.IncludeVersions)) {1041query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeVersions));1042}10431044/**1045* If version flags (IncludeLatestVersionOnly and IncludeVersions) are not included, default is to query for latest versions (IncludeLatestVersionOnly).1046*/1047if (!query.flags.includes(Flag.IncludeLatestVersionOnly) && !query.flags.includes(Flag.IncludeVersions)) {1048query = query.withFlags(...query.flags, Flag.IncludeLatestVersionOnly);1049}10501051/**1052* If versions criteria exist or every requested extension is for release version and has a pre-release version, then remove latest flags and add all versions flag.1053*/1054if (criteria.versions?.length || criteria.isQueryForReleaseVersionFromPreReleaseVersion) {1055query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeVersions);1056}10571058/**1059* Add necessary extension flags1060*/1061query = query.withFlags(...query.flags, Flag.IncludeAssetUri, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeStatistics, Flag.IncludeVersionProperties);1062const { galleryExtensions: rawGalleryExtensions, total, context } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, token);10631064const hasAllVersions: boolean = !query.flags.includes(Flag.IncludeLatestVersionOnly);1065if (hasAllVersions) {1066const extensions: IGalleryExtension[] = [];1067for (const rawGalleryExtension of rawGalleryExtensions) {1068const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);1069const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };1070const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease;1071const rawGalleryExtensionVersion = await this.getRawGalleryExtensionVersion(1072rawGalleryExtension,1073{1074compatible: criteria.compatible,1075targetPlatform: criteria.targetPlatform,1076productVersion: criteria.productVersion,1077version: criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version1078?? (includePreRelease ? VersionKind.Latest : VersionKind.Release)1079},1080allTargetPlatforms1081);1082if (rawGalleryExtensionVersion) {1083extensions.push(toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context));1084}1085}1086return { extensions, total };1087}10881089const result: [number, IGalleryExtension][] = [];1090const needAllVersions = new Map<string, number>();1091for (let index = 0; index < rawGalleryExtensions.length; index++) {1092const rawGalleryExtension = rawGalleryExtensions[index];1093const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };1094const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease;1095const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);1096if (criteria.compatible) {1097// Skip looking for all versions if requested for a web-compatible extension and it is not a web extension.1098if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {1099continue;1100}1101// Skip looking for all versions if the extension is not allowed.1102if (this.allowedExtensionsService.isAllowed({ id: extensionIdentifier.id, publisherDisplayName: rawGalleryExtension.publisher.displayName }) !== true) {1103continue;1104}1105}1106const rawGalleryExtensionVersion = await this.getRawGalleryExtensionVersion(1107rawGalleryExtension,1108{1109compatible: criteria.compatible,1110targetPlatform: criteria.targetPlatform,1111productVersion: criteria.productVersion,1112version: criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version1113?? (includePreRelease ? VersionKind.Latest : VersionKind.Release)1114},1115allTargetPlatforms1116);1117const extension = rawGalleryExtensionVersion ? toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context) : null;1118if (!extension1119/** Need all versions if the extension is a pre-release version but1120* - the query is to look for a release version or1121* - the extension has no release version1122* Get all versions to get or check the release version1123*/1124|| (extension.properties.isPreReleaseVersion && (!includePreRelease || !extension.hasReleaseVersion))1125/**1126* Need all versions if the extension is a release version with a different target platform than requested and also has a pre-release version1127* Because, this is a platform specific extension and can have a newer release version supporting this platform.1128* See https://github.com/microsoft/vscode/issues/1396281129*/1130|| (!extension.properties.isPreReleaseVersion && extension.properties.targetPlatform !== criteria.targetPlatform && extension.hasPreReleaseVersion)1131) {1132needAllVersions.set(rawGalleryExtension.extensionId, index);1133} else {1134result.push([index, extension]);1135}1136}11371138if (needAllVersions.size) {1139const stopWatch = new StopWatch();1140const query = new Query()1141.withFlags(...flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeVersions)1142.withPage(1, needAllVersions.size)1143.withFilter(FilterType.ExtensionId, ...needAllVersions.keys());1144const { extensions } = await this.queryGalleryExtensions(query, criteria, extensionGalleryManifest, token);1145this.telemetryService.publicLog2<GalleryServiceAdditionalQueryEvent, GalleryServiceAdditionalQueryClassification>('galleryService:additionalQuery', {1146duration: stopWatch.elapsed(),1147count: needAllVersions.size1148});1149for (const extension of extensions) {1150const index = needAllVersions.get(extension.identifier.uuid)!;1151result.push([index, extension]);1152}1153}11541155return { extensions: result.sort((a, b) => a[0] - b[0]).map(([, extension]) => extension), total };1156}11571158private async queryGalleryExtensionsUsingIncludeLatestPrereleaseAndStableVersionFlag(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> {11591160/**1161* If versions criteria exist, then remove latest flags and add all versions flag.1162*/1163if (criteria.versions?.length) {1164query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly && flag !== Flag.IncludeLatestPrereleaseAndStableVersionOnly), Flag.IncludeVersions);1165}11661167/**1168* If the query does not specify all versions flag, handle latest versions.1169*/1170else if (!query.flags.includes(Flag.IncludeVersions)) {1171const includeLatest = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : criteria.includePreRelease.every(({ includePreRelease }) => includePreRelease);1172query = includeLatest ? query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestPrereleaseAndStableVersionOnly), Flag.IncludeLatestVersionOnly) : query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeLatestPrereleaseAndStableVersionOnly);1173}11741175/**1176* If all versions flag is set, remove latest flags.1177*/1178if (query.flags.includes(Flag.IncludeVersions) && (query.flags.includes(Flag.IncludeLatestVersionOnly) || query.flags.includes(Flag.IncludeLatestPrereleaseAndStableVersionOnly))) {1179query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly && flag !== Flag.IncludeLatestPrereleaseAndStableVersionOnly), Flag.IncludeVersions);1180}11811182/**1183* Add necessary extension flags1184*/1185query = query.withFlags(...query.flags, Flag.IncludeAssetUri, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeStatistics, Flag.IncludeVersionProperties);1186const { galleryExtensions: rawGalleryExtensions, total, context } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, token);11871188const extensions: IGalleryExtension[] = [];1189for (let index = 0; index < rawGalleryExtensions.length; index++) {1190const rawGalleryExtension = rawGalleryExtensions[index];1191const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };1192const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);1193if (criteria.compatible) {1194// Skip looking for all versions if requested for a web-compatible extension and it is not a web extension.1195if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {1196continue;1197}1198// Skip looking for all versions if the extension is not allowed.1199if (this.allowedExtensionsService.isAllowed({ id: extensionIdentifier.id, publisherDisplayName: rawGalleryExtension.publisher.displayName }) !== true) {1200continue;1201}1202}12031204const version = criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version1205?? ((isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease) ? VersionKind.Latest : VersionKind.Release);1206const rawGalleryExtensionVersion = await this.getRawGalleryExtensionVersion(1207rawGalleryExtension,1208{1209compatible: criteria.compatible,1210targetPlatform: criteria.targetPlatform,1211productVersion: criteria.productVersion,1212version1213},1214allTargetPlatforms1215);1216if (rawGalleryExtensionVersion) {1217extensions.push(toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context));1218}1219}12201221return { extensions, total };1222}12231224private async getRawGalleryExtensionVersion(rawGalleryExtension: IRawGalleryExtension, criteria: ExtensionVersionCriteria, allTargetPlatforms: TargetPlatform[]): Promise<IRawGalleryExtensionVersion | null> {1225const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };1226const rawGalleryExtensionVersions = sortExtensionVersions(rawGalleryExtension.versions, criteria.targetPlatform);12271228if (criteria.compatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {1229return null;1230}12311232const version = isString(criteria.version) ? criteria.version : undefined;12331234for (let index = 0; index < rawGalleryExtensionVersions.length; index++) {1235const rawGalleryExtensionVersion = rawGalleryExtensionVersions[index];1236if (await this.isValidVersion(1237{1238id: extensionIdentifier.id,1239version: rawGalleryExtensionVersion.version,1240isPreReleaseVersion: isPreReleaseVersion(rawGalleryExtensionVersion),1241targetPlatform: getTargetPlatformForExtensionVersion(rawGalleryExtensionVersion),1242engine: getEngine(rawGalleryExtensionVersion),1243manifestAsset: getVersionAsset(rawGalleryExtensionVersion, AssetType.Manifest),1244enabledApiProposals: getEnabledApiProposals(rawGalleryExtensionVersion)1245},1246criteria,1247rawGalleryExtension.publisher.displayName,1248allTargetPlatforms)1249) {1250return rawGalleryExtensionVersion;1251}1252if (version && rawGalleryExtensionVersion.version === version) {1253return null;1254}1255}12561257if (version || criteria.compatible) {1258return null;1259}12601261/**1262* Fallback: Return the latest version1263* This can happen when the extension does not have a release version or does not have a version compatible with the given target platform.1264*/1265return rawGalleryExtension.versions[0];1266}12671268private async queryRawGalleryExtensions(query: Query, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IRawGalleryExtensionsResult> {1269const extensionsQueryApi = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionQueryService);12701271if (!extensionsQueryApi) {1272throw new Error('No extension gallery query service configured.');1273}12741275query = query1276/* Always exclude non validated extensions */1277.withFlags(...query.flags, Flag.ExcludeNonValidated)1278.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');12791280const unpublishedFlag = extensionGalleryManifest.capabilities.extensionQuery.flags?.find(f => f.name === Flag.Unpublished);1281/* Always exclude unpublished extensions */1282if (unpublishedFlag) {1283query = query.withFilter(FilterType.ExcludeWithFlags, String(unpublishedFlag.value));1284}12851286const data = JSON.stringify({1287filters: [1288{1289criteria: query.criteria.reduce<{ filterType: number; value?: string }[]>((criteria, c) => {1290const criterium = extensionGalleryManifest.capabilities.extensionQuery.filtering?.find(f => f.name === c.filterType);1291if (criterium) {1292criteria.push({1293filterType: criterium.value,1294value: c.value,1295});1296}1297return criteria;1298}, []),1299pageNumber: query.pageNumber,1300pageSize: query.pageSize,1301sortBy: extensionGalleryManifest.capabilities.extensionQuery.sorting?.find(s => s.name === query.sortBy)?.value,1302sortOrder: query.sortOrder,1303}1304],1305assetTypes: query.assetTypes,1306flags: query.flags.reduce<number>((flags, flag) => {1307const flagValue = extensionGalleryManifest.capabilities.extensionQuery.flags?.find(f => f.name === flag);1308if (flagValue) {1309flags |= flagValue.value;1310}1311return flags;1312}, 0)1313});13141315const commonHeaders = await this.commonHeadersPromise;1316const headers = {1317...commonHeaders,1318'Content-Type': 'application/json',1319'Accept': 'application/json;api-version=3.0-preview.1',1320'Accept-Encoding': 'gzip',1321'Content-Length': String(data.length),1322};13231324const stopWatch = new StopWatch();1325let context: IRequestContext | undefined, errorCode: ExtensionGalleryErrorCode | undefined, total: number = 0;13261327try {1328context = await this.requestService.request({1329type: 'POST',1330url: extensionsQueryApi,1331data,1332headers1333}, token);13341335if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) {1336return { galleryExtensions: [], total };1337}13381339const result = await asJson<IRawGalleryQueryResult>(context);1340if (result) {1341const r = result.results[0];1342const galleryExtensions = r.extensions;1343const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];1344total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;13451346return {1347galleryExtensions,1348total,1349context: context.res.headers['activityid'] ? {1350[SEARCH_ACTIVITY_HEADER_NAME]: context.res.headers['activityid']1351} : {}1352};1353}1354return { galleryExtensions: [], total };13551356} catch (e) {1357if (isCancellationError(e)) {1358errorCode = ExtensionGalleryErrorCode.Cancelled;1359throw e;1360} else {1361const errorMessage = getErrorMessage(e);1362errorCode = isOfflineError(e)1363? ExtensionGalleryErrorCode.Offline1364: errorMessage.startsWith('XHR timeout')1365? ExtensionGalleryErrorCode.Timeout1366: ExtensionGalleryErrorCode.Failed;1367throw new ExtensionGalleryError(errorMessage, errorCode);1368}1369} finally {1370this.telemetryService.publicLog2<GalleryServiceQueryEvent, GalleryServiceQueryClassification>('galleryService:query', {1371filterTypes: query.criteria.map(criterium => criterium.filterType),1372flags: query.flags,1373sortBy: query.sortBy,1374sortOrder: String(query.sortOrder),1375pageNumber: String(query.pageNumber),1376source: query.source,1377searchTextLength: query.searchText.length,1378requestBodySize: String(data.length),1379duration: stopWatch.elapsed(),1380success: !!context && isSuccess(context),1381responseBodySize: context?.res.headers['Content-Length'],1382statusCode: context ? String(context.res.statusCode) : undefined,1383errorCode,1384count: String(total),1385server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),1386activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),1387endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),1388});1389}1390}13911392private getHeaderValue(headers: IHeaders | undefined, name: string): TelemetryTrustedValue<string> | undefined {1393const headerValue = headers?.[name.toLowerCase()];1394const value = Array.isArray(headerValue) ? headerValue[0] : headerValue;1395return value ? new TelemetryTrustedValue(value) : undefined;1396}13971398private async getLatestRawGalleryExtensionWithFallback(extensionInfo: IExtensionInfo, resourceApi: { uri: string; fallback?: string }, token: CancellationToken): Promise<IRawGalleryExtension | null> {1399const [publisher, name] = extensionInfo.id.split('.');1400let errorCode: string | undefined;1401try {1402const uri = URI.parse(format2(resourceApi.uri, { publisher, name }));1403return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);1404} catch (error) {1405if (error instanceof ExtensionGalleryError) {1406errorCode = error.code;1407switch (error.code) {1408case ExtensionGalleryErrorCode.Offline:1409case ExtensionGalleryErrorCode.Cancelled:1410case ExtensionGalleryErrorCode.Timeout:1411case ExtensionGalleryErrorCode.ClientError:1412throw error;1413}1414} else {1415errorCode = 'Unknown';1416}1417if (!resourceApi.fallback) {1418throw error;1419}1420} finally {1421this.telemetryService.publicLog2<1422{1423extension: string;1424errorCode?: string;1425},1426{1427owner: 'sandy081';1428comment: 'Report fetching latest version of an extension';1429extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };1430errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };1431}1432>('galleryService:getmarketplacelatest', {1433extension: extensionInfo.id,1434errorCode,1435});1436}14371438this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id} from ${resourceApi.uri}. Trying the fallback ${resourceApi.fallback}`, errorCode);1439try {1440const uri = URI.parse(format2(resourceApi.fallback, { publisher, name }));1441return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);1442} catch (error) {1443errorCode = error instanceof ExtensionGalleryError ? error.code : 'Unknown';1444throw error;1445} finally {1446this.telemetryService.publicLog2<1447{1448extension: string;1449errorCode?: string;1450},1451{1452owner: 'sandy081';1453comment: 'Report the fallback to the unpkg service for getting latest extension';1454extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };1455errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };1456}>('galleryService:fallbacktounpkg', {1457extension: extensionInfo.id,1458errorCode,1459});1460}1461}14621463private async getLatestRawGalleryExtension(extension: string, uri: URI, token: CancellationToken): Promise<IRawGalleryExtension | null> {1464let context;1465let errorCode: string | undefined;1466const stopWatch = new StopWatch();14671468try {1469const commonHeaders = await this.commonHeadersPromise;1470const headers = {1471...commonHeaders,1472'Content-Type': 'application/json',1473'Accept': 'application/json;api-version=7.2-preview',1474'Accept-Encoding': 'gzip',1475};14761477context = await this.requestService.request({1478type: 'GET',1479url: uri.toString(true),1480headers,1481timeout: REQUEST_TIME_OUT1482}, token);14831484if (context.res.statusCode === 404) {1485errorCode = 'NotFound';1486return null;1487}14881489if (context.res.statusCode && context.res.statusCode !== 200) {1490throw new Error('Unexpected HTTP response: ' + context.res.statusCode);1491}14921493const result = await asJson<IRawGalleryExtension>(context);1494if (!result) {1495errorCode = 'NoData';1496}1497return result;1498}14991500catch (error) {1501let galleryErrorCode: ExtensionGalleryErrorCode;1502if (isCancellationError(error)) {1503galleryErrorCode = ExtensionGalleryErrorCode.Cancelled;1504} else if (isOfflineError(error)) {1505galleryErrorCode = ExtensionGalleryErrorCode.Offline;1506} else if (getErrorMessage(error).startsWith('XHR timeout')) {1507galleryErrorCode = ExtensionGalleryErrorCode.Timeout;1508} else if (context && isClientError(context)) {1509galleryErrorCode = ExtensionGalleryErrorCode.ClientError;1510} else if (context && isServerError(context)) {1511galleryErrorCode = ExtensionGalleryErrorCode.ServerError;1512} else {1513galleryErrorCode = ExtensionGalleryErrorCode.Failed;1514}1515errorCode = galleryErrorCode;1516throw new ExtensionGalleryError(error, galleryErrorCode);1517}15181519finally {1520type GalleryServiceGetLatestEventClassification = {1521owner: 'sandy081';1522comment: 'Report the query to the Marketplace for fetching latest version of an extension';1523host: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The host of the end point' };1524extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };1525duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Duration in ms for the query' };1526errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };1527statusCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The status code in case of error' };1528server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The server of the end point' };1529activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The activity ID of the request' };1530endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The end-to-end ID of the request' };1531};1532type GalleryServiceGetLatestEvent = {1533extension: string;1534host: string;1535duration: number;1536errorCode?: string;1537statusCode?: string;1538server?: TelemetryTrustedValue<string>;1539activityId?: TelemetryTrustedValue<string>;1540endToEndId?: TelemetryTrustedValue<string>;1541};1542this.telemetryService.publicLog2<GalleryServiceGetLatestEvent, GalleryServiceGetLatestEventClassification>('galleryService:getLatest', {1543extension,1544host: uri.authority,1545duration: stopWatch.elapsed(),1546errorCode,1547statusCode: context?.res.statusCode && context?.res.statusCode !== 200 ? `${context.res.statusCode}` : undefined,1548server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),1549activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),1550endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),1551});1552}1553}15541555async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void> {1556const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();1557if (!manifest) {1558return undefined;1559}15601561let url: string;15621563if (isWeb) {1564const resource = getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.WebExtensionStatisticsUri);1565if (!resource) {1566return;1567}1568url = format2(resource, { publisher, name, version, statTypeValue: type === StatisticType.Install ? '1' : '3' });1569} else {1570const resource = getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.ExtensionStatisticsUri);1571if (!resource) {1572return;1573}1574url = format2(resource, { publisher, name, version, statTypeName: type });1575}15761577const Accept = isWeb ? 'api-version=6.1-preview.1' : '*/*;api-version=4.0-preview.1';1578const commonHeaders = await this.commonHeadersPromise;1579const headers = { ...commonHeaders, Accept };1580try {1581await this.requestService.request({1582type: 'POST',1583url,1584headers1585}, CancellationToken.None);1586} catch (error) { /* Ignore */ }1587}15881589async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {1590this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);1591const data = getGalleryExtensionTelemetryData(extension);1592const startTime = new Date().getTime();15931594const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';1595const downloadAsset = operationParam ? {1596uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`,1597fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true`1598} : extension.assets.download;15991600const headers: IHeaders | undefined = extension.queryContext?.[SEARCH_ACTIVITY_HEADER_NAME] ? { [SEARCH_ACTIVITY_HEADER_NAME]: extension.queryContext[SEARCH_ACTIVITY_HEADER_NAME] } : undefined;1601const context = await this.getAsset(extension.identifier.id, downloadAsset, AssetType.VSIX, extension.version, headers ? { headers } : undefined);16021603try {1604await this.fileService.writeFile(location, context.stream);1605} catch (error) {1606try {1607await this.fileService.del(location);1608} catch (e) {1609/* ignore */1610this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e));1611}1612throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting);1613}16141615/* __GDPR__1616"galleryService:downloadVSIX" : {1617"owner": "sandy081",1618"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },1619"${include}": [1620"${GalleryExtensionTelemetryData}"1621]1622}1623*/1624this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration: new Date().getTime() - startTime });1625}16261627async downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise<void> {1628if (!extension.assets.signature) {1629throw new Error('No signature asset found');1630}16311632this.logService.trace('ExtensionGalleryService#downloadSignatureArchive', extension.identifier.id);16331634const context = await this.getAsset(extension.identifier.id, extension.assets.signature, AssetType.Signature, extension.version);1635try {1636await this.fileService.writeFile(location, context.stream);1637} catch (error) {1638try {1639await this.fileService.del(location);1640} catch (e) {1641/* ignore */1642this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e));1643}1644throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting);1645}16461647}16481649async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {1650if (extension.assets.readme) {1651const context = await this.getAsset(extension.identifier.id, extension.assets.readme, AssetType.Details, extension.version, {}, token);1652const content = await asTextOrError(context);1653return content || '';1654}1655return '';1656}16571658async getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null> {1659if (extension.assets.manifest) {1660const context = await this.getAsset(extension.identifier.id, extension.assets.manifest, AssetType.Manifest, extension.version, {}, token);1661const text = await asTextOrError(context);1662return text ? JSON.parse(text) : null;1663}1664return null;1665}16661667async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null> {1668const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0];1669if (asset) {1670const context = await this.getAsset(extension.identifier.id, asset[1], asset[0], extension.version);1671const text = await asTextOrError(context);1672return text ? JSON.parse(text) : null;1673}1674return null;1675}16761677async getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string> {1678if (extension.assets.changelog) {1679const context = await this.getAsset(extension.identifier.id, extension.assets.changelog, AssetType.Changelog, extension.version, {}, token);1680const content = await asTextOrError(context);1681return content || '';1682}1683return '';1684}16851686async getAllVersions(extensionIdentifier: IExtensionIdentifier): Promise<IGalleryExtensionVersion[]> {1687return this.getVersions(extensionIdentifier);1688}16891690async getAllCompatibleVersions(extensionIdentifier: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]> {1691return this.getVersions(extensionIdentifier, { version: includePreRelease ? VersionKind.Latest : VersionKind.Release, targetPlatform });1692}16931694private async getVersions(extensionIdentifier: IExtensionIdentifier, onlyCompatible?: { version: VersionKind; targetPlatform: TargetPlatform }): Promise<IGalleryExtensionVersion[]> {1695const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();1696if (!extensionGalleryManifest) {1697throw new Error('No extension gallery service configured.');1698}16991700let query = new Query()1701.withFlags(Flag.IncludeVersions, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeVersionProperties)1702.withPage(1, 1);17031704if (extensionIdentifier.uuid) {1705query = query.withFilter(FilterType.ExtensionId, extensionIdentifier.uuid);1706} else {1707query = query.withFilter(FilterType.ExtensionName, extensionIdentifier.id);1708}17091710const { galleryExtensions } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, CancellationToken.None);1711if (!galleryExtensions.length) {1712return [];1713}17141715const allTargetPlatforms = getAllTargetPlatforms(galleryExtensions[0]);1716if (onlyCompatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, onlyCompatible.targetPlatform)) {1717return [];1718}17191720const versions: IRawGalleryExtensionVersion[] = [];1721const productVersion = { version: this.productService.version, date: this.productService.date };1722await Promise.all(galleryExtensions[0].versions.map(async (version) => {1723try {1724if (1725(await this.isValidVersion(1726{1727id: extensionIdentifier.id,1728version: version.version,1729isPreReleaseVersion: isPreReleaseVersion(version),1730targetPlatform: getTargetPlatformForExtensionVersion(version),1731engine: getEngine(version),1732manifestAsset: getVersionAsset(version, AssetType.Manifest),1733enabledApiProposals: getEnabledApiProposals(version)1734},1735{1736compatible: !!onlyCompatible,1737productVersion,1738targetPlatform: onlyCompatible?.targetPlatform,1739version: onlyCompatible?.version ?? version.version1740},1741galleryExtensions[0].publisher.displayName,1742allTargetPlatforms))1743) {1744versions.push(version);1745}1746} catch (error) { /* Ignore error and skip version */ }1747}));17481749const result: IGalleryExtensionVersion[] = [];1750const seen = new Map<string, number>();1751for (const version of sortExtensionVersions(versions, onlyCompatible?.targetPlatform ?? CURRENT_TARGET_PLATFORM)) {1752const index = seen.get(version.version);1753const existing = index !== undefined ? result[index] : undefined;1754const targetPlatform = getTargetPlatformForExtensionVersion(version);1755if (!existing) {1756seen.set(version.version, result.length);1757result.push({ version: version.version, date: version.lastUpdated, isPreReleaseVersion: isPreReleaseVersion(version), targetPlatforms: [targetPlatform] });1758} else {1759existing.targetPlatforms.push(targetPlatform);1760}1761}17621763return result;1764}17651766private async getAsset(extension: string, asset: IGalleryExtensionAsset, assetType: string, extensionVersion: string, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {1767const commonHeaders = await this.commonHeadersPromise;1768const baseOptions = { type: 'GET' };1769const headers = { ...commonHeaders, ...(options.headers || {}) };1770options = { ...options, ...baseOptions, headers };17711772const url = asset.uri;1773const fallbackUrl = asset.fallbackUri;1774const firstOptions = { ...options, url, timeout: REQUEST_TIME_OUT };17751776let context;1777try {1778context = await this.requestService.request(firstOptions, token);1779if (context.res.statusCode === 200) {1780return context;1781}1782const message = await asTextOrError(context);1783throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`);1784} catch (err) {1785if (isCancellationError(err)) {1786throw err;1787}17881789const message = getErrorMessage(err);1790type GalleryServiceCDNFallbackClassification = {1791owner: 'sandy081';1792comment: 'Fallback request information when the primary asset request to CDN fails';1793extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };1794assetType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'asset that failed' };1795message: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error message' };1796extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };1797readonly server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'server that handled the query' };1798readonly endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'end to end operation id' };1799readonly activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'activity id' };1800};1801type GalleryServiceCDNFallbackEvent = {1802extension: string;1803assetType: string;1804message: string;1805extensionVersion: string;1806server?: TelemetryTrustedValue<string>;1807endToEndId?: TelemetryTrustedValue<string>;1808activityId?: TelemetryTrustedValue<string>;1809};1810this.telemetryService.publicLog2<GalleryServiceCDNFallbackEvent, GalleryServiceCDNFallbackClassification>('galleryService:cdnFallback', {1811extension,1812assetType,1813message,1814extensionVersion,1815server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),1816activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),1817endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),1818});18191820const fallbackOptions = { ...options, url: fallbackUrl, timeout: REQUEST_TIME_OUT };1821return this.requestService.request(fallbackOptions, token);1822}1823}18241825async getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {1826if (!this.isEnabled()) {1827throw new Error('No extension gallery service configured.');1828}18291830if (!this.extensionsControlUrl) {1831return { malicious: [], deprecated: {}, search: [], autoUpdate: {} };1832}18331834const context = await this.requestService.request({1835type: 'GET',1836url: this.extensionsControlUrl,1837timeout: REQUEST_TIME_OUT1838}, CancellationToken.None);18391840if (context.res.statusCode !== 200) {1841throw new Error('Could not get extensions report.');1842}18431844const result = await asJson<IRawExtensionsControlManifest>(context);1845const malicious: Array<MaliciousExtensionInfo> = [];1846const deprecated: IStringDictionary<IDeprecationInfo> = {};1847const search: ISearchPrefferedResults[] = [];1848const autoUpdate: IStringDictionary<string> = result?.autoUpdate ?? {};1849if (result) {1850for (const id of result.malicious) {1851if (!isString(id)) {1852continue;1853}1854const publisherOrExtension = EXTENSION_IDENTIFIER_REGEX.test(id) ? { id } : id;1855malicious.push({ extensionOrPublisher: publisherOrExtension, learnMoreLink: result.learnMoreLinks?.[id] });1856}1857if (result.migrateToPreRelease) {1858for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) {1859if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) {1860deprecated[unsupportedPreReleaseExtensionId.toLowerCase()] = {1861disallowInstall: true,1862extension: {1863id: preReleaseExtensionInfo.id,1864displayName: preReleaseExtensionInfo.displayName,1865autoMigrate: { storage: !!preReleaseExtensionInfo.migrateStorage },1866preRelease: true1867}1868};1869}1870}1871}1872if (result.deprecated) {1873for (const [deprecatedExtensionId, deprecationInfo] of Object.entries(result.deprecated)) {1874if (deprecationInfo) {1875deprecated[deprecatedExtensionId.toLowerCase()] = isBoolean(deprecationInfo) ? {} : deprecationInfo;1876}1877}1878}1879if (result.search) {1880for (const s of result.search) {1881search.push(s);1882}1883}1884}18851886return { malicious, deprecated, search, autoUpdate };1887}18881889}18901891export class ExtensionGalleryService extends AbstractExtensionGalleryService {18921893constructor(1894@IStorageService storageService: IStorageService,1895@IRequestService requestService: IRequestService,1896@ILogService logService: ILogService,1897@IEnvironmentService environmentService: IEnvironmentService,1898@ITelemetryService telemetryService: ITelemetryService,1899@IFileService fileService: IFileService,1900@IProductService productService: IProductService,1901@IConfigurationService configurationService: IConfigurationService,1902@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,1903@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,1904) {1905super(storageService, undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService);1906}1907}19081909export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensionGalleryService {19101911constructor(1912@IRequestService requestService: IRequestService,1913@ILogService logService: ILogService,1914@IEnvironmentService environmentService: IEnvironmentService,1915@ITelemetryService telemetryService: ITelemetryService,1916@IFileService fileService: IFileService,1917@IProductService productService: IProductService,1918@IConfigurationService configurationService: IConfigurationService,1919@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,1920@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,1921) {1922super(undefined, undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService);1923}1924}192519261927