Path: blob/main/src/vs/platform/extensionManagement/common/extensionGalleryService.ts
5272 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, isNumber, 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, ExtensionRequestsTimeoutConfigKey } 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 { ExtensionGalleryResourceType, Flag, getExtensionGalleryManifestResourceUri, IExtensionGalleryManifest, IExtensionGalleryManifestService, ExtensionGalleryManifestStatus } from './extensionGalleryManifest.js';32import { TelemetryTrustedValue } from '../../telemetry/common/telemetryUtils.js';3334const CURRENT_TARGET_PLATFORM = isWeb ? TargetPlatform.WEB : getTargetPlatform(platform, arch);35const SEARCH_ACTIVITY_HEADER_NAME = 'X-Market-Search-Activity-Id';36const ACTIVITY_HEADER_NAME = 'Activityid';37const SERVER_HEADER_NAME = 'Server';38const END_END_ID_HEADER_NAME = 'X-Vss-E2eid';3940interface IRawGalleryExtensionFile {41readonly assetType: string;42readonly source: string;43}4445interface IRawGalleryExtensionProperty {46readonly key: string;47readonly value: string;48}4950export interface IRawGalleryExtensionVersion {51readonly version: string;52readonly lastUpdated: string;53readonly assetUri: string;54readonly fallbackAssetUri: string;55readonly files: IRawGalleryExtensionFile[];56properties?: IRawGalleryExtensionProperty[];57readonly targetPlatform?: string;58}5960interface IRawGalleryExtensionStatistics {61readonly statisticName: string;62readonly value: number;63}6465interface IRawGalleryExtensionPublisher {66readonly displayName: string;67readonly publisherId: string;68readonly publisherName: string;69readonly domain?: string | null;70readonly isDomainVerified?: boolean;71readonly linkType?: string;72}7374interface IRawGalleryExtension {75readonly extensionId: string;76readonly extensionName: string;77readonly displayName: string;78readonly shortDescription?: string;79readonly publisher: IRawGalleryExtensionPublisher;80readonly versions: IRawGalleryExtensionVersion[];81readonly statistics: IRawGalleryExtensionStatistics[];82readonly tags: string[] | undefined;83readonly releaseDate: string;84readonly publishedDate: string;85readonly lastUpdated: string;86readonly categories: string[] | undefined;87readonly flags: string;88readonly linkType?: string;89readonly ratingLinkType?: string;90}9192interface IRawGalleryExtensionsResult {93readonly galleryExtensions: IRawGalleryExtension[];94readonly total: number;95readonly context?: IStringDictionary<string>;96}9798interface IRawGalleryQueryResult {99readonly results: {100readonly extensions: IRawGalleryExtension[];101readonly resultMetadata: {102readonly metadataType: string;103readonly metadataItems: {104readonly name: string;105readonly count: number;106}[];107}[];108}[];109}110111const AssetType = {112Icon: 'Microsoft.VisualStudio.Services.Icons.Default',113Details: 'Microsoft.VisualStudio.Services.Content.Details',114Changelog: 'Microsoft.VisualStudio.Services.Content.Changelog',115Manifest: 'Microsoft.VisualStudio.Code.Manifest',116VSIX: 'Microsoft.VisualStudio.Services.VSIXPackage',117License: 'Microsoft.VisualStudio.Services.Content.License',118Repository: 'Microsoft.VisualStudio.Services.Links.Source',119Signature: 'Microsoft.VisualStudio.Services.VsixSignature'120};121122const PropertyType = {123Dependency: 'Microsoft.VisualStudio.Code.ExtensionDependencies',124ExtensionPack: 'Microsoft.VisualStudio.Code.ExtensionPack',125Engine: 'Microsoft.VisualStudio.Code.Engine',126PreRelease: 'Microsoft.VisualStudio.Code.PreRelease',127EnabledApiProposals: 'Microsoft.VisualStudio.Code.EnabledApiProposals',128LocalizedLanguages: 'Microsoft.VisualStudio.Code.LocalizedLanguages',129WebExtension: 'Microsoft.VisualStudio.Code.WebExtension',130SponsorLink: 'Microsoft.VisualStudio.Code.SponsorLink',131SupportLink: 'Microsoft.VisualStudio.Services.Links.Support',132ExecutesCode: 'Microsoft.VisualStudio.Code.ExecutesCode',133Private: 'PrivateMarketplace',134};135136interface ICriterium {137readonly filterType: FilterType;138readonly value?: string;139}140141const DefaultPageSize = 10;142143interface IQueryState {144readonly pageNumber: number;145readonly pageSize: number;146readonly sortBy: SortBy;147readonly sortOrder: SortOrder;148readonly flags: Flag[];149readonly criteria: ICriterium[];150readonly assetTypes: string[];151readonly source?: string;152}153154const DefaultQueryState: IQueryState = {155pageNumber: 1,156pageSize: DefaultPageSize,157sortBy: SortBy.NoneOrRelevance,158sortOrder: SortOrder.Default,159flags: [],160criteria: [],161assetTypes: []162};163164type GalleryServiceQueryClassification = {165owner: 'sandy081';166comment: 'Information about Marketplace query and its response';167readonly filterTypes: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Filter types used in the query.' };168readonly flags: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Flags passed in the query.' };169readonly sortBy: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'sorted by option passed in the query' };170readonly sortOrder: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'sort order option passed in the query' };171readonly pageNumber: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'requested page number in the query' };172readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true; comment: 'amount of time taken by the query request' };173readonly success: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'whether the query request is success or not' };174readonly requestBodySize: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'size of the request body' };175readonly responseBodySize?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'size of the response body' };176readonly statusCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'status code of the response' };177readonly errorCode?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error code of the response' };178readonly count?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'total number of extensions matching the query' };179readonly source?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'source that requested this query, eg., recommendations, viewlet' };180readonly searchTextLength?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'length of the search text in the query' };181readonly server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'server that handled the query' };182readonly endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'end to end operation id' };183readonly activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'activity id' };184};185186type QueryTelemetryData = {187readonly filterTypes: string[];188readonly flags: string[];189readonly sortBy: string;190readonly sortOrder: string;191readonly pageNumber: string;192readonly source?: string;193readonly searchTextLength?: number;194};195196type GalleryServiceQueryEvent = QueryTelemetryData & {197readonly duration: number;198readonly success: boolean;199readonly requestBodySize: string;200readonly responseBodySize?: string;201readonly statusCode?: string;202readonly errorCode?: string;203readonly count?: string;204readonly server?: TelemetryTrustedValue<string>;205readonly endToEndId?: TelemetryTrustedValue<string>;206readonly activityId?: TelemetryTrustedValue<string>;207};208209type GalleryServiceAdditionalQueryClassification = {210owner: 'sandy081';211comment: 'Response information about the additional query to the Marketplace for fetching all versions to get release version';212readonly duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; 'isMeasurement': true; comment: 'Amount of time taken by the additional query' };213readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Total number of extensions returned by this additional query' };214};215216type GalleryServiceAdditionalQueryEvent = {217readonly duration: number;218readonly count: number;219};220221type ExtensionsCriteria = {222readonly productVersion: IProductVersion;223readonly targetPlatform: TargetPlatform;224readonly compatible: boolean;225readonly includePreRelease: boolean | (IExtensionIdentifier & { includePreRelease: boolean })[];226readonly versions?: (IExtensionIdentifier & { version: string })[];227readonly isQueryForReleaseVersionFromPreReleaseVersion?: boolean;228};229230const enum VersionKind {231Release,232Prerelease,233Latest234}235236type ExtensionVersionCriteria = {237readonly productVersion: IProductVersion;238readonly targetPlatform: TargetPlatform;239readonly compatible: boolean;240readonly version: VersionKind | string;241};242243class Query {244245constructor(private state = DefaultQueryState) { }246247get pageNumber(): number { return this.state.pageNumber; }248get pageSize(): number { return this.state.pageSize; }249get sortBy(): SortBy { return this.state.sortBy; }250get sortOrder(): number { return this.state.sortOrder; }251get flags(): Flag[] { return this.state.flags; }252get criteria(): ICriterium[] { return this.state.criteria; }253get assetTypes(): string[] { return this.state.assetTypes; }254get source(): string | undefined { return this.state.source; }255get searchText(): string {256const criterium = this.state.criteria.filter(criterium => criterium.filterType === FilterType.SearchText)[0];257return criterium && criterium.value ? criterium.value : '';258}259260261withPage(pageNumber: number, pageSize: number = this.state.pageSize): Query {262return new Query({ ...this.state, pageNumber, pageSize });263}264265withFilter(filterType: FilterType, ...values: string[]): Query {266const criteria = [267...this.state.criteria,268...values.length ? values.map(value => ({ filterType, value })) : [{ filterType }]269];270271return new Query({ ...this.state, criteria });272}273274withSortBy(sortBy: SortBy): Query {275return new Query({ ...this.state, sortBy });276}277278withSortOrder(sortOrder: SortOrder): Query {279return new Query({ ...this.state, sortOrder });280}281282withFlags(...flags: Flag[]): Query {283return new Query({ ...this.state, flags: distinct(flags) });284}285286withAssetTypes(...assetTypes: string[]): Query {287return new Query({ ...this.state, assetTypes });288}289290withSource(source: string): Query {291return new Query({ ...this.state, source });292}293}294295function getStatistic(statistics: IRawGalleryExtensionStatistics[], name: string): number {296const result = (statistics || []).filter(s => s.statisticName === name)[0];297return result ? result.value : 0;298}299300function getCoreTranslationAssets(version: IRawGalleryExtensionVersion): [string, IGalleryExtensionAsset][] {301const coreTranslationAssetPrefix = 'Microsoft.VisualStudio.Code.Translation.';302const result = version.files.filter(f => f.assetType.indexOf(coreTranslationAssetPrefix) === 0);303return result.reduce<[string, IGalleryExtensionAsset][]>((result, file) => {304const asset = getVersionAsset(version, file.assetType);305if (asset) {306result.push([file.assetType.substring(coreTranslationAssetPrefix.length), asset]);307}308return result;309}, []);310}311312function getRepositoryAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset | null {313if (version.properties) {314const results = version.properties.filter(p => p.key === AssetType.Repository);315const gitRegExp = new RegExp('((git|ssh|http(s)?)|(git@[\\w.]+))(:(//)?)([\\w.@:/\\-~]+)(.git)(/)?');316317const uri = results.filter(r => gitRegExp.test(r.value))[0];318return uri ? { uri: uri.value, fallbackUri: uri.value } : null;319}320return getVersionAsset(version, AssetType.Repository);321}322323function getDownloadAsset(version: IRawGalleryExtensionVersion): IGalleryExtensionAsset {324return {325// always use fallbackAssetUri for download asset to hit the Marketplace API so that downloads are counted326uri: `${version.fallbackAssetUri}/${AssetType.VSIX}?redirect=true${version.targetPlatform ? `&targetPlatform=${version.targetPlatform}` : ''}`,327fallbackUri: `${version.fallbackAssetUri}/${AssetType.VSIX}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`328};329}330331function getVersionAsset(version: IRawGalleryExtensionVersion, type: string): IGalleryExtensionAsset | null {332const result = version.files.filter(f => f.assetType === type)[0];333return result ? {334uri: `${version.assetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`,335fallbackUri: `${version.fallbackAssetUri}/${type}${version.targetPlatform ? `?targetPlatform=${version.targetPlatform}` : ''}`336} : null;337}338339function getExtensions(version: IRawGalleryExtensionVersion, property: string): string[] {340const values = version.properties ? version.properties.filter(p => p.key === property) : [];341const value = values.length > 0 && values[0].value;342return value ? value.split(',').map(v => adoptToGalleryExtensionId(v)) : [];343}344345function getEngine(version: IRawGalleryExtensionVersion): string {346const values = version.properties ? version.properties.filter(p => p.key === PropertyType.Engine) : [];347return (values.length > 0 && values[0].value) || '';348}349350function setEngine(version: IRawGalleryExtensionVersion, engine: string): void {351version.properties = version.properties ?? [];352version.properties.push({ key: PropertyType.Engine, value: engine });353}354355function isPreReleaseVersion(version: IRawGalleryExtensionVersion): boolean {356const values = version.properties ? version.properties.filter(p => p.key === PropertyType.PreRelease) : [];357return values.length > 0 && values[0].value === 'true';358}359360function hasPreReleaseForExtension(id: string, productService: IProductService): boolean | undefined {361return productService.extensionProperties?.[id.toLowerCase()]?.hasPrereleaseVersion;362}363364function getExcludeVersionRangeForExtension(id: string, productService: IProductService): string | undefined {365return productService.extensionProperties?.[id.toLowerCase()]?.excludeVersionRange;366}367368function isPrivateExtension(version: IRawGalleryExtensionVersion): boolean {369const values = version.properties ? version.properties.filter(p => p.key === PropertyType.Private) : [];370return values.length > 0 && values[0].value === 'true';371}372373function executesCode(version: IRawGalleryExtensionVersion): boolean | undefined {374const values = version.properties ? version.properties.filter(p => p.key === PropertyType.ExecutesCode) : [];375return values.length > 0 ? values[0].value === 'true' : undefined;376}377378function getEnabledApiProposals(version: IRawGalleryExtensionVersion): string[] {379const values = version.properties ? version.properties.filter(p => p.key === PropertyType.EnabledApiProposals) : [];380const value = (values.length > 0 && values[0].value) || '';381return value ? value.split(',') : [];382}383384function getLocalizedLanguages(version: IRawGalleryExtensionVersion): string[] {385const values = version.properties ? version.properties.filter(p => p.key === PropertyType.LocalizedLanguages) : [];386const value = (values.length > 0 && values[0].value) || '';387return value ? value.split(',') : [];388}389390function getSponsorLink(version: IRawGalleryExtensionVersion): string | undefined {391return version.properties?.find(p => p.key === PropertyType.SponsorLink)?.value;392}393394function getSupportLink(version: IRawGalleryExtensionVersion): string | undefined {395return version.properties?.find(p => p.key === PropertyType.SupportLink)?.value;396}397398function getIsPreview(flags: string): boolean {399return flags.indexOf('preview') !== -1;400}401402function getTargetPlatformForExtensionVersion(version: IRawGalleryExtensionVersion): TargetPlatform {403return version.targetPlatform ? toTargetPlatform(version.targetPlatform) : TargetPlatform.UNDEFINED;404}405406function getAllTargetPlatforms(rawGalleryExtension: IRawGalleryExtension): TargetPlatform[] {407const allTargetPlatforms = distinct(rawGalleryExtension.versions.map(getTargetPlatformForExtensionVersion));408409// Is a web extension only if it has WEB_EXTENSION_TAG410const isWebExtension = !!rawGalleryExtension.tags?.includes(WEB_EXTENSION_TAG);411412// Include Web Target Platform only if it is a web extension413const webTargetPlatformIndex = allTargetPlatforms.indexOf(TargetPlatform.WEB);414if (isWebExtension) {415if (webTargetPlatformIndex === -1) {416// Web extension but does not has web target platform -> add it417allTargetPlatforms.push(TargetPlatform.WEB);418}419} else {420if (webTargetPlatformIndex !== -1) {421// Not a web extension but has web target platform -> remove it422allTargetPlatforms.splice(webTargetPlatformIndex, 1);423}424}425426return allTargetPlatforms;427}428429export function sortExtensionVersions(versions: IRawGalleryExtensionVersion[], preferredTargetPlatform: TargetPlatform): IRawGalleryExtensionVersion[] {430/* It is expected that versions from Marketplace are sorted by version. So we are just sorting by preferred targetPlatform */431for (let index = 0; index < versions.length; index++) {432const version = versions[index];433if (version.version === versions[index - 1]?.version) {434let insertionIndex = index;435const versionTargetPlatform = getTargetPlatformForExtensionVersion(version);436/* put it at the beginning */437if (versionTargetPlatform === preferredTargetPlatform) {438while (insertionIndex > 0 && versions[insertionIndex - 1].version === version.version) { insertionIndex--; }439}440if (insertionIndex !== index) {441versions.splice(index, 1);442versions.splice(insertionIndex, 0, version);443}444}445}446return versions;447}448449/**450* Filters extension versions to return only the relevant versions for a given target platform.451*452* This function processes a list of extension versions (expected to be sorted by version descending)453* and returns a filtered list containing:454* 1. All versions that are NOT compatible with the target platform (for other platforms)455* 2. At most one compatible release version (the first/latest one encountered)456* 3. At most one compatible pre-release version (the first/latest one encountered)457*458* When a platform-specific version (exactly matching targetPlatform) is encountered with the same459* version number as a previously stored universal/undefined version, it replaces that version.460* This ensures platform-specific builds are preferred over universal builds for the same version.461*462* @param versions - Array of extension versions, expected to be sorted by version number descending463* @param targetPlatform - The target platform to filter for (e.g., LINUX_X64, WIN32_X64)464* @param allTargetPlatforms - All target platforms the extension supports465* @returns Filtered array of versions relevant for the target platform466*/467export function filterLatestExtensionVersionsForTargetPlatform(versions: IRawGalleryExtensionVersion[], targetPlatform: TargetPlatform, allTargetPlatforms: TargetPlatform[]): IRawGalleryExtensionVersion[] {468const latestVersions: IRawGalleryExtensionVersion[] = [];469470let preReleaseVersionIndex: number = -1;471let releaseVersionIndex: number = -1;472for (const version of versions) {473const versionTargetPlatform = getTargetPlatformForExtensionVersion(version);474const isCompatibleWithTargetPlatform = isTargetPlatformCompatible(versionTargetPlatform, allTargetPlatforms, targetPlatform);475476// Always include versions that are NOT compatible with the target platform477if (!isCompatibleWithTargetPlatform) {478latestVersions.push(version);479continue;480}481482// For compatible versions, only include the first (latest) of each type483// Prefer specific target platform matches over undefined/universal platforms only when version numbers are the same484if (isPreReleaseVersion(version)) {485if (preReleaseVersionIndex === -1) {486preReleaseVersionIndex = latestVersions.length;487latestVersions.push(version);488} else if (versionTargetPlatform === targetPlatform && latestVersions[preReleaseVersionIndex].version === version.version) {489latestVersions[preReleaseVersionIndex] = version;490}491} else {492if (releaseVersionIndex === -1) {493releaseVersionIndex = latestVersions.length;494latestVersions.push(version);495} else if (versionTargetPlatform === targetPlatform && latestVersions[releaseVersionIndex].version === version.version) {496latestVersions[releaseVersionIndex] = version;497}498}499}500501return latestVersions;502}503504function setTelemetry(extension: IGalleryExtension, index: number, querySource?: string): void {505/* __GDPR__FRAGMENT__506"GalleryExtensionTelemetryData2" : {507"index" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },508"querySource": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },509"queryActivityId": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }510}511*/512extension.telemetryData = { index, querySource, queryActivityId: extension.queryContext?.[SEARCH_ACTIVITY_HEADER_NAME] };513}514515function toExtension(galleryExtension: IRawGalleryExtension, version: IRawGalleryExtensionVersion, allTargetPlatforms: TargetPlatform[], extensionGalleryManifest: IExtensionGalleryManifest, productService: IProductService, queryContext?: IStringDictionary<unknown>): IGalleryExtension {516const latestVersion = galleryExtension.versions[0];517const assets: IGalleryExtensionAssets = {518manifest: getVersionAsset(version, AssetType.Manifest),519readme: getVersionAsset(version, AssetType.Details),520changelog: getVersionAsset(version, AssetType.Changelog),521license: getVersionAsset(version, AssetType.License),522repository: getRepositoryAsset(version),523download: getDownloadAsset(version),524icon: getVersionAsset(version, AssetType.Icon),525signature: getVersionAsset(version, AssetType.Signature),526coreTranslations: getCoreTranslationAssets(version)527};528529const detailsViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.linkType ?? ExtensionGalleryResourceType.ExtensionDetailsViewUri);530const publisherViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.publisher.linkType ?? ExtensionGalleryResourceType.PublisherViewUri);531const ratingViewUri = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, galleryExtension.ratingLinkType ?? ExtensionGalleryResourceType.ExtensionRatingViewUri);532const id = getGalleryExtensionId(galleryExtension.publisher.publisherName, galleryExtension.extensionName);533534return {535type: 'gallery',536identifier: {537id,538uuid: galleryExtension.extensionId539},540name: galleryExtension.extensionName,541version: version.version,542displayName: galleryExtension.displayName,543publisherId: galleryExtension.publisher.publisherId,544publisher: galleryExtension.publisher.publisherName,545publisherDisplayName: galleryExtension.publisher.displayName,546publisherDomain: galleryExtension.publisher.domain ? { link: galleryExtension.publisher.domain, verified: !!galleryExtension.publisher.isDomainVerified } : undefined,547publisherSponsorLink: getSponsorLink(latestVersion),548description: galleryExtension.shortDescription ?? '',549installCount: getStatistic(galleryExtension.statistics, 'install'),550rating: getStatistic(galleryExtension.statistics, 'averagerating'),551ratingCount: getStatistic(galleryExtension.statistics, 'ratingcount'),552categories: galleryExtension.categories || [],553tags: galleryExtension.tags || [],554releaseDate: Date.parse(galleryExtension.releaseDate),555lastUpdated: Date.parse(galleryExtension.lastUpdated),556allTargetPlatforms,557assets,558properties: {559dependencies: getExtensions(version, PropertyType.Dependency),560extensionPack: getExtensions(version, PropertyType.ExtensionPack),561engine: getEngine(version),562enabledApiProposals: getEnabledApiProposals(version),563localizedLanguages: getLocalizedLanguages(version),564targetPlatform: getTargetPlatformForExtensionVersion(version),565isPreReleaseVersion: isPreReleaseVersion(version),566executesCode: executesCode(version)567},568hasPreReleaseVersion: hasPreReleaseForExtension(id, productService) ?? isPreReleaseVersion(latestVersion),569hasReleaseVersion: true,570private: isPrivateExtension(latestVersion),571preview: getIsPreview(galleryExtension.flags),572isSigned: !!assets.signature,573queryContext,574supportLink: getSupportLink(latestVersion),575detailsLink: detailsViewUri ? format2(detailsViewUri, { publisher: galleryExtension.publisher.publisherName, name: galleryExtension.extensionName }) : undefined,576publisherLink: publisherViewUri ? format2(publisherViewUri, { publisher: galleryExtension.publisher.publisherName }) : undefined,577ratingLink: ratingViewUri ? format2(ratingViewUri, { publisher: galleryExtension.publisher.publisherName, name: galleryExtension.extensionName }) : undefined,578};579}580581interface IRawExtensionsControlManifest {582malicious: string[];583learnMoreLinks?: IStringDictionary<string>;584migrateToPreRelease?: IStringDictionary<{585id: string;586displayName: string;587migrateStorage?: boolean;588engine?: string;589}>;590deprecated?: IStringDictionary<boolean | {591disallowInstall?: boolean;592extension?: {593id: string;594displayName: string;595};596settings?: string[];597additionalInfo?: string;598}>;599search?: ISearchPrefferedResults[];600autoUpdate?: IStringDictionary<string>;601}602603export abstract class AbstractExtensionGalleryService implements IExtensionGalleryService {604605declare readonly _serviceBrand: undefined;606607private readonly extensionsControlUrl: string | undefined;608private readonly unpkgResourceApi: string | undefined;609610private readonly commonHeadersPromise: Promise<IHeaders>;611private readonly extensionsEnabledWithApiProposalVersion: string[];612613constructor(614storageService: IStorageService | undefined,615@IRequestService private readonly requestService: IRequestService,616@ILogService private readonly logService: ILogService,617@IEnvironmentService private readonly environmentService: IEnvironmentService,618@ITelemetryService private readonly telemetryService: ITelemetryService,619@IFileService private readonly fileService: IFileService,620@IProductService private readonly productService: IProductService,621@IConfigurationService private readonly configurationService: IConfigurationService,622@IAllowedExtensionsService private readonly allowedExtensionsService: IAllowedExtensionsService,623@IExtensionGalleryManifestService private readonly extensionGalleryManifestService: IExtensionGalleryManifestService,624) {625this.extensionsControlUrl = productService.extensionsGallery?.controlUrl;626this.unpkgResourceApi = productService.extensionsGallery?.extensionUrlTemplate;627this.extensionsEnabledWithApiProposalVersion = productService.extensionsEnabledWithApiProposalVersion?.map(id => id.toLowerCase()) ?? [];628this.commonHeadersPromise = resolveMarketplaceHeaders(629productService.version,630productService,631this.environmentService,632this.configurationService,633this.fileService,634storageService,635this.telemetryService);636}637638isEnabled(): boolean {639return this.extensionGalleryManifestService.extensionGalleryManifestStatus === ExtensionGalleryManifestStatus.Available;640}641642getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, token: CancellationToken): Promise<IGalleryExtension[]>;643getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, token: CancellationToken): Promise<IGalleryExtension[]>;644async getExtensions(extensionInfos: ReadonlyArray<IExtensionInfo>, arg1: CancellationToken | IExtensionQueryOptions, arg2?: CancellationToken): Promise<IGalleryExtension[]> {645const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();646if (!extensionGalleryManifest) {647throw new Error('No extension gallery service configured.');648}649650const options = CancellationToken.isCancellationToken(arg1) ? {} : arg1 as IExtensionQueryOptions;651const token = CancellationToken.isCancellationToken(arg1) ? arg1 : arg2 as CancellationToken;652653const resourceApi = this.getResourceApi(extensionGalleryManifest);654const result = resourceApi655? await this.getExtensionsUsingResourceApi(extensionInfos, options, resourceApi, extensionGalleryManifest, token)656: await this.getExtensionsUsingQueryApi(extensionInfos, options, extensionGalleryManifest, token);657658const uuids = result.map(r => r.identifier.uuid);659const extensionInfosByName: IExtensionInfo[] = [];660for (const e of extensionInfos) {661if (e.uuid && !uuids.includes(e.uuid)) {662extensionInfosByName.push({ ...e, uuid: undefined });663}664}665666if (extensionInfosByName.length) {667// report telemetry data for additional query668this.telemetryService.publicLog2<669{ count: number },670{671owner: 'sandy081';672comment: 'Report the query to the Marketplace for fetching extensions by name';673readonly count: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Number of extensions to fetch' };674}>('galleryService:additionalQueryByName', {675count: extensionInfosByName.length676});677678const extensions = await this.getExtensionsUsingQueryApi(extensionInfosByName, options, extensionGalleryManifest, token);679result.push(...extensions);680}681682return result;683}684685private getResourceApi(extensionGalleryManifest: IExtensionGalleryManifest): { uri: string; fallback?: string } | undefined {686const latestVersionResource = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionLatestVersionUri);687if (latestVersionResource) {688return {689uri: latestVersionResource,690fallback: this.unpkgResourceApi691};692}693return undefined;694}695696private async getExtensionsUsingQueryApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {697const names: string[] = [],698ids: string[] = [],699includePreRelease: (IExtensionIdentifier & { includePreRelease: boolean })[] = [],700versions: (IExtensionIdentifier & { version: string })[] = [];701let isQueryForReleaseVersionFromPreReleaseVersion = true;702703for (const extensionInfo of extensionInfos) {704if (extensionInfo.uuid) {705ids.push(extensionInfo.uuid);706} else {707names.push(extensionInfo.id);708}709if (extensionInfo.version) {710versions.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, version: extensionInfo.version });711} else {712includePreRelease.push({ id: extensionInfo.id, uuid: extensionInfo.uuid, includePreRelease: !!extensionInfo.preRelease });713}714isQueryForReleaseVersionFromPreReleaseVersion = isQueryForReleaseVersionFromPreReleaseVersion && (!!extensionInfo.hasPreRelease && !extensionInfo.preRelease);715}716717if (!ids.length && !names.length) {718return [];719}720721let query = new Query().withPage(1, extensionInfos.length);722if (ids.length) {723query = query.withFilter(FilterType.ExtensionId, ...ids);724}725if (names.length) {726query = query.withFilter(FilterType.ExtensionName, ...names);727}728if (options.queryAllVersions) {729query = query.withFlags(...query.flags, Flag.IncludeVersions);730}731if (options.source) {732query = query.withSource(options.source);733}734735const { extensions } = await this.queryGalleryExtensions(736query,737{738targetPlatform: options.targetPlatform ?? CURRENT_TARGET_PLATFORM,739includePreRelease,740versions,741compatible: !!options.compatible,742productVersion: options.productVersion ?? { version: this.productService.version, date: this.productService.date },743isQueryForReleaseVersionFromPreReleaseVersion744},745extensionGalleryManifest,746token);747748if (options.source) {749extensions.forEach((e, index) => setTelemetry(e, index, options.source));750}751752return extensions;753}754755private async getExtensionsUsingResourceApi(extensionInfos: ReadonlyArray<IExtensionInfo>, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension[]> {756757const result: IGalleryExtension[] = [];758const toQuery: IExtensionInfo[] = [];759const toFetchLatest: IExtensionInfo[] = [];760761for (const extensionInfo of extensionInfos) {762if (!EXTENSION_IDENTIFIER_REGEX.test(extensionInfo.id)) {763continue;764}765if (extensionInfo.version) {766toQuery.push(extensionInfo);767} else {768toFetchLatest.push(extensionInfo);769}770}771772await Promise.all(toFetchLatest.map(async extensionInfo => {773let galleryExtension: IGalleryExtension | string;774try {775galleryExtension = await this.getLatestGalleryExtension(extensionInfo, options, resourceApi, extensionGalleryManifest, token);776if (isString(galleryExtension)) {777// fallback to query778this.telemetryService.publicLog2<779{780extension: string;781preRelease: boolean;782compatible: boolean;783errorCode: string;784},785{786owner: 'sandy081';787comment: 'Report the fallback to the Marketplace query for fetching extensions';788extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };789preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' };790compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' };791errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code or reason' };792}>('galleryService:fallbacktoquery', {793extension: extensionInfo.id,794preRelease: !!extensionInfo.preRelease,795compatible: !!options.compatible,796errorCode: galleryExtension797});798toQuery.push(extensionInfo);799} else {800result.push(galleryExtension);801}802} catch (error) {803if (error instanceof ExtensionGalleryError) {804switch (error.code) {805case ExtensionGalleryErrorCode.Offline:806case ExtensionGalleryErrorCode.Cancelled:807case ExtensionGalleryErrorCode.Timeout:808throw error;809}810}811812// fallback to query813this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id}.`, getErrorMessage(error));814this.telemetryService.publicLog2<815{816extension: string;817preRelease: boolean;818compatible: boolean;819errorCode: string;820},821{822owner: 'sandy081';823comment: 'Report the fallback to the Marketplace query for fetching extensions';824extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };825preRelease: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get pre-release version' };826compatible: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Get compatible version' };827errorCode: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Error code' };828}>('galleryService:fallbacktoquery', {829extension: extensionInfo.id,830preRelease: !!extensionInfo.preRelease,831compatible: !!options.compatible,832errorCode: error instanceof ExtensionGalleryError ? error.code : 'Unknown'833});834toQuery.push(extensionInfo);835}836837}));838839if (toQuery.length) {840const extensions = await this.getExtensionsUsingQueryApi(toQuery, options, extensionGalleryManifest, token);841result.push(...extensions);842}843844return result;845}846847private async getLatestGalleryExtension(extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, resourceApi: { uri: string; fallback?: string }, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IGalleryExtension | string> {848const rawGalleryExtension = await this.getLatestRawGalleryExtensionWithFallback(extensionInfo, resourceApi, token);849850if (!rawGalleryExtension) {851return 'NOT_FOUND';852}853854const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);855const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersionFromLatestVersions(rawGalleryExtension, rawGalleryExtension.versions, extensionInfo, options, allTargetPlatforms);856857if (!rawGalleryExtensionVersion) {858return 'NOT_COMPATIBLE';859}860861return toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService);862}863864private async getValidRawGalleryExtensionVersionFromLatestVersions(rawGalleryExtension: IRawGalleryExtension, latestVersions: IRawGalleryExtensionVersion[], extensionInfo: IExtensionInfo, options: IExtensionQueryOptions, allTargetPlatforms: TargetPlatform[]): Promise<IRawGalleryExtensionVersion | null> {865const targetPlatform = options.targetPlatform ?? CURRENT_TARGET_PLATFORM;866const latestExtensionVersionsForTargetPlatform = filterLatestExtensionVersionsForTargetPlatform(latestVersions, targetPlatform, allTargetPlatforms);867868// First, find a valid version matching the requested type (pre-release or release)869const result = await this.getValidRawGalleryExtensionVersion(870rawGalleryExtension,871latestExtensionVersionsForTargetPlatform,872{873targetPlatform,874compatible: !!options.compatible,875productVersion: options.productVersion ?? {876version: this.productService.version,877date: this.productService.date878},879version: extensionInfo.preRelease ? VersionKind.Prerelease : VersionKind.Release880}, allTargetPlatforms);881882// For release version requests, simply return the found release version883if (!extensionInfo.preRelease) {884return result;885}886887// For pre-release version requests, we need to consider both pre-release and release versions888const prereleaseVersion = result;889const releaseVersion = await this.getValidRawGalleryExtensionVersion(890rawGalleryExtension,891latestExtensionVersionsForTargetPlatform,892{893targetPlatform,894compatible: !!options.compatible,895productVersion: options.productVersion ?? {896version: this.productService.version,897date: this.productService.date898},899version: VersionKind.Release900}, allTargetPlatforms);901902// When both versions exist, return whichever has the higher version number903if (prereleaseVersion && releaseVersion) {904return semver.gt(releaseVersion.version, prereleaseVersion.version) ? releaseVersion : prereleaseVersion;905}906907// Special handling for compatible version requests908if (options.compatible) {909// If we have a compatible release version, check if it's better than any pre-release910if (releaseVersion) {911// Check if there exists any pre-release version (ignoring compatibility)912const anyPrereleaseVersion = await this.getValidRawGalleryExtensionVersion(913rawGalleryExtension,914latestExtensionVersionsForTargetPlatform,915{916targetPlatform,917compatible: false,918productVersion: options.productVersion ?? {919version: this.productService.version,920date: this.productService.date921},922version: VersionKind.Prerelease923}, allTargetPlatforms);924925// If no pre-release exists or the release version is greater, prefer the compatible release926// This ensures users get a stable compatible version when pre-releases aren't newer or compatible927if (!anyPrereleaseVersion || semver.gt(releaseVersion.version, anyPrereleaseVersion.version)) {928return releaseVersion;929}930}931return prereleaseVersion;932}933934// Return pre-release if available, otherwise release, otherwise null935return prereleaseVersion ?? releaseVersion ?? null;936}937938async getCompatibleExtension(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<IGalleryExtension | null> {939if (isNotWebExtensionInWebTargetPlatform(extension.allTargetPlatforms, targetPlatform)) {940return null;941}942if (await this.isExtensionCompatible(extension, includePreRelease, targetPlatform)) {943return extension;944}945if (this.allowedExtensionsService.isAllowed({ id: extension.identifier.id, publisherDisplayName: extension.publisherDisplayName }) !== true) {946return null;947}948const result = await this.getExtensions([{949...extension.identifier,950preRelease: includePreRelease,951hasPreRelease: extension.hasPreReleaseVersion,952}], {953compatible: true,954productVersion,955queryAllVersions: true,956targetPlatform,957}, CancellationToken.None);958959return result[0] ?? null;960}961962async isExtensionCompatible(extension: IGalleryExtension, includePreRelease: boolean, targetPlatform: TargetPlatform, productVersion: IProductVersion = { version: this.productService.version, date: this.productService.date }): Promise<boolean> {963return this.isValidVersion(964{965id: extension.identifier.id,966version: extension.version,967isPreReleaseVersion: extension.properties.isPreReleaseVersion,968targetPlatform: extension.properties.targetPlatform,969manifestAsset: extension.assets.manifest,970engine: extension.properties.engine,971enabledApiProposals: extension.properties.enabledApiProposals972},973{974targetPlatform,975compatible: true,976productVersion,977version: includePreRelease ? VersionKind.Latest : VersionKind.Release978},979extension.publisherDisplayName,980extension.allTargetPlatforms981);982}983984private async isValidVersion(985extension: { id: string; version: string; isPreReleaseVersion: boolean; targetPlatform: TargetPlatform; manifestAsset: IGalleryExtensionAsset | null; engine: string | undefined; enabledApiProposals: string[] | undefined },986{ targetPlatform, compatible, productVersion, version }: Omit<ExtensionVersionCriteria, 'targetPlatform'> & { targetPlatform: TargetPlatform | undefined },987publisherDisplayName: string,988allTargetPlatforms: TargetPlatform[]989): Promise<boolean> {990991const hasPreRelease = hasPreReleaseForExtension(extension.id, this.productService);992const excludeVersionRange = getExcludeVersionRangeForExtension(extension.id, this.productService);993994if (extension.isPreReleaseVersion && hasPreRelease === false /* Skip if hasPreRelease is not defined for this extension */) {995return false;996}997998if (excludeVersionRange && semver.satisfies(extension.version, excludeVersionRange)) {999return false;1000}10011002// Specific version1003if (isString(version)) {1004if (extension.version !== version) {1005return false;1006}1007}10081009// Prerelease or release version kind1010else if (version === VersionKind.Release || version === VersionKind.Prerelease) {1011if (extension.isPreReleaseVersion !== (version === VersionKind.Prerelease)) {1012return false;1013}1014}10151016if (targetPlatform && !isTargetPlatformCompatible(extension.targetPlatform, allTargetPlatforms, targetPlatform)) {1017return false;1018}10191020if (compatible) {1021if (this.allowedExtensionsService.isAllowed({ id: extension.id, publisherDisplayName, version: extension.version, prerelease: extension.isPreReleaseVersion, targetPlatform: extension.targetPlatform }) !== true) {1022return false;1023}10241025if (!this.areApiProposalsCompatible(extension.id, extension.enabledApiProposals)) {1026return false;1027}10281029if (!(await this.isEngineValid(extension.id, extension.version, extension.engine, extension.manifestAsset, productVersion))) {1030return false;1031}1032}10331034return true;1035}10361037private areApiProposalsCompatible(extensionId: string, enabledApiProposals: string[] | undefined): boolean {1038if (!enabledApiProposals) {1039return true;1040}1041if (!this.extensionsEnabledWithApiProposalVersion.includes(extensionId.toLowerCase())) {1042return true;1043}1044return areApiProposalsCompatible(enabledApiProposals);1045}10461047private async isEngineValid(extensionId: string, version: string, engine: string | undefined, manifestAsset: IGalleryExtensionAsset | null, productVersion: IProductVersion): Promise<boolean> {1048if (!engine) {1049try {1050engine = await this.getEngine(extensionId, version, manifestAsset);1051} catch (error) {1052this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));1053return false;1054}1055}10561057if (!engine) {1058this.logService.error(`Missing engine for the extension ${extensionId} with version ${version}`);1059return false;1060}10611062return isEngineValid(engine, productVersion.version, productVersion.date);1063}10641065private async getEngine(extensionId: string, version: string, manifestAsset: IGalleryExtensionAsset | null): Promise<string | undefined> {1066if (!manifestAsset) {1067this.logService.error(`Missing engine and manifest asset for the extension ${extensionId} with version ${version}`);1068return undefined;1069}1070try {1071type GalleryServiceEngineFallbackClassification = {1072owner: 'sandy081';1073comment: 'Fallback request when engine is not found in properties of an extension version';1074extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };1075extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };1076};1077type GalleryServiceEngineFallbackEvent = {1078extension: string;1079extensionVersion: string;1080};1081this.telemetryService.publicLog2<GalleryServiceEngineFallbackEvent, GalleryServiceEngineFallbackClassification>('galleryService:engineFallback', { extension: extensionId, extensionVersion: version });10821083const headers = { 'Accept-Encoding': 'gzip' };1084const context = await this.getAsset(extensionId, manifestAsset, AssetType.Manifest, version, { headers });1085const manifest = await asJson<IExtensionManifest>(context);1086if (!manifest) {1087this.logService.error(`Manifest was not found for the extension ${extensionId} with version ${version}`);1088return undefined;1089}1090return manifest.engines.vscode;1091} catch (error) {1092this.logService.error(`Error while getting the engine for the version ${version}.`, getErrorMessage(error));1093return undefined;1094}1095}10961097async query(options: IQueryOptions, token: CancellationToken): Promise<IPager<IGalleryExtension>> {1098const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();10991100if (!extensionGalleryManifest) {1101throw new Error('No extension gallery service configured.');1102}11031104let text = options.text || '';1105const pageSize = options.pageSize ?? 50;11061107let query = new Query()1108.withPage(1, pageSize);11091110if (text) {1111// Use category filter instead of "category:themes"1112text = text.replace(/\bcategory:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedCategory, category) => {1113query = query.withFilter(FilterType.Category, category || quotedCategory);1114return '';1115});11161117// Use tag filter instead of "tag:debuggers"1118text = text.replace(/\btag:("([^"]*)"|([^"]\S*))(\s+|\b|$)/g, (_, quotedTag, tag) => {1119query = query.withFilter(FilterType.Tag, tag || quotedTag);1120return '';1121});11221123// Use featured filter1124text = text.replace(/\bfeatured(\s+|\b|$)/g, () => {1125query = query.withFilter(FilterType.Featured);1126return '';1127});11281129text = text.trim();11301131if (text) {1132text = text.length < 200 ? text : text.substring(0, 200);1133query = query.withFilter(FilterType.SearchText, text);1134}11351136if (extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === SortBy.NoneOrRelevance)) {1137query = query.withSortBy(SortBy.NoneOrRelevance);1138}1139} else {1140if (extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === SortBy.InstallCount)) {1141query = query.withSortBy(SortBy.InstallCount);1142}1143}11441145if (options.sortBy && extensionGalleryManifest.capabilities.extensionQuery.sorting?.some(c => c.name === options.sortBy)) {1146query = query.withSortBy(options.sortBy);1147}11481149if (typeof options.sortOrder === 'number') {1150query = query.withSortOrder(options.sortOrder);1151}11521153if (options.source) {1154query = query.withSource(options.source);1155}11561157const runQuery = async (query: Query, token: CancellationToken) => {1158const { 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);11591160const result: IGalleryExtension[] = [];1161let defaultChatAgentExtension: IGalleryExtension | undefined;1162for (let index = 0; index < extensions.length; index++) {1163const extension = extensions[index];1164setTelemetry(extension, ((query.pageNumber - 1) * query.pageSize) + index, options.source);1165if (areSameExtensions(extension.identifier, { id: this.productService.defaultChatAgent.extensionId, })) {1166defaultChatAgentExtension = extension;1167} else {1168result.push(extension);1169}1170}1171if (defaultChatAgentExtension) {1172result.push(defaultChatAgentExtension);1173}11741175return { extensions: result, total };1176};1177const { extensions, total } = await runQuery(query, token);1178const getPage = async (pageIndex: number, ct: CancellationToken) => {1179if (ct.isCancellationRequested) {1180throw new CancellationError();1181}1182const { extensions } = await runQuery(query.withPage(pageIndex + 1), ct);1183return extensions;1184};11851186return { firstPage: extensions, total, pageSize: query.pageSize, getPage };1187}11881189private async queryGalleryExtensions(query: Query, criteria: ExtensionsCriteria, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<{ extensions: IGalleryExtension[]; total: number }> {1190const flags = query.flags;11911192/**1193* If both version flags (IncludeLatestVersionOnly and IncludeVersions) are included, then only include latest versions (IncludeLatestVersionOnly) flag.1194*/1195if (query.flags.includes(Flag.IncludeLatestVersionOnly) && query.flags.includes(Flag.IncludeVersions)) {1196query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeVersions));1197}11981199/**1200* If version flags (IncludeLatestVersionOnly and IncludeVersions) are not included, default is to query for latest versions (IncludeLatestVersionOnly).1201*/1202if (!query.flags.includes(Flag.IncludeLatestVersionOnly) && !query.flags.includes(Flag.IncludeVersions)) {1203query = query.withFlags(...query.flags, Flag.IncludeLatestVersionOnly);1204}12051206/**1207* 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.1208*/1209if (criteria.versions?.length || criteria.isQueryForReleaseVersionFromPreReleaseVersion) {1210query = query.withFlags(...query.flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeVersions);1211}12121213/**1214* Add necessary extension flags1215*/1216query = query.withFlags(...query.flags, Flag.IncludeAssetUri, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeStatistics, Flag.IncludeVersionProperties);1217const { galleryExtensions: rawGalleryExtensions, total, context } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, token);12181219const hasAllVersions: boolean = !query.flags.includes(Flag.IncludeLatestVersionOnly);1220if (hasAllVersions) {1221const extensions: IGalleryExtension[] = [];1222for (const rawGalleryExtension of rawGalleryExtensions) {1223const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);1224const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };1225const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease;1226const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion(1227rawGalleryExtension,1228rawGalleryExtension.versions,1229{1230compatible: criteria.compatible,1231targetPlatform: criteria.targetPlatform,1232productVersion: criteria.productVersion,1233version: criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version1234?? (includePreRelease ? VersionKind.Latest : VersionKind.Release)1235},1236allTargetPlatforms1237);1238if (rawGalleryExtensionVersion) {1239extensions.push(toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context));1240}1241}1242return { extensions, total };1243}12441245const result: [number, IGalleryExtension][] = [];1246const needAllVersions = new Map<string, number>();1247for (let index = 0; index < rawGalleryExtensions.length; index++) {1248const rawGalleryExtension = rawGalleryExtensions[index];1249const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };1250const includePreRelease = isBoolean(criteria.includePreRelease) ? criteria.includePreRelease : !!criteria.includePreRelease.find(extensionIdentifierWithPreRelease => areSameExtensions(extensionIdentifierWithPreRelease, extensionIdentifier))?.includePreRelease;1251const allTargetPlatforms = getAllTargetPlatforms(rawGalleryExtension);1252if (criteria.compatible) {1253// Skip looking for all versions if requested for a web-compatible extension and it is not a web extension.1254if (isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {1255continue;1256}1257// Skip looking for all versions if the extension is not allowed.1258if (this.allowedExtensionsService.isAllowed({ id: extensionIdentifier.id, publisherDisplayName: rawGalleryExtension.publisher.displayName }) !== true) {1259continue;1260}1261}1262const rawGalleryExtensionVersion = await this.getValidRawGalleryExtensionVersion(1263rawGalleryExtension,1264rawGalleryExtension.versions,1265{1266compatible: criteria.compatible,1267targetPlatform: criteria.targetPlatform,1268productVersion: criteria.productVersion,1269version: criteria.versions?.find(extensionIdentifierWithVersion => areSameExtensions(extensionIdentifierWithVersion, extensionIdentifier))?.version1270?? (includePreRelease ? VersionKind.Latest : VersionKind.Release)1271},1272allTargetPlatforms1273);1274const extension = rawGalleryExtensionVersion ? toExtension(rawGalleryExtension, rawGalleryExtensionVersion, allTargetPlatforms, extensionGalleryManifest, this.productService, context) : null;1275if (!extension1276/** Need all versions if the extension is a pre-release version but1277* - the query is to look for a release version or1278* - the extension has no release version1279* Get all versions to get or check the release version1280*/1281|| (extension.properties.isPreReleaseVersion && (!includePreRelease || !extension.hasReleaseVersion))1282/**1283* Need all versions if the extension is a release version with a different target platform than requested and also has a pre-release version1284* Because, this is a platform specific extension and can have a newer release version supporting this platform.1285* See https://github.com/microsoft/vscode/issues/1396281286*/1287|| (!extension.properties.isPreReleaseVersion && extension.properties.targetPlatform !== criteria.targetPlatform && extension.hasPreReleaseVersion)1288) {1289needAllVersions.set(rawGalleryExtension.extensionId, index);1290} else {1291result.push([index, extension]);1292}1293}12941295if (needAllVersions.size) {1296const stopWatch = new StopWatch();1297const query = new Query()1298.withFlags(...flags.filter(flag => flag !== Flag.IncludeLatestVersionOnly), Flag.IncludeVersions)1299.withPage(1, needAllVersions.size)1300.withFilter(FilterType.ExtensionId, ...needAllVersions.keys());1301const { extensions } = await this.queryGalleryExtensions(query, criteria, extensionGalleryManifest, token);1302this.telemetryService.publicLog2<GalleryServiceAdditionalQueryEvent, GalleryServiceAdditionalQueryClassification>('galleryService:additionalQuery', {1303duration: stopWatch.elapsed(),1304count: needAllVersions.size1305});1306for (const extension of extensions) {1307const index = needAllVersions.get(extension.identifier.uuid)!;1308result.push([index, extension]);1309}1310}13111312return { extensions: result.sort((a, b) => a[0] - b[0]).map(([, extension]) => extension), total };1313}13141315private async getValidRawGalleryExtensionVersion(rawGalleryExtension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[], criteria: ExtensionVersionCriteria, allTargetPlatforms: TargetPlatform[]): Promise<IRawGalleryExtensionVersion | null> {1316const extensionIdentifier = { id: getGalleryExtensionId(rawGalleryExtension.publisher.publisherName, rawGalleryExtension.extensionName), uuid: rawGalleryExtension.extensionId };1317const rawGalleryExtensionVersions = sortExtensionVersions(versions, criteria.targetPlatform);13181319if (criteria.compatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, criteria.targetPlatform)) {1320return null;1321}13221323const version = isString(criteria.version) ? criteria.version : undefined;13241325for (let index = 0; index < rawGalleryExtensionVersions.length; index++) {1326const rawGalleryExtensionVersion = rawGalleryExtensionVersions[index];1327if (criteria.compatible) {1328await this.setEngineIfNotExists(extensionIdentifier.id, rawGalleryExtensionVersion);1329}1330if (await this.isValidVersion(1331{1332id: extensionIdentifier.id,1333version: rawGalleryExtensionVersion.version,1334isPreReleaseVersion: isPreReleaseVersion(rawGalleryExtensionVersion),1335targetPlatform: getTargetPlatformForExtensionVersion(rawGalleryExtensionVersion),1336engine: getEngine(rawGalleryExtensionVersion),1337manifestAsset: getVersionAsset(rawGalleryExtensionVersion, AssetType.Manifest),1338enabledApiProposals: getEnabledApiProposals(rawGalleryExtensionVersion)1339},1340criteria,1341rawGalleryExtension.publisher.displayName,1342allTargetPlatforms)1343) {1344return rawGalleryExtensionVersion;1345}1346if (version && rawGalleryExtensionVersion.version === version) {1347return null;1348}1349}13501351if (version || criteria.compatible) {1352return null;1353}13541355/**1356* Fallback: Return the latest version1357* This can happen when the extension does not have a release version or does not have a version compatible with the given target platform.1358*/1359return rawGalleryExtension.versions[0];1360}13611362private async setEngineIfNotExists(extensionId: string, rawGalleryExtensionVersion: IRawGalleryExtensionVersion): Promise<void> {1363if (getEngine(rawGalleryExtensionVersion)) {1364return;1365}13661367try {1368const engine = await this.getEngine(extensionId, rawGalleryExtensionVersion.version, getVersionAsset(rawGalleryExtensionVersion, AssetType.Manifest));1369if (engine) {1370setEngine(rawGalleryExtensionVersion, engine);1371}1372} catch (error) {1373this.logService.error(`Error while getting the engine for the version ${rawGalleryExtensionVersion.version}.`, getErrorMessage(error));1374}1375}13761377private async queryRawGalleryExtensions(query: Query, extensionGalleryManifest: IExtensionGalleryManifest, token: CancellationToken): Promise<IRawGalleryExtensionsResult> {1378const extensionsQueryApi = getExtensionGalleryManifestResourceUri(extensionGalleryManifest, ExtensionGalleryResourceType.ExtensionQueryService);13791380if (!extensionsQueryApi) {1381throw new Error('No extension gallery query service configured.');1382}13831384query = query1385/* Always exclude non validated extensions */1386.withFlags(...query.flags, Flag.ExcludeNonValidated)1387.withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code');13881389const unpublishedFlag = extensionGalleryManifest.capabilities.extensionQuery.flags?.find(f => f.name === Flag.Unpublished);1390/* Always exclude unpublished extensions */1391if (unpublishedFlag) {1392query = query.withFilter(FilterType.ExcludeWithFlags, String(unpublishedFlag.value));1393}13941395const data = JSON.stringify({1396filters: [1397{1398criteria: query.criteria.reduce<{ filterType: number; value?: string }[]>((criteria, c) => {1399const criterium = extensionGalleryManifest.capabilities.extensionQuery.filtering?.find(f => f.name === c.filterType);1400if (criterium) {1401criteria.push({1402filterType: criterium.value,1403value: c.value,1404});1405}1406return criteria;1407}, []),1408pageNumber: query.pageNumber,1409pageSize: query.pageSize,1410sortBy: extensionGalleryManifest.capabilities.extensionQuery.sorting?.find(s => s.name === query.sortBy)?.value,1411sortOrder: query.sortOrder,1412}1413],1414assetTypes: query.assetTypes,1415flags: query.flags.reduce<number>((flags, flag) => {1416const flagValue = extensionGalleryManifest.capabilities.extensionQuery.flags?.find(f => f.name === flag);1417if (flagValue) {1418flags |= flagValue.value;1419}1420return flags;1421}, 0)1422});14231424const commonHeaders = await this.commonHeadersPromise;1425const headers = {1426...commonHeaders,1427'Content-Type': 'application/json',1428'Accept': 'application/json;api-version=3.0-preview.1',1429'Accept-Encoding': 'gzip',1430'Content-Length': String(data.length),1431};14321433const stopWatch = new StopWatch();1434let context: IRequestContext | undefined, errorCode: ExtensionGalleryErrorCode | undefined, total: number = 0;14351436try {1437context = await this.requestService.request({1438type: 'POST',1439url: extensionsQueryApi,1440data,1441headers1442}, token);14431444if (context.res.statusCode && context.res.statusCode >= 400 && context.res.statusCode < 500) {1445return { galleryExtensions: [], total };1446}14471448const result = await asJson<IRawGalleryQueryResult>(context);1449if (result) {1450const r = result.results[0];1451const galleryExtensions = r.extensions;1452const resultCount = r.resultMetadata && r.resultMetadata.filter(m => m.metadataType === 'ResultCount')[0];1453total = resultCount && resultCount.metadataItems.filter(i => i.name === 'TotalCount')[0].count || 0;14541455return {1456galleryExtensions,1457total,1458context: context.res.headers['activityid'] ? {1459[SEARCH_ACTIVITY_HEADER_NAME]: context.res.headers['activityid']1460} : {}1461};1462}1463return { galleryExtensions: [], total };14641465} catch (e) {1466if (isCancellationError(e)) {1467errorCode = ExtensionGalleryErrorCode.Cancelled;1468throw e;1469} else {1470const errorMessage = getErrorMessage(e);1471errorCode = isOfflineError(e)1472? ExtensionGalleryErrorCode.Offline1473: errorMessage.startsWith('XHR timeout')1474? ExtensionGalleryErrorCode.Timeout1475: ExtensionGalleryErrorCode.Failed;1476throw new ExtensionGalleryError(errorMessage, errorCode);1477}1478} finally {1479this.telemetryService.publicLog2<GalleryServiceQueryEvent, GalleryServiceQueryClassification>('galleryService:query', {1480filterTypes: query.criteria.map(criterium => criterium.filterType),1481flags: query.flags,1482sortBy: query.sortBy,1483sortOrder: String(query.sortOrder),1484pageNumber: String(query.pageNumber),1485source: query.source,1486searchTextLength: query.searchText.length,1487requestBodySize: String(data.length),1488duration: stopWatch.elapsed(),1489success: !!context && isSuccess(context),1490responseBodySize: context?.res.headers['Content-Length'],1491statusCode: context ? String(context.res.statusCode) : undefined,1492errorCode,1493count: String(total),1494server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),1495activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),1496endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),1497});1498}1499}15001501private getHeaderValue(headers: IHeaders | undefined, name: string): TelemetryTrustedValue<string> | undefined {1502const headerValue = headers?.[name.toLowerCase()];1503const value = Array.isArray(headerValue) ? headerValue[0] : headerValue;1504return value ? new TelemetryTrustedValue(value) : undefined;1505}15061507private async getLatestRawGalleryExtensionWithFallback(extensionInfo: IExtensionInfo, resourceApi: { uri: string; fallback?: string }, token: CancellationToken): Promise<IRawGalleryExtension | null> {1508const [publisher, name] = extensionInfo.id.split('.');1509let errorCode: string | undefined;1510try {1511const uri = URI.parse(format2(resourceApi.uri, { publisher, name }));1512return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);1513} catch (error) {1514if (error instanceof ExtensionGalleryError) {1515errorCode = error.code;1516switch (error.code) {1517case ExtensionGalleryErrorCode.Offline:1518case ExtensionGalleryErrorCode.Cancelled:1519case ExtensionGalleryErrorCode.Timeout:1520case ExtensionGalleryErrorCode.ClientError:1521throw error;1522}1523} else {1524errorCode = 'Unknown';1525}1526if (!resourceApi.fallback) {1527throw error;1528}1529} finally {1530this.telemetryService.publicLog2<1531{1532extension: string;1533errorCode?: string;1534},1535{1536owner: 'sandy081';1537comment: 'Report fetching latest version of an extension';1538extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };1539errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };1540}1541>('galleryService:getmarketplacelatest', {1542extension: extensionInfo.id,1543errorCode,1544});1545}15461547this.logService.error(`Error while getting the latest version for the extension ${extensionInfo.id} from ${resourceApi.uri}. Trying the fallback ${resourceApi.fallback}`, errorCode);1548try {1549const uri = URI.parse(format2(resourceApi.fallback, { publisher, name }));1550return await this.getLatestRawGalleryExtension(extensionInfo.id, uri, token);1551} catch (error) {1552errorCode = error instanceof ExtensionGalleryError ? error.code : 'Unknown';1553throw error;1554} finally {1555this.telemetryService.publicLog2<1556{1557extension: string;1558errorCode?: string;1559},1560{1561owner: 'sandy081';1562comment: 'Report the fallback to the unpkg service for getting latest extension';1563extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Extension id' };1564errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };1565}>('galleryService:fallbacktounpkg', {1566extension: extensionInfo.id,1567errorCode,1568});1569}1570}15711572private async getLatestRawGalleryExtension(extension: string, uri: URI, token: CancellationToken): Promise<IRawGalleryExtension | null> {1573let context;1574let errorCode: string | undefined;1575const stopWatch = new StopWatch();15761577try {1578const commonHeaders = await this.commonHeadersPromise;1579const headers = {1580...commonHeaders,1581'Content-Type': 'application/json',1582'Accept': 'application/json;api-version=7.2-preview',1583'Accept-Encoding': 'gzip',1584};15851586context = await this.requestService.request({1587type: 'GET',1588url: uri.toString(true),1589headers,1590timeout: this.getRequestTimeout()1591}, token);15921593if (context.res.statusCode === 404) {1594errorCode = 'NotFound';1595return null;1596}15971598if (context.res.statusCode && context.res.statusCode !== 200) {1599throw new Error('Unexpected HTTP response: ' + context.res.statusCode);1600}16011602const result = await asJson<IRawGalleryExtension>(context);1603if (!result) {1604errorCode = 'NoData';1605}1606return result;1607}16081609catch (error) {1610let galleryErrorCode: ExtensionGalleryErrorCode;1611if (isCancellationError(error)) {1612galleryErrorCode = ExtensionGalleryErrorCode.Cancelled;1613} else if (isOfflineError(error)) {1614galleryErrorCode = ExtensionGalleryErrorCode.Offline;1615} else if (getErrorMessage(error).startsWith('XHR timeout')) {1616galleryErrorCode = ExtensionGalleryErrorCode.Timeout;1617} else if (context && isClientError(context)) {1618galleryErrorCode = ExtensionGalleryErrorCode.ClientError;1619} else if (context && isServerError(context)) {1620galleryErrorCode = ExtensionGalleryErrorCode.ServerError;1621} else {1622galleryErrorCode = ExtensionGalleryErrorCode.Failed;1623}1624errorCode = galleryErrorCode;1625throw new ExtensionGalleryError(error, galleryErrorCode);1626}16271628finally {1629type GalleryServiceGetLatestEventClassification = {1630owner: 'sandy081';1631comment: 'Report the query to the Marketplace for fetching latest version of an extension';1632host: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The host of the end point' };1633extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The identifier of the extension' };1634duration: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; isMeasurement: true; comment: 'Duration in ms for the query' };1635errorCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The error code in case of error' };1636statusCode?: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The status code in case of error' };1637server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The server of the end point' };1638activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The activity ID of the request' };1639endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The end-to-end ID of the request' };1640};1641type GalleryServiceGetLatestEvent = {1642extension: string;1643host: string;1644duration: number;1645errorCode?: string;1646statusCode?: string;1647server?: TelemetryTrustedValue<string>;1648activityId?: TelemetryTrustedValue<string>;1649endToEndId?: TelemetryTrustedValue<string>;1650};1651this.telemetryService.publicLog2<GalleryServiceGetLatestEvent, GalleryServiceGetLatestEventClassification>('galleryService:getLatest', {1652extension,1653host: uri.authority,1654duration: stopWatch.elapsed(),1655errorCode,1656statusCode: context?.res.statusCode && context?.res.statusCode !== 200 ? `${context.res.statusCode}` : undefined,1657server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),1658activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),1659endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),1660});1661}1662}16631664async reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise<void> {1665if (isWeb) {1666this.logService.info('ExtensionGalleryService#reportStatistic: Skipped in web');1667return undefined;1668}16691670const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();1671if (!manifest) {1672return undefined;1673}16741675const resource = getExtensionGalleryManifestResourceUri(manifest, ExtensionGalleryResourceType.ExtensionStatisticsUri);1676if (!resource) {1677return;1678}1679const url = format2(resource, { publisher, name, version, statTypeName: type });16801681const Accept = '*/*;api-version=4.0-preview.1';1682const commonHeaders = await this.commonHeadersPromise;1683const headers = { ...commonHeaders, Accept };1684try {1685await this.requestService.request({1686type: 'POST',1687url,1688headers1689}, CancellationToken.None);1690} catch (error) { /* Ignore */ }1691}16921693async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {1694this.logService.trace('ExtensionGalleryService#download', extension.identifier.id);1695const data = getGalleryExtensionTelemetryData(extension);1696const startTime = new Date().getTime();16971698const operationParam = operation === InstallOperation.Install ? 'install' : operation === InstallOperation.Update ? 'update' : '';1699const downloadAsset = operationParam ? {1700uri: `${extension.assets.download.uri}${URI.parse(extension.assets.download.uri).query ? '&' : '?'}${operationParam}=true`,1701fallbackUri: `${extension.assets.download.fallbackUri}${URI.parse(extension.assets.download.fallbackUri).query ? '&' : '?'}${operationParam}=true`1702} : extension.assets.download;17031704const activityId = extension.queryContext?.[SEARCH_ACTIVITY_HEADER_NAME];1705const headers: IHeaders | undefined = activityId && typeof activityId === 'string' ? { [SEARCH_ACTIVITY_HEADER_NAME]: activityId } : undefined;1706const context = await this.getAsset(extension.identifier.id, downloadAsset, AssetType.VSIX, extension.version, headers ? { headers } : undefined);17071708try {1709await this.fileService.writeFile(location, context.stream);1710} catch (error) {1711try {1712await this.fileService.del(location);1713} catch (e) {1714/* ignore */1715this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e));1716}1717throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting);1718}17191720/* __GDPR__1721"galleryService:downloadVSIX" : {1722"owner": "sandy081",1723"duration": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },1724"${include}": [1725"${GalleryExtensionTelemetryData}"1726]1727}1728*/1729this.telemetryService.publicLog('galleryService:downloadVSIX', { ...data, duration: new Date().getTime() - startTime });1730}17311732async downloadSignatureArchive(extension: IGalleryExtension, location: URI): Promise<void> {1733if (!extension.assets.signature) {1734throw new Error('No signature asset found');1735}17361737this.logService.trace('ExtensionGalleryService#downloadSignatureArchive', extension.identifier.id);17381739const context = await this.getAsset(extension.identifier.id, extension.assets.signature, AssetType.Signature, extension.version);1740try {1741await this.fileService.writeFile(location, context.stream);1742} catch (error) {1743try {1744await this.fileService.del(location);1745} catch (e) {1746/* ignore */1747this.logService.warn(`Error while deleting the file ${location.toString()}`, getErrorMessage(e));1748}1749throw new ExtensionGalleryError(getErrorMessage(error), ExtensionGalleryErrorCode.DownloadFailedWriting);1750}17511752}17531754async getReadme(extension: IGalleryExtension, token: CancellationToken): Promise<string> {1755if (extension.assets.readme) {1756const context = await this.getAsset(extension.identifier.id, extension.assets.readme, AssetType.Details, extension.version, {}, token);1757const content = await asTextOrError(context);1758return content || '';1759}1760return '';1761}17621763async getManifest(extension: IGalleryExtension, token: CancellationToken): Promise<IExtensionManifest | null> {1764if (extension.assets.manifest) {1765const context = await this.getAsset(extension.identifier.id, extension.assets.manifest, AssetType.Manifest, extension.version, {}, token);1766const text = await asTextOrError(context);1767return text ? JSON.parse(text) : null;1768}1769return null;1770}17711772async getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise<ITranslation | null> {1773const asset = extension.assets.coreTranslations.filter(t => t[0] === languageId.toUpperCase())[0];1774if (asset) {1775const context = await this.getAsset(extension.identifier.id, asset[1], asset[0], extension.version);1776const text = await asTextOrError(context);1777return text ? JSON.parse(text) : null;1778}1779return null;1780}17811782async getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise<string> {1783if (extension.assets.changelog) {1784const context = await this.getAsset(extension.identifier.id, extension.assets.changelog, AssetType.Changelog, extension.version, {}, token);1785const content = await asTextOrError(context);1786return content || '';1787}1788return '';1789}17901791async getAllVersions(extensionIdentifier: IExtensionIdentifier): Promise<IGalleryExtensionVersion[]> {1792return this.getVersions(extensionIdentifier);1793}17941795async getAllCompatibleVersions(extensionIdentifier: IExtensionIdentifier, includePreRelease: boolean, targetPlatform: TargetPlatform): Promise<IGalleryExtensionVersion[]> {1796return this.getVersions(extensionIdentifier, { version: includePreRelease ? VersionKind.Latest : VersionKind.Release, targetPlatform });1797}17981799private async getVersions(extensionIdentifier: IExtensionIdentifier, onlyCompatible?: { version: VersionKind; targetPlatform: TargetPlatform }): Promise<IGalleryExtensionVersion[]> {1800const extensionGalleryManifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();1801if (!extensionGalleryManifest) {1802throw new Error('No extension gallery service configured.');1803}18041805let query = new Query()1806.withFlags(Flag.IncludeVersions, Flag.IncludeCategoryAndTags, Flag.IncludeFiles, Flag.IncludeVersionProperties)1807.withPage(1, 1);18081809if (extensionIdentifier.uuid) {1810query = query.withFilter(FilterType.ExtensionId, extensionIdentifier.uuid);1811} else {1812query = query.withFilter(FilterType.ExtensionName, extensionIdentifier.id);1813}18141815const { galleryExtensions } = await this.queryRawGalleryExtensions(query, extensionGalleryManifest, CancellationToken.None);1816if (!galleryExtensions.length) {1817return [];1818}18191820const allTargetPlatforms = getAllTargetPlatforms(galleryExtensions[0]);1821if (onlyCompatible && isNotWebExtensionInWebTargetPlatform(allTargetPlatforms, onlyCompatible.targetPlatform)) {1822return [];1823}18241825const versions: IRawGalleryExtensionVersion[] = [];1826const productVersion = { version: this.productService.version, date: this.productService.date };1827await Promise.all(galleryExtensions[0].versions.map(async (version) => {1828try {1829if (1830(await this.isValidVersion(1831{1832id: extensionIdentifier.id,1833version: version.version,1834isPreReleaseVersion: isPreReleaseVersion(version),1835targetPlatform: getTargetPlatformForExtensionVersion(version),1836engine: getEngine(version),1837manifestAsset: getVersionAsset(version, AssetType.Manifest),1838enabledApiProposals: getEnabledApiProposals(version)1839},1840{1841compatible: !!onlyCompatible,1842productVersion,1843targetPlatform: onlyCompatible?.targetPlatform,1844version: onlyCompatible?.version ?? version.version1845},1846galleryExtensions[0].publisher.displayName,1847allTargetPlatforms))1848) {1849versions.push(version);1850}1851} catch (error) { /* Ignore error and skip version */ }1852}));18531854const result: IGalleryExtensionVersion[] = [];1855const seen = new Map<string, number>();1856for (const version of sortExtensionVersions(versions, onlyCompatible?.targetPlatform ?? CURRENT_TARGET_PLATFORM)) {1857const index = seen.get(version.version);1858const existing = index !== undefined ? result[index] : undefined;1859const targetPlatform = getTargetPlatformForExtensionVersion(version);1860if (!existing) {1861seen.set(version.version, result.length);1862result.push({ version: version.version, date: version.lastUpdated, isPreReleaseVersion: isPreReleaseVersion(version), targetPlatforms: [targetPlatform] });1863} else {1864existing.targetPlatforms.push(targetPlatform);1865}1866}18671868return result;1869}18701871private async getAsset(extension: string, asset: IGalleryExtensionAsset, assetType: string, extensionVersion: string, options: IRequestOptions = {}, token: CancellationToken = CancellationToken.None): Promise<IRequestContext> {1872const commonHeaders = await this.commonHeadersPromise;1873const baseOptions = { type: 'GET' };1874const headers = { ...commonHeaders, ...(options.headers || {}) };1875options = { ...options, ...baseOptions, headers };18761877const url = asset.uri;1878const fallbackUrl = asset.fallbackUri;1879const firstOptions = { ...options, url, timeout: this.getRequestTimeout() };18801881let context;1882try {1883context = await this.requestService.request(firstOptions, token);1884if (context.res.statusCode === 200) {1885return context;1886}1887const message = await asTextOrError(context);1888throw new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`);1889} catch (err) {1890if (isCancellationError(err)) {1891throw err;1892}18931894const message = getErrorMessage(err);1895type GalleryServiceCDNFallbackClassification = {1896owner: 'sandy081';1897comment: 'Fallback request information when the primary asset request to CDN fails';1898extension: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'extension name' };1899assetType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'asset that failed' };1900message: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'error message' };1901extensionVersion: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'version' };1902readonly server?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'server that handled the query' };1903readonly endToEndId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'end to end operation id' };1904readonly activityId?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'activity id' };1905};1906type GalleryServiceCDNFallbackEvent = {1907extension: string;1908assetType: string;1909message: string;1910extensionVersion: string;1911server?: TelemetryTrustedValue<string>;1912endToEndId?: TelemetryTrustedValue<string>;1913activityId?: TelemetryTrustedValue<string>;1914};1915this.telemetryService.publicLog2<GalleryServiceCDNFallbackEvent, GalleryServiceCDNFallbackClassification>('galleryService:cdnFallback', {1916extension,1917assetType,1918message,1919extensionVersion,1920server: this.getHeaderValue(context?.res.headers, SERVER_HEADER_NAME),1921activityId: this.getHeaderValue(context?.res.headers, ACTIVITY_HEADER_NAME),1922endToEndId: this.getHeaderValue(context?.res.headers, END_END_ID_HEADER_NAME),1923});19241925const fallbackOptions = { ...options, url: fallbackUrl, timeout: this.getRequestTimeout() };1926return this.requestService.request(fallbackOptions, token);1927}1928}19291930async getExtensionsControlManifest(): Promise<IExtensionsControlManifest> {1931const manifest = await this.extensionGalleryManifestService.getExtensionGalleryManifest();1932if (!manifest) {1933throw new Error('No extension gallery service configured.');1934}193519361937if (!this.extensionsControlUrl) {1938return { malicious: [], deprecated: {}, search: [], autoUpdate: {} };1939}19401941const context = await this.requestService.request({1942type: 'GET',1943url: this.extensionsControlUrl,1944timeout: this.getRequestTimeout()1945}, CancellationToken.None);19461947if (context.res.statusCode !== 200) {1948throw new Error('Could not get extensions report.');1949}19501951const result = await asJson<IRawExtensionsControlManifest>(context);1952const malicious: Array<MaliciousExtensionInfo> = [];1953const deprecated: IStringDictionary<IDeprecationInfo> = {};1954const search: ISearchPrefferedResults[] = [];1955const autoUpdate: IStringDictionary<string> = result?.autoUpdate ?? {};1956if (result) {1957for (const id of result.malicious) {1958if (!isString(id)) {1959continue;1960}1961const publisherOrExtension = EXTENSION_IDENTIFIER_REGEX.test(id) ? { id } : id;1962malicious.push({ extensionOrPublisher: publisherOrExtension, learnMoreLink: result.learnMoreLinks?.[id] });1963}1964if (result.migrateToPreRelease) {1965for (const [unsupportedPreReleaseExtensionId, preReleaseExtensionInfo] of Object.entries(result.migrateToPreRelease)) {1966if (!preReleaseExtensionInfo.engine || isEngineValid(preReleaseExtensionInfo.engine, this.productService.version, this.productService.date)) {1967deprecated[unsupportedPreReleaseExtensionId.toLowerCase()] = {1968disallowInstall: true,1969extension: {1970id: preReleaseExtensionInfo.id,1971displayName: preReleaseExtensionInfo.displayName,1972autoMigrate: { storage: !!preReleaseExtensionInfo.migrateStorage },1973preRelease: true1974}1975};1976}1977}1978}1979if (result.deprecated) {1980for (const [deprecatedExtensionId, deprecationInfo] of Object.entries(result.deprecated)) {1981if (deprecationInfo) {1982deprecated[deprecatedExtensionId.toLowerCase()] = isBoolean(deprecationInfo) ? {} : deprecationInfo;1983}1984}1985}1986if (result.search) {1987for (const s of result.search) {1988search.push(s);1989}1990}1991}19921993deprecated[this.productService.defaultChatAgent.extensionId.toLowerCase()] = {1994disallowInstall: true,1995extension: {1996id: this.productService.defaultChatAgent.chatExtensionId,1997displayName: 'GitHub Copilot Chat',1998autoMigrate: { storage: false, donotDisable: true },1999preRelease: this.productService.quality !== 'stable'2000}2001};20022003return { malicious, deprecated, search, autoUpdate };2004}20052006private getRequestTimeout(): number {2007const configuredTimeout = this.configurationService.getValue<number>(ExtensionRequestsTimeoutConfigKey);2008return isNumber(configuredTimeout) && configuredTimeout >= 0 ? configuredTimeout : 60_000;2009}20102011}20122013export class ExtensionGalleryService extends AbstractExtensionGalleryService {20142015constructor(2016@IStorageService storageService: IStorageService,2017@IRequestService requestService: IRequestService,2018@ILogService logService: ILogService,2019@IEnvironmentService environmentService: IEnvironmentService,2020@ITelemetryService telemetryService: ITelemetryService,2021@IFileService fileService: IFileService,2022@IProductService productService: IProductService,2023@IConfigurationService configurationService: IConfigurationService,2024@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,2025@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,2026) {2027super(storageService, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService);2028}2029}20302031export class ExtensionGalleryServiceWithNoStorageService extends AbstractExtensionGalleryService {20322033constructor(2034@IRequestService requestService: IRequestService,2035@ILogService logService: ILogService,2036@IEnvironmentService environmentService: IEnvironmentService,2037@ITelemetryService telemetryService: ITelemetryService,2038@IFileService fileService: IFileService,2039@IProductService productService: IProductService,2040@IConfigurationService configurationService: IConfigurationService,2041@IAllowedExtensionsService allowedExtensionsService: IAllowedExtensionsService,2042@IExtensionGalleryManifestService extensionGalleryManifestService: IExtensionGalleryManifestService,2043) {2044super(undefined, requestService, logService, environmentService, telemetryService, fileService, productService, configurationService, allowedExtensionsService, extensionGalleryManifestService);2045}2046}204720482049