-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#2212 Incorrect calculation of placeholders, only the last placeholder should be allowed to match several segments #2213
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,10 +67,12 @@ public Response<List<PlaceholderNameAndValue>> Find(string path, string query, s | |
private static List<Group> FindGroups(string path, string query, string template) | ||
{ | ||
template = EscapeExceptBraces(template); | ||
var regexPattern = $"^{RegexPlaceholders().Replace(template, match => $"(?<{match.Groups[1].Value}>[^&]*)")}"; | ||
var regexPattern = GenerateRegexPattern(template); | ||
var testedPath = ShouldSkipQuery(query, template) ? path : $"{path}{query}"; | ||
|
||
var match = Regex.Match(testedPath, regexPattern); | ||
var foundGroups = match.Groups.Cast<Group>().Skip(1).ToList(); | ||
|
||
if (foundGroups.Count > 0 || !IsCatchAllPath(template)) | ||
{ | ||
return foundGroups; | ||
|
@@ -80,6 +82,29 @@ private static List<Group> FindGroups(string path, string query, string template | |
match = Regex.Match($"{testedPath}/", regexPattern); | ||
return match.Groups.Cast<Group>().Skip(1).ToList(); | ||
} | ||
|
||
/// <summary> | ||
/// The placeholders that are not placed at the end of the template are delimited by forward slashes, only the last one, the catch-all can match more segments. | ||
/// </summary> | ||
/// <param name="escapedTemplate">The escaped path template.</param> | ||
/// <returns>The pattern for values replacement.</returns> | ||
private static string GenerateRegexPattern(string escapedTemplate) | ||
{ | ||
// First we count the matches | ||
var placeHoldersCountMatch = RegexPlaceholders().Matches(escapedTemplate); | ||
int index = 0, placeHoldersCount = placeHoldersCountMatch.Count; | ||
|
||
// We know that the replace process will be started from the beginning of the url, | ||
// so we can use a simple counter to determine the last placeholder | ||
string MatchEvaluator(Match match) | ||
{ | ||
var groupName = match.Groups[1].Value; | ||
index++; | ||
return index == placeHoldersCount ? $"(?<{groupName}>[^&]*)" : $"(?<{groupName}>[^/|&]*)"; | ||
} | ||
|
||
return $@"^{RegexPlaceholders().Replace(escapedTemplate, MatchEvaluator)}"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it possible to move this replacement to the static object? It appears not, due to the varying |
||
} | ||
|
||
private const int CatchAllQueryMilliseconds = 300; | ||
#if NET7_0_OR_GREATER | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -501,6 +501,30 @@ public void ShouldNotMatchComplexQueriesCaseSensitive(string downstream, string | |
.BDDfy(); | ||
} | ||
|
||
[Theory] | ||
[Trait("Bug", "2212")] | ||
[InlineData("/data-registers/{version}/it/{everything}", "/dati-registri/{version}/{everything}", "/dati-registri/v1.0/operatore/R80QQ5J9600/valida", "/data-registers/v1.0/it/operatore/R80QQ5J9600/valida")] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this test data from #2212, right? |
||
[InlineData("/files/{version}/uploads/{everything}", "/data/{version}/storage/{everything}", "/data/v2.0/storage/images/photos/nature", "/files/v2.0/uploads/images/photos/nature")] | ||
[InlineData("/resources/{area}/details/{everything}", "/api/resources/{area}/info/{everything}", "/api/resources/global/info/stats/2024/data", "/resources/global/details/stats/2024/data")] | ||
[InlineData("/users/{userId}/logs/{everything}", "/data/users/{userId}/activity/{everything}", "/data/users/12345/activity/session/login/2024", "/users/12345/logs/session/login/2024")] | ||
[InlineData("/orders/{orderId}/items/{everything}", "/ecommerce/{orderId}/details/{everything}", "/ecommerce/98765/details/category/electronics/phone", "/orders/98765/items/category/electronics/phone")] | ||
[InlineData("/tasks/{taskId}/subtasks/{everything}", "/work/{taskId}/breakdown/{everything}", "/work/56789/breakdown/phase/3/step/2", "/tasks/56789/subtasks/phase/3/step/2")] | ||
[InlineData("/configs/{env}/overrides/{everything}", "/settings/{env}/{everything}", "/settings/prod/feature/toggles", "/configs/prod/overrides/feature/toggles")] | ||
public void OnlyTheLastPlaceholderShouldMatchSeveralSegments(string downstream, string upstream, string requestUrl, string downstreamPath) | ||
{ | ||
var port = PortFinder.GetRandomPort(); | ||
var route = GivenRoute(port, upstream, downstream); | ||
var configuration = GivenConfiguration(route); | ||
this.Given(x => GivenThereIsAServiceRunningOn(port, downstreamPath, HttpStatusCode.OK, "Hello from Guillaume")) | ||
.And(x => GivenThereIsAConfiguration(configuration)) | ||
.And(x => GivenOcelotIsRunning()) | ||
.When(x => WhenIGetUrlOnTheApiGateway(requestUrl)) | ||
.Then(x => ThenTheDownstreamUrlPathShouldBe(downstreamPath)) | ||
.And(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) | ||
.And(x => ThenTheResponseBodyShouldBe("Hello from Guillaume")) | ||
.BDDfy(); | ||
} | ||
|
||
[Fact] | ||
[Trait("Feat", "91, 94")] | ||
public void Should_return_response_201_with_simple_url_and_multiple_upstream_http_method() | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems this is dynamic
Regex
, so the pattern is dynamically generated, but the previous version akaRegexPlaceholders()
returns static object.Well... is it possible to convert somehow to static object to improve the performance?