File: Model\LocalPackageSearchMetadata.cs
Web Access
Project: src\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.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;
        }
    }
}