diff --git a/src/Umbraco.Core/StringUdi.cs b/src/Umbraco.Core/StringUdi.cs index 0f42f4b5f6..59eb40af7e 100644 --- a/src/Umbraco.Core/StringUdi.cs +++ b/src/Umbraco.Core/StringUdi.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel; +using System.Linq; namespace Umbraco.Core { @@ -20,7 +21,7 @@ namespace Umbraco.Core /// The entity type part of the udi. /// The string id part of the udi. public StringUdi(string entityType, string id) - : base(entityType, "umb://" + entityType + "/" + Uri.EscapeUriString(id)) + : base(entityType, "umb://" + entityType + "/" + EscapeUriString(id)) { Id = id; } @@ -35,6 +36,19 @@ namespace Umbraco.Core Id = Uri.UnescapeDataString(uriValue.AbsolutePath.TrimStart('/')); } + private static string EscapeUriString(string s) + { + // Uri.EscapeUriString preserves / but also [ and ] which is bad + // Uri.EscapeDataString does not preserve / which is bad + + // reserved = : / ? # [ ] @ ! $ & ' ( ) * + , ; = + // unreserved = alpha digit - . _ ~ + + // we want to preserve the / and the unreserved + // so... + return string.Join("/", s.Split('/').Select(Uri.EscapeDataString)); + } + /// /// Converts the string representation of an entity identifier into the equivalent StringUdi instance. /// diff --git a/src/Umbraco.Tests/App.config b/src/Umbraco.Tests/App.config index 6500cd3741..84256a1e9b 100644 --- a/src/Umbraco.Tests/App.config +++ b/src/Umbraco.Tests/App.config @@ -79,6 +79,7 @@ + diff --git a/src/Umbraco.Tests/UdiTests.cs b/src/Umbraco.Tests/UdiTests.cs index 6e7e4671a9..f841f9e1a1 100644 --- a/src/Umbraco.Tests/UdiTests.cs +++ b/src/Umbraco.Tests/UdiTests.cs @@ -59,6 +59,29 @@ namespace Umbraco.Tests Assert.AreEqual("umb://" + Constants.UdiEntityType.AnyString + "/path%20to/this%20is%20a%20test.xyz", udi3.ToString()); } + [Test] + public void StringEncodingTest2() + { + // reserved = : / ? # [ ] @ ! $ & ' ( ) * + , ; = + // unreserved = alpha digit - . _ ~ + + Assert.AreEqual("%3A%2F%3F%23%5B%5D%40%21%24%26%27%28%29%2B%2C%3B%3D.-_~%25", Uri.EscapeDataString(":/?#[]@!$&'()+,;=.-_~%")); + Assert.AreEqual(":/?#[]@!$&'()+,;=.-_~%25", Uri.EscapeUriString(":/?#[]@!$&'()+,;=.-_~%")); + + // we cannot have reserved chars at random places + // we want to keep the / in string udis + + var r = string.Join("/", "path/to/View[1].cshtml".Split('/').Select(Uri.EscapeDataString)); + Assert.AreEqual("path/to/View%5B1%5D.cshtml", r); + Assert.IsTrue(Uri.IsWellFormedUriString("umb://partial-view-macro/" + r, UriKind.Absolute)); + + // with the proper fix in StringUdi this should work: + var udi1 = new StringUdi("partial-view-macro", "path/to/View[1].cshtml"); + Assert.AreEqual("umb://partial-view-macro/path/to/View%5B1%5D.cshtml", udi1.ToString()); + var udi2 = Udi.Parse("umb://partial-view-macro/path/to/View%5B1%5D.cshtml"); + Assert.AreEqual("path/to/View[1].cshtml", ((StringUdi) udi2).Id); + } + [Test] public void GuidEntityCtorTest() { diff --git a/src/Umbraco.Tests/packages.config b/src/Umbraco.Tests/packages.config index a9cbce1aad..4458fd06c5 100644 --- a/src/Umbraco.Tests/packages.config +++ b/src/Umbraco.Tests/packages.config @@ -21,6 +21,7 @@ +