Skip to content

Commit 55f68e9

Browse files
committed
Force package metadata URLs to be parsed as absolute URIs (#87)
See JamesNK/Newtonsoft.Json#2128 for more context. NuGet.Core always parses things as absolute URIs so it's not possible for strings only parsable as relative to it into the system. The weird case is something like `//fileserver/Framework/NuGet/server.png` which is parsable as both relative and absolute. Address NuGet/NuGetGallery#7414
1 parent cfe584f commit 55f68e9

2 files changed

Lines changed: 99 additions & 6 deletions

File tree

src/NuGet.Server.Core/Infrastructure/JsonNetPackagesSerializer.cs

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
56
using System.IO;
67
using System.Linq;
@@ -18,7 +19,8 @@ public class JsonNetPackagesSerializer
1819
private readonly JsonSerializer _serializer = new JsonSerializer
1920
{
2021
Formatting = Formatting.None,
21-
NullValueHandling = NullValueHandling.Ignore
22+
NullValueHandling = NullValueHandling.Ignore,
23+
Converters = { new AbsoluteUriConverter() },
2224
};
2325

2426
public void Serialize(IEnumerable<ServerPackage> packages, Stream stream)
@@ -51,5 +53,54 @@ public IEnumerable<ServerPackage> Deserialize(Stream stream)
5153
return packages.Packages;
5254
}
5355
}
56+
57+
/// <summary>
58+
/// This is necessary because Newtonsoft.Json creates <see cref="Uri"/> instances with
59+
/// <see cref="UriKind.RelativeOrAbsolute"/> which treats UNC paths as relative. NuGet.Core uses
60+
/// <see cref="UriKind.Absolute"/> which treats UNC paths as absolute. For more details, see:
61+
/// https://github.com/JamesNK/Newtonsoft.Json/issues/2128
62+
/// </summary>
63+
private class AbsoluteUriConverter : JsonConverter
64+
{
65+
public override bool CanConvert(Type objectType)
66+
{
67+
return objectType == typeof(Uri);
68+
}
69+
70+
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
71+
{
72+
if (reader.TokenType == JsonToken.Null)
73+
{
74+
return null;
75+
}
76+
else if (reader.TokenType == JsonToken.String)
77+
{
78+
return new Uri((string)reader.Value, UriKind.Absolute);
79+
}
80+
81+
throw new JsonSerializationException("The JSON value must be a string.");
82+
}
83+
84+
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
85+
{
86+
if (value == null)
87+
{
88+
writer.WriteNull();
89+
return;
90+
}
91+
92+
if (!(value is Uri uriValue))
93+
{
94+
throw new JsonSerializationException("The value must be a URI.");
95+
}
96+
97+
if (!uriValue.IsAbsoluteUri)
98+
{
99+
throw new JsonSerializationException("The URI value must be an absolute Uri. Relative URI instances are not allowed.");
100+
}
101+
102+
writer.WriteValue(uriValue.OriginalString);
103+
}
104+
}
54105
}
55106
}

test/NuGet.Server.Core.Tests/JsonNetPackagesSerializerTests.cs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,37 @@ namespace NuGet.Server.Core.Tests
1414
{
1515
public class JsonNetPackagesSerializerTests
1616
{
17+
[Fact]
18+
public void RoundTripsUncPaths()
19+
{
20+
var originalPackages = GenerateServerPackages(1);
21+
var originalPackage = originalPackages.Single();
22+
originalPackage.IconUrl = new Uri("//testunc/test/a", UriKind.Absolute);
23+
originalPackage.LicenseUrl = new Uri("//testunc/test/b", UriKind.Absolute);
24+
originalPackage.ProjectUrl = new Uri("//testunc/test/c", UriKind.Absolute);
25+
originalPackage.ReportAbuseUrl = new Uri("//testunc/test/d", UriKind.Absolute);
26+
var serializer = new JsonNetPackagesSerializer();
27+
28+
// Act
29+
var deserializedPackages = new List<ServerPackage>();
30+
using (var memoryStream = new MemoryStream())
31+
{
32+
serializer.Serialize(originalPackages, memoryStream);
33+
34+
memoryStream.Position = 0;
35+
36+
deserializedPackages.AddRange(serializer.Deserialize(memoryStream));
37+
}
38+
39+
// Assert
40+
AssertPackagesAreEqual(originalPackages, deserializedPackages);
41+
var deserializedPackage = deserializedPackages.Single();
42+
Assert.True(deserializedPackage.IconUrl.IsAbsoluteUri, "The icon URL should still be absolute.");
43+
Assert.True(deserializedPackage.LicenseUrl.IsAbsoluteUri, "The license URL should still be absolute.");
44+
Assert.True(deserializedPackage.ProjectUrl.IsAbsoluteUri, "The project URL should still be absolute.");
45+
Assert.True(deserializedPackage.ReportAbuseUrl.IsAbsoluteUri, "The report abuse URL should still be absolute.");
46+
}
47+
1748
[Fact]
1849
public void TestSerializationRoundTrip()
1950
{
@@ -33,14 +64,25 @@ public void TestSerializationRoundTrip()
3364
}
3465

3566
// Assert
67+
AssertPackagesAreEqual(originalPackages, deserializedPackages);
68+
}
69+
70+
private static void AssertPackagesAreEqual(List<ServerPackage> originalPackages, List<ServerPackage> deserializedPackages)
71+
{
3672
Assert.Equal(originalPackages.Count, deserializedPackages.Count);
3773
for (var i = 0; i < originalPackages.Count; i++)
3874
{
39-
Assert.True(PublicPropertiesEqual(originalPackages[i], deserializedPackages[i], "DependencySets", "FrameworkAssemblies", "PackageAssemblyReferences", "AssemblyReferences"));
75+
AssertPublicPropertiesEqual(
76+
originalPackages[i],
77+
deserializedPackages[i],
78+
"DependencySets",
79+
"FrameworkAssemblies",
80+
"PackageAssemblyReferences",
81+
"AssemblyReferences");
4082
}
4183
}
4284

43-
private static bool PublicPropertiesEqual<T>(T a, T b, params string[] ignoreProperties) where T : class
85+
private static void AssertPublicPropertiesEqual<T>(T a, T b, params string[] ignoreProperties) where T : class
4486
{
4587
if (a != null && b != null)
4688
{
@@ -55,15 +97,15 @@ private static bool PublicPropertiesEqual<T>(T a, T b, params string[] ignorePro
5597

5698
if (selfValue != toValue && (selfValue == null || !selfValue.Equals(toValue)) && !(selfValue is IEnumerable))
5799
{
58-
return false;
100+
Assert.False(true, $"The property '{pi.Name}' is not equal.");
59101
}
60102
}
61103
}
62104

63-
return true;
105+
return;
64106
}
65107

66-
return a == b;
108+
Assert.Equal(a, b);
67109
}
68110

69111
private static List<ServerPackage> GenerateServerPackages(int count)

0 commit comments

Comments
 (0)