-
Notifications
You must be signed in to change notification settings - Fork 838
/
QueryParameterMatcherPolicy.cs
137 lines (114 loc) · 4.76 KB
/
QueryParameterMatcherPolicy.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.Primitives;
using Yarp.ReverseProxy.Configuration;
namespace Yarp.ReverseProxy.Routing;
internal sealed class QueryParameterMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, IEndpointSelectorPolicy
{
/// <inheritdoc/>
// Run after HttpMethodMatcherPolicy (-1000) and HostMatcherPolicy (-100), and HeaderMatcherPolicy (-50), but before default (0)
public override int Order => -25;
/// <inheritdoc/>
public IComparer<Endpoint> Comparer => new QueryParameterMetadataEndpointComparer();
/// <inheritdoc/>
bool IEndpointSelectorPolicy.AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{
_ = endpoints ?? throw new ArgumentNullException(nameof(endpoints));
// When the node contains dynamic endpoints we can't make any assumptions.
if (ContainsDynamicEndpoints(endpoints))
{
return true;
}
return AppliesToEndpointsCore(endpoints);
}
private static bool AppliesToEndpointsCore(IReadOnlyList<Endpoint> endpoints)
{
return endpoints.Any(e =>
{
var metadata = e.Metadata.GetMetadata<IQueryParameterMetadata>();
return metadata?.Matchers?.Length > 0;
});
}
/// <inheritdoc/>
public Task ApplyAsync(HttpContext httpContext, CandidateSet candidates)
{
_ = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
_ = candidates ?? throw new ArgumentNullException(nameof(candidates));
var query = httpContext.Request.Query;
for (var i = 0; i < candidates.Count; i++)
{
if (!candidates.IsValidCandidate(i))
{
continue;
}
var matchers = candidates[i].Endpoint.Metadata.GetMetadata<IQueryParameterMetadata>()?.Matchers;
if (matchers is null)
{
continue;
}
foreach (var matcher in matchers)
{
query.TryGetValue(matcher.Name, out var requestQueryParameterValues);
var valueIsEmpty = StringValues.IsNullOrEmpty(requestQueryParameterValues);
var matched = matcher.Mode switch
{
QueryParameterMatchMode.Exists => !valueIsEmpty,
QueryParameterMatchMode.Exact => !valueIsEmpty && TryMatch(matcher, requestQueryParameterValues),
QueryParameterMatchMode.Prefix => !valueIsEmpty && TryMatch(matcher, requestQueryParameterValues),
QueryParameterMatchMode.Contains => !valueIsEmpty && TryMatch(matcher, requestQueryParameterValues),
QueryParameterMatchMode.NotContains => valueIsEmpty || TryMatch(matcher, requestQueryParameterValues),
_ => false
};
if (!matched)
{
candidates.SetValidity(i, false);
break;
}
}
}
return Task.CompletedTask;
}
private static bool TryMatch(QueryParameterMatcher matcher, StringValues requestHeaderValues)
{
var requestHeaderCount = requestHeaderValues.Count;
for (var i = 0; i < requestHeaderCount; i++)
{
var requestValue = requestHeaderValues[i];
if (requestValue is null)
{
continue;
}
foreach (var expectedValue in matcher.Values)
{
if (TryMatch(matcher, requestValue, expectedValue))
{
return matcher.Mode != QueryParameterMatchMode.NotContains;
}
}
}
return matcher.Mode == QueryParameterMatchMode.NotContains;
static bool TryMatch(QueryParameterMatcher matcher, string queryValue, string expectedValue)
{
return matcher.Mode switch
{
QueryParameterMatchMode.Exact => queryValue.Equals(expectedValue, matcher.Comparison),
QueryParameterMatchMode.Prefix => queryValue.StartsWith(expectedValue, matcher.Comparison),
_ => queryValue.Contains(expectedValue, matcher.Comparison)
};
}
}
private class QueryParameterMetadataEndpointComparer : EndpointMetadataComparer<IQueryParameterMetadata>
{
protected override int CompareMetadata(IQueryParameterMetadata? x, IQueryParameterMetadata? y)
{
return (y?.Matchers?.Length ?? 0).CompareTo(x?.Matchers?.Length ?? 0);
}
}
}