File: Model\LocalPackageSearchMetadata.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.

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NuGet.Common;
using NuGet.Packaging;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;

namespace NuGet.Protocol
{
    public class LocalPackageSearchMetadata : IPackageSearchMetadata
    {
        private readonly NuspecReader _nuspec;
        private readonly LocalPackageInfo _package;

        public LocalPackageSearchMetadata(LocalPackageInfo package)
        {
            _package = package ?? throw new ArgumentNullException(nameof(package));
            _nuspec = package.Nuspec;
        }

        public string? Authors => _nuspec.GetAuthors();

        public IEnumerable<PackageDependencyGroup> DependencySets => _nuspec.GetDependencyGroups().ToArray();

        public string? Description => _nuspec.GetDescription();

        /// <remarks>
        /// Local package sources never provide a download count.
        /// </remarks>
        public long? DownloadCount => null;

        public Uri? IconUrl => GetIconUri();

        public string? ReadmeFileUrl => GetReadmeUri();

        public PackageIdentity Identity => _nuspec.GetIdentity();

        public Uri? LicenseUrl => Convert(_nuspec.GetLicenseUrl());

        private IReadOnlyList<string>? _ownersList;

        public IReadOnlyList<string>? OwnersList
        {
            get
            {
                if (_ownersList is null)
                {
                    _ownersList = Owners != null ? Owners.Split(',').Where(s => !string.IsNullOrWhiteSpace(s)).Select(s => s.Trim()).ToList() : null;
                }

                return _ownersList;
            }
        }

        public string? Owners => _nuspec.GetOwners();

        public Uri? ProjectUrl => Convert(_nuspec.GetProjectUrl());

        public DateTimeOffset? Published => _package.LastWriteTimeUtc;

        /// <remarks>
        /// There is no readme url for local packages. Later releases may display the readme file in a VS window.
        /// </remarks>
        public Uri? ReadmeUrl => null;

        /// <remarks>
        /// There is no report abuse url for local packages.
        /// </remarks>
        public Uri? ReportAbuseUrl => null;

        public Uri? PackageDetailsUrl => null;

        public bool RequireLicenseAcceptance => _nuspec.GetRequireLicenseAcceptance();

        public string? Summary => !string.IsNullOrEmpty(_nuspec.GetSummary()) ? _nuspec.GetSummary() : Description;

        public string Tags
        {
            get
            {
                var tags = _nuspec.GetTags()?.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
                return string.Join(" ", tags);
            }
        }

        public string Title => !string.IsNullOrEmpty(_nuspec.GetTitle()) ? _nuspec.GetTitle()! : _nuspec.GetId();

        /// <summary>
        /// Gets a function that provides <c>PackageReaderBase</c>-like objects, for reading package content
        /// </summary>
        /// <remarks>
        /// This property depends from the reader contained in the <c>LocalPackageInfo</c> specified at object creation time
        /// </remarks>
        public Func<PackageReaderBase> PackageReader
        {
            get
            {
                return new Func<PackageReaderBase>(() => _package.GetReader());
            }
        }

        /// <inheritdoc cref="IPackageSearchMetadata.GetVersionsAsync" />
        public Task<IEnumerable<VersionInfo>> GetVersionsAsync() => TaskResult.EmptyEnumerable<VersionInfo>();

        /// <summary>
        /// Convert a string to a URI safely. This will return null if there are errors.
        /// </summary>
        private static Uri? Convert(string? uri)
        {
            Uri? fullUri = null;

            if (!string.IsNullOrEmpty(uri))
            {
                Uri.TryCreate(uri, UriKind.Absolute, out fullUri);
            }

            return fullUri;
        }

        public bool IsListed => true;

        /// <remarks>
        /// The prefix reservation is not applicable to local packages
        /// </remarks>
        public bool PrefixReserved => false;

        public LicenseMetadata? LicenseMetadata => _nuspec.GetLicenseMetadata();

        /// <inheritdoc cref="IPackageSearchMetadata.GetDeprecationMetadataAsync" />
        public Task<PackageDeprecationMetadata?> GetDeprecationMetadataAsync() => TaskResult.Null<PackageDeprecationMetadata>();

        /// <inheritdoc cref="IPackageSearchMetadata.Vulnerabilities" />
        public IEnumerable<PackageVulnerabilityMetadata>? Vulnerabilities => null;

        public string PackagePath => _package.Path;

        private const int FiveMegabytes = 5242880; // 1024 * 1024 * 5, 5MB

        public string LoadFileAsText(string path)
        {
            string? fileContent = null;
            try
            {
                if (_package.GetReader() is PackageArchiveReader reader) // This will never be anything else in reality. The search resource always uses a PAR
                {
                    var entry = reader.GetEntry(PathUtility.StripLeadingDirectorySeparators(path));
                    if (entry != null)
                    {
                        if (entry.Length >= FiveMegabytes)
                        {
                            fileContent = string.Format(CultureInfo.CurrentCulture, Strings.LoadFileFromNupkg_FileTooLarge, path, "5");
                        }
                        else
                        {
                            using (var licenseStream = entry.Open())
                            using (TextReader textReader = new StreamReader(licenseStream))
                            {
                                fileContent = textReader.ReadToEnd();
                            }
                        }
                    }
                    else
                    {
                        fileContent = string.Format(CultureInfo.CurrentCulture, Strings.LoadFileFromNupkg_FileNotFound, path);
                    }
                }
            }
            catch
            {
                fileContent = string.Format(CultureInfo.CurrentCulture, Strings.LoadFileFromNupkg_UnknownProblemLoadingTheFile, path);
            }
            finally
            {
                if (fileContent == null)
                {
                    fileContent = string.Format(CultureInfo.CurrentCulture, Strings.LoadFileFromNupkg_UnknownProblemLoadingTheFile, path);
                }
            }
            return fileContent;
        }

        /// <summary>
        /// Points to an Icon, either Embedded Icon or IconUrl
        /// </summary>
        private Uri? GetIconUri()
        {
            string? embeddedIcon = _nuspec.GetIcon();

            if (embeddedIcon == null)
            {
                return Convert(_nuspec.GetIconUrl());
            }

            var baseUri = Convert(_package.Path)!;

            UriBuilder builder = new UriBuilder(baseUri)
            {
                Fragment = embeddedIcon
            };

            // get the special icon url
            return builder.Uri;
        }

        private string? GetReadmeUri()
        {
            string? embeddedReadme = _nuspec.GetReadme();
            if (embeddedReadme == null)
            {
                return null;
            }

            var baseUri = Convert(_package.Path)!;
            UriBuilder builder = new UriBuilder(baseUri)
            {
                Fragment = embeddedReadme
            };

            // get the readme url
            return builder.Uri.AbsoluteUri;
        }
    }
}