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