| File: LegacyFeed\LegacyFeedCapabilityResourceV2Feed.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Protocol\NuGet.Protocol.csproj (NuGet.Protocol) |
// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. #nullable disable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; using NuGet.Common; using NuGet.Protocol.Core.Types; namespace NuGet.Protocol { /// <summary> /// A resource for detecting a V2 feed's capabilities based on the metadata document. /// </summary> public class LegacyFeedCapabilityResourceV2Feed : LegacyFeedCapabilityResource { private static readonly ConcurrentDictionary<string, Task<Capabilities>> CachedCapabilities = new ConcurrentDictionary<string, Task<Capabilities>>(); private const string MetadataUriFormat = "{0}/$metadata"; private readonly string _metadataUri; private readonly V2FeedParser _feedParser; public LegacyFeedCapabilityResourceV2Feed(V2FeedParser feedParser, string baseAddress) { _feedParser = feedParser; _metadataUri = string.Format( CultureInfo.InvariantCulture, MetadataUriFormat, baseAddress); } public override async Task<bool> SupportsIsAbsoluteLatestVersionAsync(ILogger log, CancellationToken token) { var capabilities = await GetCachedCapabilitiesAsync(log, token); return capabilities.SupportsIsAbsoluteLatestVersion; } public override async Task<bool> SupportsSearchAsync(ILogger log, CancellationToken token) { var capabilities = await GetCachedCapabilitiesAsync(log, token); return capabilities.SupportsSearch; } private async Task<Capabilities> GetCachedCapabilitiesAsync(ILogger log, CancellationToken token) { var task = CachedCapabilities.GetOrAdd( _metadataUri, key => GetCapabilitiesAsync(key, log, token)); return await task; } private async Task<Capabilities> GetCapabilitiesAsync(string metadataUri, ILogger log, CancellationToken token) { var capabilities = new Capabilities { SupportsIsAbsoluteLatestVersion = true, SupportsSearch = true }; XDocument document; try { document = await _feedParser.LoadXmlAsync( metadataUri, cacheKey: null, ignoreNotFounds: true, sourceCacheContext: null, log: log, token: token); var metadata = DataServiceMetadataExtractor.Extract(document); capabilities.SupportsIsAbsoluteLatestVersion = metadata .SupportedProperties .Contains("IsAbsoluteLatestVersion"); capabilities.SupportsSearch = metadata .SupportedMethodNames .Contains("Search"); } catch { // If there is a failure getting the metadata, assume default capabilities. } return capabilities; } private class Capabilities { public string MetadataUri { get; set; } public bool SupportsIsAbsoluteLatestVersion { get; set; } public bool SupportsSearch { get; set; } } private class DataServiceMetadata { public HashSet<string> SupportedMethodNames { get; set; } public HashSet<string> SupportedProperties { get; set; } } /// <summary> /// This implementation is copied from NuGet 2.x. /// </summary> private static class DataServiceMetadataExtractor { public static DataServiceMetadata Extract(XDocument schemaDocument) { // Get all entity containers var entityContainers = from e in schemaDocument.Descendants() where e.Name.LocalName == "EntityContainer" select e; // Find the entity container with the Packages entity set var result = (from e in entityContainers let entitySet = e.Elements().FirstOrDefault(el => el.Name.LocalName == "EntitySet") let name = entitySet != null ? entitySet.Attribute("Name").Value : null where name != null && name.Equals("Packages", StringComparison.OrdinalIgnoreCase) select new { Container = e, EntitySet = entitySet }).FirstOrDefault(); if (result == null) { return null; } var packageEntityContainer = result.Container; var packageEntityTypeAttribute = result.EntitySet.Attribute("EntityType"); string packageEntityName = null; if (packageEntityTypeAttribute != null) { packageEntityName = packageEntityTypeAttribute.Value; } var methodNames = from e in packageEntityContainer.Elements() where e.Name.LocalName == "FunctionImport" select e.Attribute("Name").Value; var metadata = new DataServiceMetadata { SupportedMethodNames = new HashSet<string>( methodNames, StringComparer.OrdinalIgnoreCase), SupportedProperties = new HashSet<string>( ExtractSupportedProperties(schemaDocument, packageEntityName), StringComparer.OrdinalIgnoreCase) }; return metadata; } private static IEnumerable<string> ExtractSupportedProperties( XDocument schemaDocument, string packageEntityName) { packageEntityName = TrimNamespace(packageEntityName); var packageEntity = (from e in schemaDocument.Descendants() where e.Name.LocalName == "EntityType" let attribute = e.Attribute("Name") where attribute != null && attribute.Value.Equals( packageEntityName, StringComparison.OrdinalIgnoreCase) select e).FirstOrDefault(); if (packageEntity != null) { return from e in packageEntity.Elements() where e.Name.LocalName == "Property" select e.Attribute("Name").Value; } return Enumerable.Empty<string>(); } private static string TrimNamespace(string packageEntityName) { var lastIndex = packageEntityName.LastIndexOf('.'); if (lastIndex > 0 && lastIndex < packageEntityName.Length) { packageEntityName = packageEntityName.Substring(lastIndex + 1); } return packageEntityName; } } } }