From 3c7e83f454709a3cd943b655f68da4261a1660fe Mon Sep 17 00:00:00 2001 From: Sam Xu Date: Thu, 21 Apr 2022 11:19:56 -0700 Subject: [PATCH] Issue #567: Escape character , for example ('/') is not proper handled at key or function in quoted string --- .../Controllers/PeopleController.cs | 3 +- .../Routing/Template/KeySegmentTemplate.cs | 2 + .../Template/SegmentTemplateHelpers.cs | 1 + .../Template/FunctionSegmentTemplateTests.cs | 34 ++++++++++++++ .../Template/KeySegmentTemplateTests.cs | 47 +++++++++++++++++++ 5 files changed, 86 insertions(+), 1 deletion(-) diff --git a/sample/ODataRoutingSample/Controllers/PeopleController.cs b/sample/ODataRoutingSample/Controllers/PeopleController.cs index 4d3d8b400..34b577e5c 100644 --- a/sample/ODataRoutingSample/Controllers/PeopleController.cs +++ b/sample/ODataRoutingSample/Controllers/PeopleController.cs @@ -21,7 +21,7 @@ public class PeopleController : ControllerBase new Person { FirstName = "Goods", - LastName = "Zhangg", + LastName = "Zha/ngg", }, new Person { @@ -42,6 +42,7 @@ public IActionResult Get(CancellationToken token) return Ok(_persons); } + // People(FirstName='Goods',LastName='Zha%2Fngg') [HttpGet] [EnableQuery] public IActionResult Get(string keyFirstName, string keyLastName) diff --git a/src/Microsoft.AspNetCore.OData/Routing/Template/KeySegmentTemplate.cs b/src/Microsoft.AspNetCore.OData/Routing/Template/KeySegmentTemplate.cs index dc282ffdd..b139b7cf5 100644 --- a/src/Microsoft.AspNetCore.OData/Routing/Template/KeySegmentTemplate.cs +++ b/src/Microsoft.AspNetCore.OData/Routing/Template/KeySegmentTemplate.cs @@ -5,6 +5,7 @@ // //------------------------------------------------------------------------------ +using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; @@ -172,6 +173,7 @@ public override bool TryTranslate(ODataTemplateTranslateContext context) IEdmTypeReference edmType = keyProperty.Type; string strValue = rawValue as string; string newStrValue = context.GetParameterAliasOrSelf(strValue); + newStrValue = Uri.UnescapeDataString(newStrValue); if (newStrValue != strValue) { updateValues[templateName] = newStrValue; diff --git a/src/Microsoft.AspNetCore.OData/Routing/Template/SegmentTemplateHelpers.cs b/src/Microsoft.AspNetCore.OData/Routing/Template/SegmentTemplateHelpers.cs index f65a34b47..46e44a38f 100644 --- a/src/Microsoft.AspNetCore.OData/Routing/Template/SegmentTemplateHelpers.cs +++ b/src/Microsoft.AspNetCore.OData/Routing/Template/SegmentTemplateHelpers.cs @@ -58,6 +58,7 @@ public static IList Match(ODataTemplateTranslateConte { string strValue = rawValue as string; string newStrValue = context.GetParameterAliasOrSelf(strValue); + newStrValue = Uri.UnescapeDataString(newStrValue); if (newStrValue != strValue) { updatedValues[parameterTemp] = newStrValue; diff --git a/test/Microsoft.AspNetCore.OData.Tests/Routing/Template/FunctionSegmentTemplateTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Routing/Template/FunctionSegmentTemplateTests.cs index ee848b2d8..3c18a2019 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Routing/Template/FunctionSegmentTemplateTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Routing/Template/FunctionSegmentTemplateTests.cs @@ -370,5 +370,39 @@ public void TryTranslateFunctionSegmentTemplate_ReturnsODataFunctionSegment_With Assert.Equal(42, e.Value); }); } + + [Fact] + public void TryTranslateFunctionSegmentTemplate_ReturnsODataFunctionSegment_UsingEscapedString() + { + // Arrange + var primitive = EdmCoreModel.Instance.GetPrimitive(EdmPrimitiveTypeKind.String, false); + EdmFunction function = new EdmFunction("NS", "MyFunction", primitive, true, null, false); + function.AddParameter("bindingParameter", primitive); + function.AddParameter("name", primitive); + + FunctionSegmentTemplate template = new FunctionSegmentTemplate(function, null); + EdmModel edmModel = new EdmModel(); + edmModel.AddElement(function); + + RouteValueDictionary routeValue = new RouteValueDictionary(new { name = "'Ji%2FChange%23%20T'" }); + + ODataTemplateTranslateContext context = new ODataTemplateTranslateContext + { + RouteValues = routeValue, + Model = edmModel + }; + + // Act + bool ok = template.TryTranslate(context); + + // Assert + Assert.True(ok); + ODataPathSegment actual = Assert.Single(context.Segments); + OperationSegment functionSegment = Assert.IsType(actual); + Assert.Same(function, functionSegment.Operations.First()); + var parameter = Assert.Single(functionSegment.Parameters); + Assert.Equal("name", parameter.Name); + Assert.Equal("Ji/Change# T", parameter.Value.ToString()); + } } } diff --git a/test/Microsoft.AspNetCore.OData.Tests/Routing/Template/KeySegmentTemplateTests.cs b/test/Microsoft.AspNetCore.OData.Tests/Routing/Template/KeySegmentTemplateTests.cs index d33dbeb7e..d79a635fe 100644 --- a/test/Microsoft.AspNetCore.OData.Tests/Routing/Template/KeySegmentTemplateTests.cs +++ b/test/Microsoft.AspNetCore.OData.Tests/Routing/Template/KeySegmentTemplateTests.cs @@ -484,6 +484,53 @@ public void TryTranslateKeySegmentTemplate_WorksWithKeyParametersAlias() Assert.Equal(42, actualKeys.Value); } + [Fact] + public void TryTranslateKeySegmentTemplate_WorksWithKeyValue_UsingEscapedString() + { + // Arrange + EdmModel model = new EdmModel(); + EdmEntityType customerType = new EdmEntityType("NS", "Customer"); + EdmStructuralProperty firstName = customerType.AddStructuralProperty("FirstName", EdmPrimitiveTypeKind.String); + EdmStructuralProperty lastName = customerType.AddStructuralProperty("LastName", EdmPrimitiveTypeKind.String); + customerType.AddKeys(firstName, lastName); + model.AddElement(customerType); + EdmEntityContainer container = new EdmEntityContainer("NS", "Default"); + EdmEntitySet customers = container.AddEntitySet("Customers", customerType); + model.AddElement(container); + RouteValueDictionary routeValueDictionary = new RouteValueDictionary(new { First = "'Zhang'", Last = "'Gan%2Fnng%23%20T'" }); + IDictionary keys = new Dictionary + { + { "FirstName", "{first}" }, + { "LastName", "{last}" } + }; + KeySegmentTemplate template = new KeySegmentTemplate(keys, customerType, customers); + + ODataTemplateTranslateContext context = new ODataTemplateTranslateContext + { + RouteValues = routeValueDictionary, + Model = model + }; + + // Act + bool ok = template.TryTranslate(context); + + // Assert + Assert.True(ok); + ODataPathSegment actual = Assert.Single(context.Segments); + KeySegment keySegment = Assert.IsType(actual); + Assert.Collection(keySegment.Keys, + e => + { + Assert.Equal("FirstName", e.Key); + Assert.Equal("Zhang", e.Value); + }, + e => + { + Assert.Equal("LastName", e.Key); + Assert.Equal("Gan/nng# T", e.Value); + }); + } + [Fact] public void CreateKeySegmentKeySegmentTemplate_ThrowsArgumentNull_EntityType() {