File: Licenses\NuGetLicense.cs
Web Access
Project: src\src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging)
// 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.Globalization;
using System.Text.RegularExpressions;

namespace NuGet.Packaging.Licenses
{
    /// <summary>
    /// NuGet's internal representation of a license identifier. 
    /// </summary>
    public class NuGetLicense : NuGetLicenseExpression
    {
        /// <summary>
        /// Identifier
        /// </summary>
        public string Identifier { get; }

        /// <summary>
        /// Signifies whether the plus operator has been specified on this license
        /// </summary>
        public bool Plus { get; }

        /// <summary>
        /// Signifies whether this is a standard license known by the NuGet APIs.
        /// Pack for example should warn for these.
        /// </summary>
        public bool IsStandardLicense { get; }

        private NuGetLicense(string identifier, bool plus, bool isStandardLicense)
        {
            Identifier = identifier ?? throw new ArgumentNullException(nameof(identifier));
            Plus = plus;
            IsStandardLicense = isStandardLicense;
            Type = LicenseExpressionType.License;
        }

        /// <summary>
        /// Parse a licenseIdentifier. If a licenseIdentifier is deprecated, this will throw. Non-standard licenses get parsed into a object model as well.
        /// </summary>
        /// <param name="licenseIdentifier">license identifier to be parsed</param>
        /// <param name="allowUnlicensed">Whether the parser allows the UNLICENSED identifier</param>
        /// <returns>Prased NuGetLicense object</returns>
        /// <exception cref="NuGetLicenseExpressionParsingException">If the identifier is deprecated, contains invalid characters or is an exception identifier.</exception>
        /// <exception cref="ArgumentException">If it's null or empty.</exception>
        /// <remarks>The purpose of the <paramref name="allowUnlicensed"/> switch is to allow the expression parser communicate at which operand location the unlicensed identifier is legal. </remarks>
        internal static NuGetLicense ParseIdentifier(string licenseIdentifier, bool allowUnlicensed = false)
        {
            if (!string.IsNullOrWhiteSpace(licenseIdentifier))
            {
                if (NuGetLicenseData.LicenseList.TryGetValue(licenseIdentifier, out var licenseData))
                {
                    return !licenseData.IsDeprecatedLicenseId ?
                        new NuGetLicense(licenseIdentifier, plus: false, isStandardLicense: true) :
                        throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_DeprecatedIdentifier, licenseIdentifier));
                }
                else
                {
                    if (licenseIdentifier[licenseIdentifier.Length - 1] == '+')
                    {
                        var cleanIdentifier = licenseIdentifier.Substring(0, licenseIdentifier.Length - 1);
                        var plus = true;
                        if (NuGetLicenseData.LicenseList.TryGetValue(cleanIdentifier, out licenseData))
                        {
                            return !licenseData.IsDeprecatedLicenseId ?
                                new NuGetLicense(cleanIdentifier, plus: plus, isStandardLicense: true) :
                                throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_DeprecatedIdentifier, licenseIdentifier));
                        }
                        return ProcessLicenseNotInStandardData(cleanIdentifier, plus: plus, allowUnlicensed: allowUnlicensed);
                    }

                    return ProcessLicenseNotInStandardData(licenseIdentifier, plus: false, allowUnlicensed: allowUnlicensed);
                }
            }
            // This will not happen in production code as the tokenizer takes cares of that. 
            throw new ArgumentException(
                string.Format(CultureInfo.CurrentCulture, Strings.ArgumentCannotBeNullOrEmpty, nameof(licenseIdentifier)));
        }

        /// <summary>
        /// The valid characters for a license identifier are a-zA-Z0-9.-
        /// This method assumes that the trailing + operator has been stripped out.
        /// </summary>
        /// <param name="value">The value to be validated.</param>
        /// <returns>whether the value has valid characters</returns>
        private static bool HasValidCharacters(string value)
        {
            var regex = new Regex("^[a-zA-Z0-9\\.\\-]+$", RegexOptions.CultureInvariant);
            return regex.IsMatch(value);
        }

        private static NuGetLicense ProcessLicenseNotInStandardData(string licenseIdentifier, bool plus, bool allowUnlicensed)
        {
            if (!NuGetLicenseData.ExceptionList.TryGetValue(licenseIdentifier, out var exceptionData))
            {
                if (HasValidCharacters(licenseIdentifier))
                {
                    if (licenseIdentifier.Equals(UNLICENSED, StringComparison.Ordinal))
                    {
                        if (plus)
                        {
                            throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_IllegalUnlicensedOperator));
                        }
                        if (allowUnlicensed)
                        {
                            return new NuGetLicense(licenseIdentifier, plus: false, isStandardLicense: true); // UNLICENSED is considered a standard license.
                        }
                        throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_UnexpectedIdentifier, licenseIdentifier));
                    }
                    else
                    {
                        return new NuGetLicense(licenseIdentifier, plus: plus, isStandardLicense: false);
                    }
                }
                else
                {
                    throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_LicenseInvalidCharacters, licenseIdentifier));
                }
            }
            throw new NuGetLicenseExpressionParsingException(string.Format(CultureInfo.CurrentCulture, Strings.NuGetLicenseExpression_LicenseIdentifierIsException, licenseIdentifier));
        }

        internal static string UNLICENSED = "UNLICENSED";

        public override string ToString()
        {
            var plus = Plus ? "+" : string.Empty;
            return $"{Identifier}{plus}";
        }
    }
}