Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Cli.Tests/ConfigureOptionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ public void TestUpdateEnabledForRestSettings(bool updatedEnabledValue)
[DataRow("/updatedPath", DisplayName = "Update REST path to /updatedPath.")]
[DataRow("/updated_Path", DisplayName = "Ensure underscore is allowed in REST path.")]
[DataRow("/updated-Path", DisplayName = "Ensure hyphen is allowed in REST path.")]
[DataRow("/api/v2", DisplayName = "Ensure multi-segment paths are allowed in REST path.")]
public void TestUpdatePathForRestSettings(string updatedPathValue)
{
// Arrange -> all the setup which includes creating options.
Expand Down
6 changes: 4 additions & 2 deletions src/Cli.Tests/EndToEndTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -343,11 +343,12 @@ public void TestUpdateDepthLimitInGraphQLRuntimeSettings(string depthLimit, bool
[DataRow("/updatedPath", true, DisplayName = "Success in updated GraphQL Path to /updatedPath.")]
[DataRow("/updated-Path", true, DisplayName = "Success in updated GraphQL Path to /updated-Path.")]
[DataRow("/updated_Path", true, DisplayName = "Success in updated GraphQL Path to /updated_Path.")]
[DataRow("/api/v2", true, DisplayName = "Success in updated GraphQL Path to multi-segment path /api/v2.")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Think it would be better to use a different name than api since that is the default we use for REST paths. I think it is better to use graphql

[DataRow("updatedPath", false, DisplayName = "Failure due to '/' missing.")]
[DataRow("/updated Path", false, DisplayName = "Failure due to white spaces.")]
[DataRow("/updated.Path", false, DisplayName = "Failure due to reserved char '.'.")]
[DataRow("/updated@Path", false, DisplayName = "Failure due reserved chars '@'.")]
[DataRow("/updated/Path", false, DisplayName = "Failure due reserved chars '/'.")]
[DataRow("/api//v2", false, DisplayName = "Failure due to empty path segment.")]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous comment

public void TestUpdateGraphQLPathRuntimeSettings(string path, bool isSuccess)
{
// Initialize the config file.
Expand Down Expand Up @@ -405,11 +406,12 @@ public void TestUpdateHostCorsOriginsRuntimeSettings(string path, bool isSuccess
[DataRow("/updatedPath", true, DisplayName = "Successfully updated Rest Path to /updatedPath.")]
[DataRow("/updated-Path", true, DisplayName = "Successfully updated Rest Path to /updated-Path.")]
[DataRow("/updated_Path", true, DisplayName = "Successfully updated Rest Path to /updated_Path.")]
[DataRow("/api/v2", true, DisplayName = "Successfully updated Rest Path to multi-segment path /api/v2.")]
[DataRow("updatedPath", false, DisplayName = "Failure due to '/' missing.")]
[DataRow("/updated Path", false, DisplayName = "Failure due to white spaces.")]
[DataRow("/updated.Path", false, DisplayName = "Failure due to reserved char '.'.")]
[DataRow("/updated@Path", false, DisplayName = "Failure due reserved chars '@'.")]
[DataRow("/updated/Path", false, DisplayName = "Failure due reserved chars '/'.")]
[DataRow("/api//v2", false, DisplayName = "Failure due to empty path segment.")]
public void TestUpdateRestPathRuntimeSettings(string path, bool isSuccess)
{
// Initialize the config file.
Expand Down
3 changes: 2 additions & 1 deletion src/Cli/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,8 @@ public static bool IsURIComponentValid(string? uriComponent)
uriComponent = uriComponent.Substring(1);
}

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

/// <summary>
Expand Down
33 changes: 31 additions & 2 deletions src/Core/Configurations/RuntimeConfigValidatorUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ public static bool TryValidateUriComponent(string? uriComponent, out string exce
}
else
{
// Remove the leading '/' before validating the remaining path. The path may contain
// multiple '/'-separated segments (e.g. '/api/v2'), each of which is validated individually.
uriComponent = uriComponent.Substring(1);
// URI component should not contain any reserved characters.
if (DoesUriComponentContainReservedChars(uriComponent))
if (DoesUriPathContainReservedChars(uriComponent))
{
exceptionMessageSuffix = URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG;
}
Expand All @@ -66,6 +67,34 @@ public static bool DoesUriComponentContainReservedChars(string uriComponent)
return _reservedUriCharsRgx.IsMatch(uriComponent);
}

/// <summary>
/// Method to validate a URI path that may contain multiple '/'-separated segments
/// (for example 'api/v2'). The leading '/' is expected to already be removed.
/// Each segment is validated to ensure it is non-empty and free of reserved characters.
/// An empty input (representing the root path '/') is considered valid.
/// </summary>
/// <param name="uriPath">Runtime endpoint path prefix with the leading '/' already removed (REST/GraphQL/MCP/base-route).</param>
/// <returns>true if any segment is empty or contains reserved characters, false otherwise.</returns>
Comment thread
Copilot marked this conversation as resolved.
public static bool DoesUriPathContainReservedChars(string uriPath)
{
// An empty path represents the root '/' which is valid and contains no segments to validate.
if (string.IsNullOrEmpty(uriPath))
{
return false;
}

foreach (string segment in uriPath.Split('/'))
{
// An empty segment indicates leading, consecutive, or trailing slashes.
if (string.IsNullOrEmpty(segment) || DoesUriComponentContainReservedChars(segment))
{
return true;
}
}

return false;
}

/// <summary>
/// Method to validate an entity REST path allowing sub-directories (forward slashes).
/// Each segment of the path is validated for reserved characters and path traversal patterns.
Expand Down
8 changes: 8 additions & 0 deletions src/Service.Tests/UnitTests/ConfigValidationUnitTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1683,6 +1683,14 @@ private static void ValidateExceptionForDuplicateQueriesDueToEntityDefinitions(S
DisplayName = "GraphQL path prefix containing space at the start and underscore in between.")]
[DataRow("/", null, ApiType.REST, false,
DisplayName = "REST path containing only a forward slash.")]
[DataRow("/api/v2", null, ApiType.REST, false,
DisplayName = "REST path containing multiple segments.")]
[DataRow("/api/v2", null, ApiType.GraphQL, false,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous comment

DisplayName = "GraphQL path containing multiple segments.")]
[DataRow("/api/", $"REST path {RuntimeConfigValidatorUtil.URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG}", ApiType.REST, true,
DisplayName = "REST path containing a trailing slash.")]
[DataRow("/api//v2", $"REST path {RuntimeConfigValidatorUtil.URI_COMPONENT_WITH_RESERVED_CHARS_ERR_MSG}", ApiType.REST, true,
DisplayName = "REST path containing an empty segment.")]
public void ValidateApiURIsAreWellFormed(
string apiPathPrefix,
string expectedErrorMessage,
Expand Down