Skip to content

Commit 3db9548

Browse files
Merge branch 'main' into Usr/sogh/mssql-json-phase2
2 parents 9e5a38d + b2cf224 commit 3db9548

5 files changed

Lines changed: 46 additions & 5 deletions

File tree

src/Cli.Tests/ConfigureOptionsTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ public void TestUpdateEnabledForRestSettings(bool updatedEnabledValue)
418418
[DataRow("/updatedPath", DisplayName = "Update REST path to /updatedPath.")]
419419
[DataRow("/updated_Path", DisplayName = "Ensure underscore is allowed in REST path.")]
420420
[DataRow("/updated-Path", DisplayName = "Ensure hyphen is allowed in REST path.")]
421+
[DataRow("/api/v2", DisplayName = "Ensure multi-segment paths are allowed in REST path.")]
421422
public void TestUpdatePathForRestSettings(string updatedPathValue)
422423
{
423424
// Arrange -> all the setup which includes creating options.

src/Cli.Tests/EndToEndTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -343,11 +343,12 @@ public void TestUpdateDepthLimitInGraphQLRuntimeSettings(string depthLimit, bool
343343
[DataRow("/updatedPath", true, DisplayName = "Success in updated GraphQL Path to /updatedPath.")]
344344
[DataRow("/updated-Path", true, DisplayName = "Success in updated GraphQL Path to /updated-Path.")]
345345
[DataRow("/updated_Path", true, DisplayName = "Success in updated GraphQL Path to /updated_Path.")]
346+
[DataRow("/api/v2", true, DisplayName = "Success in updated GraphQL Path to multi-segment path /api/v2.")]
346347
[DataRow("updatedPath", false, DisplayName = "Failure due to '/' missing.")]
347348
[DataRow("/updated Path", false, DisplayName = "Failure due to white spaces.")]
348349
[DataRow("/updated.Path", false, DisplayName = "Failure due to reserved char '.'.")]
349350
[DataRow("/updated@Path", false, DisplayName = "Failure due reserved chars '@'.")]
350-
[DataRow("/updated/Path", false, DisplayName = "Failure due reserved chars '/'.")]
351+
[DataRow("/api//v2", false, DisplayName = "Failure due to empty path segment.")]
351352
public void TestUpdateGraphQLPathRuntimeSettings(string path, bool isSuccess)
352353
{
353354
// Initialize the config file.
@@ -405,11 +406,12 @@ public void TestUpdateHostCorsOriginsRuntimeSettings(string path, bool isSuccess
405406
[DataRow("/updatedPath", true, DisplayName = "Successfully updated Rest Path to /updatedPath.")]
406407
[DataRow("/updated-Path", true, DisplayName = "Successfully updated Rest Path to /updated-Path.")]
407408
[DataRow("/updated_Path", true, DisplayName = "Successfully updated Rest Path to /updated_Path.")]
409+
[DataRow("/api/v2", true, DisplayName = "Successfully updated Rest Path to multi-segment path /api/v2.")]
408410
[DataRow("updatedPath", false, DisplayName = "Failure due to '/' missing.")]
409411
[DataRow("/updated Path", false, DisplayName = "Failure due to white spaces.")]
410412
[DataRow("/updated.Path", false, DisplayName = "Failure due to reserved char '.'.")]
411413
[DataRow("/updated@Path", false, DisplayName = "Failure due reserved chars '@'.")]
412-
[DataRow("/updated/Path", false, DisplayName = "Failure due reserved chars '/'.")]
414+
[DataRow("/api//v2", false, DisplayName = "Failure due to empty path segment.")]
413415
public void TestUpdateRestPathRuntimeSettings(string path, bool isSuccess)
414416
{
415417
// Initialize the config file.

src/Cli/Utils.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,8 @@ public static bool IsURIComponentValid(string? uriComponent)
205205
uriComponent = uriComponent.Substring(1);
206206
}
207207

208-
return !RuntimeConfigValidatorUtil.DoesUriComponentContainReservedChars(uriComponent);
208+
// The path may contain multiple '/'-separated segments (e.g. 'api/v2'); validate each segment.
209+
return !RuntimeConfigValidatorUtil.DoesUriPathContainReservedChars(uriComponent);
209210
}
210211

211212
/// <summary>

src/Core/Configurations/RuntimeConfigValidatorUtil.cs

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,10 @@ public static bool TryValidateUriComponent(string? uriComponent, out string exce
4545
}
4646
else
4747
{
48+
// Remove the leading '/' before validating the remaining path. The path may contain
49+
// multiple '/'-separated segments (e.g. '/api/v2'), each of which is validated individually.
4850
uriComponent = uriComponent.Substring(1);
49-
// URI component should not contain any reserved characters.
50-
if (DoesUriComponentContainReservedChars(uriComponent))
51+
if (DoesUriPathContainReservedChars(uriComponent))
5152
{
5253
exceptionMessageSuffix = URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG;
5354
}
@@ -66,6 +67,34 @@ public static bool DoesUriComponentContainReservedChars(string uriComponent)
6667
return _reservedUriCharsRgx.IsMatch(uriComponent);
6768
}
6869

70+
/// <summary>
71+
/// Method to validate a URI path that may contain multiple '/'-separated segments
72+
/// (for example 'api/v2'). The leading '/' is expected to already be removed.
73+
/// Each segment is validated to ensure it is non-empty and free of reserved characters.
74+
/// An empty input (representing the root path '/') is considered valid.
75+
/// </summary>
76+
/// <param name="uriPath">Runtime endpoint path prefix with the leading '/' already removed (REST/GraphQL/MCP/base-route).</param>
77+
/// <returns>true if any segment is empty or contains reserved characters, false otherwise.</returns>
78+
public static bool DoesUriPathContainReservedChars(string uriPath)
79+
{
80+
// An empty path represents the root '/' which is valid and contains no segments to validate.
81+
if (string.IsNullOrEmpty(uriPath))
82+
{
83+
return false;
84+
}
85+
86+
foreach (string segment in uriPath.Split('/'))
87+
{
88+
// An empty segment indicates leading, consecutive, or trailing slashes.
89+
if (string.IsNullOrEmpty(segment) || DoesUriComponentContainReservedChars(segment))
90+
{
91+
return true;
92+
}
93+
}
94+
95+
return false;
96+
}
97+
6998
/// <summary>
7099
/// Method to validate an entity REST path allowing sub-directories (forward slashes).
71100
/// Each segment of the path is validated for reserved characters and path traversal patterns.

src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,14 @@ private static void ValidateExceptionForDuplicateQueriesDueToEntityDefinitions(S
16831683
DisplayName = "GraphQL path prefix containing space at the start and underscore in between.")]
16841684
[DataRow("/", null, ApiType.REST, false,
16851685
DisplayName = "REST path containing only a forward slash.")]
1686+
[DataRow("/api/v2", null, ApiType.REST, false,
1687+
DisplayName = "REST path containing multiple segments.")]
1688+
[DataRow("/api/v2", null, ApiType.GraphQL, false,
1689+
DisplayName = "GraphQL path containing multiple segments.")]
1690+
[DataRow("/api/", $"REST path {RuntimeConfigValidatorUtil.URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG}", ApiType.REST, true,
1691+
DisplayName = "REST path containing a trailing slash.")]
1692+
[DataRow("/api//v2", $"REST path {RuntimeConfigValidatorUtil.URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG}", ApiType.REST, true,
1693+
DisplayName = "REST path containing an empty segment.")]
16861694
public void ValidateApiURIsAreWellFormed(
16871695
string apiPathPrefix,
16881696
string expectedErrorMessage,

0 commit comments

Comments
 (0)