diff --git a/src/NuGetGallery.Core/NuGetVersionExtensions.cs b/src/NuGetGallery.Core/NuGetVersionExtensions.cs index 04f8e3d57d..64e9564a0d 100644 --- a/src/NuGetGallery.Core/NuGetVersionExtensions.cs +++ b/src/NuGetGallery.Core/NuGetVersionExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Text.RegularExpressions; using NuGet.Versioning; namespace NuGetGallery @@ -23,6 +24,9 @@ public static string Normalize(string version) public static class NuGetVersionExtensions { + private const RegexOptions Flags = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture; + private static readonly Regex SemanticVersionRegex = new Regex(@"^(?\d+(\s*\.\s*\d+){0,3})(?-[a-z][0-9a-z-]*)?$", Flags); + public static string ToNormalizedStringSafe(this NuGetVersion self) { return self != null ? self.ToNormalizedString() : String.Empty; @@ -32,5 +36,12 @@ public static bool IsSemVer200(this NuGetVersion self) { return self.ReleaseLabels.Count() > 1 || self.HasMetadata; } + + public static bool IsValidVersion(this NuGetVersion self) + { + var match = SemanticVersionRegex.Match(self.ToString().Trim()); + + return match.Success; + } } } \ No newline at end of file diff --git a/src/NuGetGallery.Core/Packaging/ManifestValidator.cs b/src/NuGetGallery.Core/Packaging/ManifestValidator.cs index 7ca939fd87..a5e2ddfa65 100644 --- a/src/NuGetGallery.Core/Packaging/ManifestValidator.cs +++ b/src/NuGetGallery.Core/Packaging/ManifestValidator.cs @@ -8,6 +8,7 @@ using System.IO; using System.Linq; using NuGet.Packaging; +using NuGet.Versioning; namespace NuGetGallery.Packaging { @@ -77,13 +78,10 @@ private static IEnumerable ValidateCore(PackageMetadata packag Strings.Manifest_InvalidVersion, version)); } - if (packageMetadata.Version.IsSemVer200()) - { - yield return new ValidationResult(String.Format( - CultureInfo.CurrentCulture, - Strings.Manifest_InvalidVersionSemVer200, - packageMetadata.Version.ToFullString())); + foreach (var validationResult in ValidateVersion(packageMetadata.Version)) + { + yield return validationResult; } // Check framework reference groups @@ -122,7 +120,7 @@ private static IEnumerable ValidateCore(PackageMetadata packag dependencyGroup.TargetFramework?.ToString())); } - // Verify package id's + // Verify package id's and versions foreach (var dependency in dependencyGroup.Packages) { bool duplicate = !dependencyIds.Add(dependency.Id); @@ -143,11 +141,47 @@ private static IEnumerable ValidateCore(PackageMetadata packag dependency.Id, dependency.VersionRange.OriginalString)); } + + // Versions + if (dependency.VersionRange.MinVersion != null) + { + foreach (var validationResult in ValidateVersion(dependency.VersionRange.MinVersion)) + { + yield return validationResult; + } + } + + if (dependency.VersionRange.MaxVersion != null + && dependency.VersionRange.MaxVersion != dependency.VersionRange.MinVersion) + { + foreach (var validationResult in ValidateVersion(dependency.VersionRange.MaxVersion)) + { + yield return validationResult; + } + } } } } } + private static IEnumerable ValidateVersion(NuGetVersion version) + { + if (version.IsSemVer200()) + { + yield return new ValidationResult(string.Format( + CultureInfo.CurrentCulture, + Strings.Manifest_InvalidVersionSemVer200, + version.ToFullString())); + } + else if (!version.IsValidVersion()) + { + yield return new ValidationResult(string.Format( + CultureInfo.CurrentCulture, + Strings.Manifest_InvalidVersion, + version)); + } + } + private static IEnumerable CheckUrls(params string[] urls) { foreach (var url in urls) diff --git a/tests/NuGetGallery.Core.Facts/Packaging/ManifestValidatorFacts.cs b/tests/NuGetGallery.Core.Facts/Packaging/ManifestValidatorFacts.cs index cc650c90d4..ec876b0d9b 100644 --- a/tests/NuGetGallery.Core.Facts/Packaging/ManifestValidatorFacts.cs +++ b/tests/NuGetGallery.Core.Facts/Packaging/ManifestValidatorFacts.cs @@ -102,6 +102,40 @@ public class ManifestValidatorFacts "; + private const string NuSpecDependencyVersionPlaceholder = @" + + + valid + 2.0.0 + Package A + ownera, ownerb + ownera, ownerb + false + package A description. + en-US + + + + + + + "; + + private const string NuSpecPlaceholderVersion = @" + + + valid + {0} + Package A + ownera, ownerb + ownera, ownerb + false + package A description. + en-US + + + "; + private const string NuSpecIconUrlInvalid = @" @@ -399,6 +433,39 @@ public void ReturnsErrorIfVersionIsSemVer200() Assert.Equal(new[] { String.Format(Strings.Manifest_InvalidVersionSemVer200, "2.0.0+123") }, GetErrors(nuspecStream)); } + [Theory] + [InlineData("1.0.0-beta.1")] + [InlineData("3.0.0-beta+12")] + public void ReturnsErrorIfDependencyVersionIsSemVer200(string version) + { + var nuspecStream = CreateNuspecStream(string.Format(NuSpecDependencyVersionPlaceholder, version)); + + Assert.Equal(new[] { String.Format(Strings.Manifest_InvalidVersionSemVer200, version) }, GetErrors(nuspecStream)); + } + + [Theory] + [InlineData("1.0.0-10")] + [InlineData("1.0.0--")] + public void ReturnsErrorIfVersionIsInvalid(string version) + { + // https://github.com/NuGet/NuGetGallery/issues/3226 + + var nuspecStream = CreateNuspecStream(string.Format(NuSpecPlaceholderVersion, version)); + + Assert.Equal(new[] { String.Format(Strings.Manifest_InvalidVersion, version) }, GetErrors(nuspecStream)); + } + + + [Theory] + [InlineData("1.0.0-10")] + [InlineData("1.0.0--")] + public void ReturnsErrorIfDependencyVersionIsInvalid(string version) + { + var nuspecStream = CreateNuspecStream(string.Format(NuSpecDependencyVersionPlaceholder, version)); + + Assert.Equal(new[] { String.Format(Strings.Manifest_InvalidVersion, version) }, GetErrors(nuspecStream)); + } + [Fact] public void ReturnsErrorIfFrameworkAssemblyReferenceContainsUnsupportedTargetFramework() {