xref: /aosp_15_r20/external/grpc-grpc-java/xds/src/main/java/io/grpc/xds/internal/Matchers.java (revision e07d83d3ffcef9ecfc9f7f475418ec639ff0e5fe)
1 /*
2  * Copyright 2021 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.xds.internal;
18 
19 import static com.google.common.base.Preconditions.checkNotNull;
20 
21 import com.google.auto.value.AutoValue;
22 import com.google.re2j.Pattern;
23 import java.math.BigInteger;
24 import java.net.InetAddress;
25 import javax.annotation.Nullable;
26 
27 /**
28  * Provides a group of request matchers. A matcher evaluates an input and tells whether certain
29  * argument in the input matches a predefined matching pattern.
30  */
31 public final class Matchers {
32   // Prevent instantiation.
Matchers()33   private Matchers() {}
34 
35   /** Matcher for HTTP request headers. */
36   @AutoValue
37   public abstract static class HeaderMatcher {
38     // Name of the header to be matched.
name()39     public abstract String name();
40 
41     // Matches exact header value.
42     @Nullable
exactValue()43     public abstract String exactValue();
44 
45     // Matches header value with the regular expression pattern.
46     @Nullable
safeRegEx()47     public abstract Pattern safeRegEx();
48 
49     // Matches header value an integer value in the range.
50     @Nullable
range()51     public abstract Range range();
52 
53     // Matches header presence.
54     @Nullable
present()55     public abstract Boolean present();
56 
57     // Matches header value with the prefix.
58     @Nullable
prefix()59     public abstract String prefix();
60 
61     // Matches header value with the suffix.
62     @Nullable
suffix()63     public abstract String suffix();
64 
65     // Matches header value with the substring.
66     @Nullable
contains()67     public abstract String contains();
68 
69     // Matches header value with the string matcher.
70     @Nullable
stringMatcher()71     public abstract StringMatcher stringMatcher();
72 
73     // Whether the matching semantics is inverted. E.g., present && !inverted -> !present
inverted()74     public abstract boolean inverted();
75 
76     /** The request header value should exactly match the specified value. */
forExactValue(String name, String exactValue, boolean inverted)77     public static HeaderMatcher forExactValue(String name, String exactValue, boolean inverted) {
78       checkNotNull(name, "name");
79       checkNotNull(exactValue, "exactValue");
80       return HeaderMatcher.create(
81         name, exactValue, null, null, null, null, null, null, null, inverted);
82     }
83 
84     /** The request header value should match the regular expression pattern. */
forSafeRegEx(String name, Pattern safeRegEx, boolean inverted)85     public static HeaderMatcher forSafeRegEx(String name, Pattern safeRegEx, boolean inverted) {
86       checkNotNull(name, "name");
87       checkNotNull(safeRegEx, "safeRegEx");
88       return HeaderMatcher.create(
89         name, null, safeRegEx, null, null, null, null, null, null, inverted);
90     }
91 
92     /** The request header value should be within the range. */
forRange(String name, Range range, boolean inverted)93     public static HeaderMatcher forRange(String name, Range range, boolean inverted) {
94       checkNotNull(name, "name");
95       checkNotNull(range, "range");
96       return HeaderMatcher.create(name, null, null, range, null, null, null, null, null, inverted);
97     }
98 
99     /** The request header value should exist. */
forPresent(String name, boolean present, boolean inverted)100     public static HeaderMatcher forPresent(String name, boolean present, boolean inverted) {
101       checkNotNull(name, "name");
102       return HeaderMatcher.create(
103         name, null, null, null, present, null, null, null, null, inverted);
104     }
105 
106     /** The request header value should have this prefix. */
forPrefix(String name, String prefix, boolean inverted)107     public static HeaderMatcher forPrefix(String name, String prefix, boolean inverted) {
108       checkNotNull(name, "name");
109       checkNotNull(prefix, "prefix");
110       return HeaderMatcher.create(name, null, null, null, null, prefix, null, null, null, inverted);
111     }
112 
113     /** The request header value should have this suffix. */
forSuffix(String name, String suffix, boolean inverted)114     public static HeaderMatcher forSuffix(String name, String suffix, boolean inverted) {
115       checkNotNull(name, "name");
116       checkNotNull(suffix, "suffix");
117       return HeaderMatcher.create(name, null, null, null, null, null, suffix, null, null, inverted);
118     }
119 
120     /** The request header value should have this substring. */
forContains(String name, String contains, boolean inverted)121     public static HeaderMatcher forContains(String name, String contains, boolean inverted) {
122       checkNotNull(name, "name");
123       checkNotNull(contains, "contains");
124       return HeaderMatcher.create(
125         name, null, null, null, null, null, null, contains, null, inverted);
126     }
127 
128     /** The request header value should match this stringMatcher. */
forString( String name, StringMatcher stringMatcher, boolean inverted)129     public static HeaderMatcher forString(
130         String name, StringMatcher stringMatcher, boolean inverted) {
131       checkNotNull(name, "name");
132       checkNotNull(stringMatcher, "stringMatcher");
133       return HeaderMatcher.create(
134         name, null, null, null, null, null, null, null, stringMatcher, inverted);
135     }
136 
create(String name, @Nullable String exactValue, @Nullable Pattern safeRegEx, @Nullable Range range, @Nullable Boolean present, @Nullable String prefix, @Nullable String suffix, @Nullable String contains, @Nullable StringMatcher stringMatcher, boolean inverted)137     private static HeaderMatcher create(String name, @Nullable String exactValue,
138         @Nullable Pattern safeRegEx, @Nullable Range range,
139         @Nullable Boolean present, @Nullable String prefix,
140         @Nullable String suffix, @Nullable String contains,
141         @Nullable StringMatcher stringMatcher, boolean inverted) {
142       checkNotNull(name, "name");
143       return new AutoValue_Matchers_HeaderMatcher(name, exactValue, safeRegEx, range, present,
144           prefix, suffix, contains, stringMatcher, inverted);
145     }
146 
147     /** Returns the matching result. */
matches(@ullable String value)148     public boolean matches(@Nullable String value) {
149       if (value == null) {
150         return present() != null && present() == inverted();
151       }
152       boolean baseMatch;
153       if (exactValue() != null) {
154         baseMatch = exactValue().equals(value);
155       } else if (safeRegEx() != null) {
156         baseMatch = safeRegEx().matches(value);
157       } else if (range() != null) {
158         long numValue;
159         try {
160           numValue = Long.parseLong(value);
161           baseMatch = numValue >= range().start()
162               && numValue <= range().end();
163         } catch (NumberFormatException ignored) {
164           baseMatch = false;
165         }
166       } else if (prefix() != null) {
167         baseMatch = value.startsWith(prefix());
168       } else if (present() != null) {
169         baseMatch = present();
170       } else if (suffix() != null) {
171         baseMatch = value.endsWith(suffix());
172       } else if (contains() != null) {
173         baseMatch = value.contains(contains());
174       } else {
175         baseMatch = stringMatcher().matches(value);
176       }
177       return baseMatch != inverted();
178     }
179 
180     /** Represents an integer range. */
181     @AutoValue
182     public abstract static class Range {
start()183       public abstract long start();
184 
end()185       public abstract long end();
186 
create(long start, long end)187       public static Range create(long start, long end) {
188         return new AutoValue_Matchers_HeaderMatcher_Range(start, end);
189       }
190     }
191   }
192 
193   /** Represents a fractional value. */
194   @AutoValue
195   public abstract static class FractionMatcher {
numerator()196     public abstract int numerator();
197 
denominator()198     public abstract int denominator();
199 
create(int numerator, int denominator)200     public static FractionMatcher create(int numerator, int denominator) {
201       return new AutoValue_Matchers_FractionMatcher(numerator, denominator);
202     }
203   }
204 
205   /** Represents various ways to match a string .*/
206   @AutoValue
207   public abstract static class StringMatcher {
208     @Nullable
exact()209     abstract String exact();
210 
211     // The input string has this prefix.
212     @Nullable
prefix()213     abstract String prefix();
214 
215     // The input string has this suffix.
216     @Nullable
suffix()217     abstract String suffix();
218 
219     // The input string matches the regular expression.
220     @Nullable
regEx()221     abstract Pattern regEx();
222 
223     // The input string has this substring.
224     @Nullable
contains()225     abstract String contains();
226 
227     // If true, exact/prefix/suffix matching should be case insensitive.
ignoreCase()228     abstract boolean ignoreCase();
229 
230     /** The input string should exactly matches the specified string. */
forExact(String exact, boolean ignoreCase)231     public static StringMatcher forExact(String exact, boolean ignoreCase) {
232       checkNotNull(exact, "exact");
233       return StringMatcher.create(exact, null, null, null, null,
234           ignoreCase);
235     }
236 
237     /** The input string should have the prefix. */
forPrefix(String prefix, boolean ignoreCase)238     public static StringMatcher forPrefix(String prefix, boolean ignoreCase) {
239       checkNotNull(prefix, "prefix");
240       return StringMatcher.create(null, prefix, null, null, null,
241           ignoreCase);
242     }
243 
244     /** The input string should have the suffix. */
forSuffix(String suffix, boolean ignoreCase)245     public static StringMatcher forSuffix(String suffix, boolean ignoreCase) {
246       checkNotNull(suffix, "suffix");
247       return StringMatcher.create(null, null, suffix, null, null,
248           ignoreCase);
249     }
250 
251     /** The input string should match this pattern. */
forSafeRegEx(Pattern regEx)252     public static StringMatcher forSafeRegEx(Pattern regEx) {
253       checkNotNull(regEx, "regEx");
254       return StringMatcher.create(null, null, null, regEx, null,
255           false/* doesn't matter */);
256     }
257 
258     /** The input string should contain this substring. */
forContains(String contains)259     public static StringMatcher forContains(String contains) {
260       checkNotNull(contains, "contains");
261       return StringMatcher.create(null, null, null, null, contains,
262           false/* doesn't matter */);
263     }
264 
265     /** Returns the matching result for this string. */
matches(String args)266     public boolean matches(String args) {
267       if (args == null) {
268         return false;
269       }
270       if (exact() != null) {
271         return ignoreCase()
272             ? exact().equalsIgnoreCase(args)
273             : exact().equals(args);
274       } else if (prefix() != null) {
275         return ignoreCase()
276             ? args.toLowerCase().startsWith(prefix().toLowerCase())
277             : args.startsWith(prefix());
278       } else if (suffix() != null) {
279         return ignoreCase()
280             ? args.toLowerCase().endsWith(suffix().toLowerCase())
281             : args.endsWith(suffix());
282       } else if (contains() != null) {
283         return args.contains(contains());
284       }
285       return regEx().matches(args);
286     }
287 
create(@ullable String exact, @Nullable String prefix, @Nullable String suffix, @Nullable Pattern regEx, @Nullable String contains, boolean ignoreCase)288     private static StringMatcher create(@Nullable String exact, @Nullable String prefix,
289         @Nullable String suffix, @Nullable Pattern regEx, @Nullable String contains,
290         boolean ignoreCase) {
291       return new AutoValue_Matchers_StringMatcher(exact, prefix, suffix, regEx, contains,
292           ignoreCase);
293     }
294   }
295 
296   /** Matcher to evaluate whether an IPv4 or IPv6 address is within a CIDR range. */
297   @AutoValue
298   public abstract static class CidrMatcher {
299 
addressPrefix()300     abstract InetAddress addressPrefix();
301 
prefixLen()302     abstract int prefixLen();
303 
304     /** Returns matching result for this address. */
matches(InetAddress address)305     public boolean matches(InetAddress address) {
306       if (address == null) {
307         return false;
308       }
309       byte[] cidr = addressPrefix().getAddress();
310       byte[] addr = address.getAddress();
311       if (addr.length != cidr.length) {
312         return false;
313       }
314       BigInteger cidrInt = new BigInteger(cidr);
315       BigInteger addrInt = new BigInteger(addr);
316 
317       int shiftAmount = 8 * cidr.length - prefixLen();
318 
319       cidrInt = cidrInt.shiftRight(shiftAmount);
320       addrInt = addrInt.shiftRight(shiftAmount);
321       return cidrInt.equals(addrInt);
322     }
323 
324     /** Constructs a CidrMatcher with this prefix and prefix length.
325      * Do not provide string addressPrefix constructor to avoid IO exception handling.
326      * */
create(InetAddress addressPrefix, int prefixLen)327     public static CidrMatcher create(InetAddress addressPrefix, int prefixLen) {
328       return new AutoValue_Matchers_CidrMatcher(addressPrefix, prefixLen);
329     }
330   }
331 }
332