From 9515c4c0ff7901b6c442497c93113982ecdf68e5 Mon Sep 17 00:00:00 2001 From: Youssef1313 Date: Tue, 18 Aug 2026 10:31:46 +0200 Subject: [PATCH] Reject empty reference tokens in JsonPatch --- .../src/Internal/ParsedPath.cs | 14 +++++++++++++ .../test/Internal/ParsedPathTests.cs | 18 +++++++++++++++++ .../JsonPatch/src/Internal/ParsedPath.cs | 13 ++++++++++++ .../test/Internal/ParsedPathTests.cs | 20 ++++++++++++++++++- 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/Features/JsonPatch.SystemTextJson/src/Internal/ParsedPath.cs b/src/Features/JsonPatch.SystemTextJson/src/Internal/ParsedPath.cs index a5e13cae9663..cf73fd263024 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/Internal/ParsedPath.cs +++ b/src/Features/JsonPatch.SystemTextJson/src/Internal/ParsedPath.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Collections.Generic; using System.Text; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Exceptions; @@ -36,6 +37,11 @@ public string LastSegment private static string[] ParsePath(string path) { + if (path.Length == 0) + { + return Array.Empty(); + } + var strings = new List(); var sb = new StringBuilder(path.Length); @@ -48,6 +54,10 @@ private static string[] ParsePath(string path) strings.Add(sb.ToString()); sb.Length = 0; } + else if (i != 0) + { + throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null); + } } else if (path[i] == '~') { @@ -80,6 +90,10 @@ private static string[] ParsePath(string path) { strings.Add(sb.ToString()); } + else + { + throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null); + } return strings.ToArray(); } diff --git a/src/Features/JsonPatch.SystemTextJson/test/Internal/ParsedPathTests.cs b/src/Features/JsonPatch.SystemTextJson/test/Internal/ParsedPathTests.cs index 547d69ed704c..db5f3a7a2cfe 100644 --- a/src/Features/JsonPatch.SystemTextJson/test/Internal/ParsedPathTests.cs +++ b/src/Features/JsonPatch.SystemTextJson/test/Internal/ParsedPathTests.cs @@ -16,6 +16,9 @@ public class ParsedPathTests [InlineData("foo/bar~1baz", new string[] { "foo", "bar/baz" })] [InlineData("foo/bar~0/~0/~1~1/~0~0/baz", new string[] { "foo", "bar~", "~", "//", "~~", "baz" })] [InlineData("~0~1foo", new string[] { "~/foo" })] + [InlineData("/foo/bar", new string[] { "foo", "bar" })] + [InlineData("/foo", new string[] { "foo" })] + [InlineData("", new string[] { })] public void ParsingValidPathShouldSucceed(string path, string[] expected) { // Arrange & Act @@ -38,4 +41,19 @@ public void PathWithInvalidEscapeSequenceShouldFail(string path) var parsedPath = new ParsedPath(path); }); } + + [Theory] + [InlineData("//isSmth")] + [InlineData("//")] + [InlineData("/foo/")] + [InlineData("/foo//bar")] + [InlineData("foo//bar")] + [InlineData("foo/")] + public void PathWithEmptyReferenceTokenShouldFail(string path) + { + Assert.Throws(() => + { + var parsedPath = new ParsedPath(path); + }); + } } diff --git a/src/Features/JsonPatch/src/Internal/ParsedPath.cs b/src/Features/JsonPatch/src/Internal/ParsedPath.cs index 44ac3f75df92..edea3eef1689 100644 --- a/src/Features/JsonPatch/src/Internal/ParsedPath.cs +++ b/src/Features/JsonPatch/src/Internal/ParsedPath.cs @@ -41,6 +41,11 @@ public string LastSegment private static string[] ParsePath(string path) { + if (path.Length == 0) + { + return Array.Empty(); + } + var strings = new List(); var sb = new StringBuilder(path.Length); @@ -53,6 +58,10 @@ private static string[] ParsePath(string path) strings.Add(sb.ToString()); sb.Length = 0; } + else if (i != 0) + { + throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null); + } } else if (path[i] == '~') { @@ -85,6 +94,10 @@ private static string[] ParsePath(string path) { strings.Add(sb.ToString()); } + else + { + throw new JsonPatchException(Resources.FormatInvalidValueForPath(path), null); + } return strings.ToArray(); } diff --git a/src/Features/JsonPatch/test/Internal/ParsedPathTests.cs b/src/Features/JsonPatch/test/Internal/ParsedPathTests.cs index c726b96216bb..e1b8e1c0d7ff 100644 --- a/src/Features/JsonPatch/test/Internal/ParsedPathTests.cs +++ b/src/Features/JsonPatch/test/Internal/ParsedPathTests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.AspNetCore.JsonPatch.Exceptions; @@ -16,6 +16,9 @@ public class ParsedPathTests [InlineData("foo/bar~1baz", new string[] { "foo", "bar/baz" })] [InlineData("foo/bar~0/~0/~1~1/~0~0/baz", new string[] { "foo", "bar~", "~", "//", "~~", "baz" })] [InlineData("~0~1foo", new string[] { "~/foo" })] + [InlineData("/foo/bar", new string[] { "foo", "bar" })] + [InlineData("/foo", new string[] { "foo" })] + [InlineData("", new string[] { })] public void ParsingValidPathShouldSucceed(string path, string[] expected) { // Arrange & Act @@ -38,4 +41,19 @@ public void PathWithInvalidEscapeSequenceShouldFail(string path) var parsedPath = new ParsedPath(path); }); } + + [Theory] + [InlineData("//isSmth")] + [InlineData("//")] + [InlineData("/foo/")] + [InlineData("/foo//bar")] + [InlineData("foo//bar")] + [InlineData("foo/")] + public void PathWithEmptyReferenceTokenShouldFail(string path) + { + Assert.Throws(() => + { + var parsedPath = new ParsedPath(path); + }); + } }