• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #19126

28 Mar 2024 04:54AM UTC coverage: 88.247% (-0.03%) from 88.274%
#19126

push

github

web-flow
Specify a locale for upper/lower case conversions (1.63.x backport)

None of these conversions should use the arbitrary system locale. Error
Prone will help prevent these getting introduced in the future.

Fixes #10372

31204 of 35360 relevant lines covered (88.25%)

0.88 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.94
/../xds/src/main/java/io/grpc/xds/internal/Matchers.java
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 java.util.Locale;
26
import javax.annotation.Nullable;
27

28
/**
29
 * Provides a group of request matchers. A matcher evaluates an input and tells whether certain
30
 * argument in the input matches a predefined matching pattern.
31
 */
32
public final class Matchers {
33
  // Prevent instantiation.
34
  private Matchers() {}
35

36
  /** Matcher for HTTP request headers. */
37
  @AutoValue
38
  public abstract static class HeaderMatcher {
1✔
39
    // Name of the header to be matched.
40
    public abstract String name();
41

42
    // Matches exact header value.
43
    @Nullable
44
    public abstract String exactValue();
45

46
    // Matches header value with the regular expression pattern.
47
    @Nullable
48
    public abstract Pattern safeRegEx();
49

50
    // Matches header value an integer value in the range.
51
    @Nullable
52
    public abstract Range range();
53

54
    // Matches header presence.
55
    @Nullable
56
    public abstract Boolean present();
57

58
    // Matches header value with the prefix.
59
    @Nullable
60
    public abstract String prefix();
61

62
    // Matches header value with the suffix.
63
    @Nullable
64
    public abstract String suffix();
65

66
    // Matches header value with the substring.
67
    @Nullable
68
    public abstract String contains();
69

70
    // Matches header value with the string matcher.
71
    @Nullable
72
    public abstract StringMatcher stringMatcher();
73

74
    // Whether the matching semantics is inverted. E.g., present && !inverted -> !present
75
    public abstract boolean inverted();
76

77
    /** The request header value should exactly match the specified value. */
78
    public static HeaderMatcher forExactValue(String name, String exactValue, boolean inverted) {
79
      checkNotNull(name, "name");
1✔
80
      checkNotNull(exactValue, "exactValue");
1✔
81
      return HeaderMatcher.create(
1✔
82
        name, exactValue, null, null, null, null, null, null, null, inverted);
83
    }
84

85
    /** The request header value should match the regular expression pattern. */
86
    public static HeaderMatcher forSafeRegEx(String name, Pattern safeRegEx, boolean inverted) {
87
      checkNotNull(name, "name");
1✔
88
      checkNotNull(safeRegEx, "safeRegEx");
1✔
89
      return HeaderMatcher.create(
1✔
90
        name, null, safeRegEx, null, null, null, null, null, null, inverted);
91
    }
92

93
    /** The request header value should be within the range. */
94
    public static HeaderMatcher forRange(String name, Range range, boolean inverted) {
95
      checkNotNull(name, "name");
1✔
96
      checkNotNull(range, "range");
1✔
97
      return HeaderMatcher.create(name, null, null, range, null, null, null, null, null, inverted);
1✔
98
    }
99

100
    /** The request header value should exist. */
101
    public static HeaderMatcher forPresent(String name, boolean present, boolean inverted) {
102
      checkNotNull(name, "name");
1✔
103
      return HeaderMatcher.create(
1✔
104
        name, null, null, null, present, null, null, null, null, inverted);
1✔
105
    }
106

107
    /** The request header value should have this prefix. */
108
    public static HeaderMatcher forPrefix(String name, String prefix, boolean inverted) {
109
      checkNotNull(name, "name");
1✔
110
      checkNotNull(prefix, "prefix");
1✔
111
      return HeaderMatcher.create(name, null, null, null, null, prefix, null, null, null, inverted);
1✔
112
    }
113

114
    /** The request header value should have this suffix. */
115
    public static HeaderMatcher forSuffix(String name, String suffix, boolean inverted) {
116
      checkNotNull(name, "name");
1✔
117
      checkNotNull(suffix, "suffix");
1✔
118
      return HeaderMatcher.create(name, null, null, null, null, null, suffix, null, null, inverted);
1✔
119
    }
120

121
    /** The request header value should have this substring. */
122
    public static HeaderMatcher forContains(String name, String contains, boolean inverted) {
123
      checkNotNull(name, "name");
1✔
124
      checkNotNull(contains, "contains");
1✔
125
      return HeaderMatcher.create(
1✔
126
        name, null, null, null, null, null, null, contains, null, inverted);
127
    }
128

129
    /** The request header value should match this stringMatcher. */
130
    public static HeaderMatcher forString(
131
        String name, StringMatcher stringMatcher, boolean inverted) {
132
      checkNotNull(name, "name");
1✔
133
      checkNotNull(stringMatcher, "stringMatcher");
1✔
134
      return HeaderMatcher.create(
1✔
135
        name, null, null, null, null, null, null, null, stringMatcher, inverted);
136
    }
137

138
    private static HeaderMatcher create(String name, @Nullable String exactValue,
139
        @Nullable Pattern safeRegEx, @Nullable Range range,
140
        @Nullable Boolean present, @Nullable String prefix,
141
        @Nullable String suffix, @Nullable String contains,
142
        @Nullable StringMatcher stringMatcher, boolean inverted) {
143
      checkNotNull(name, "name");
1✔
144
      return new AutoValue_Matchers_HeaderMatcher(name, exactValue, safeRegEx, range, present,
1✔
145
          prefix, suffix, contains, stringMatcher, inverted);
146
    }
147

148
    /** Returns the matching result. */
149
    public boolean matches(@Nullable String value) {
150
      if (value == null) {
1✔
151
        return present() != null && present() == inverted();
1✔
152
      }
153
      boolean baseMatch;
154
      if (exactValue() != null) {
1✔
155
        baseMatch = exactValue().equals(value);
1✔
156
      } else if (safeRegEx() != null) {
1✔
157
        baseMatch = safeRegEx().matches(value);
1✔
158
      } else if (range() != null) {
1✔
159
        long numValue;
160
        try {
161
          numValue = Long.parseLong(value);
1✔
162
          baseMatch = numValue >= range().start()
1✔
163
              && numValue <= range().end();
1✔
164
        } catch (NumberFormatException ignored) {
×
165
          baseMatch = false;
×
166
        }
1✔
167
      } else if (prefix() != null) {
1✔
168
        baseMatch = value.startsWith(prefix());
1✔
169
      } else if (present() != null) {
1✔
170
        baseMatch = present();
1✔
171
      } else if (suffix() != null) {
1✔
172
        baseMatch = value.endsWith(suffix());
1✔
173
      } else if (contains() != null) {
1✔
174
        baseMatch = value.contains(contains());
1✔
175
      } else {
176
        baseMatch = stringMatcher().matches(value);
1✔
177
      }
178
      return baseMatch != inverted();
1✔
179
    }
180

181
    /** Represents an integer range. */
182
    @AutoValue
183
    public abstract static class Range {
1✔
184
      public abstract long start();
185

186
      public abstract long end();
187

188
      public static Range create(long start, long end) {
189
        return new AutoValue_Matchers_HeaderMatcher_Range(start, end);
1✔
190
      }
191
    }
192
  }
193

194
  /** Represents a fractional value. */
195
  @AutoValue
196
  public abstract static class FractionMatcher {
1✔
197
    public abstract int numerator();
198

199
    public abstract int denominator();
200

201
    public static FractionMatcher create(int numerator, int denominator) {
202
      return new AutoValue_Matchers_FractionMatcher(numerator, denominator);
1✔
203
    }
204
  }
205

206
  /** Represents various ways to match a string .*/
207
  @AutoValue
208
  public abstract static class StringMatcher {
1✔
209
    @Nullable
210
    abstract String exact();
211

212
    // The input string has this prefix.
213
    @Nullable
214
    abstract String prefix();
215

216
    // The input string has this suffix.
217
    @Nullable
218
    abstract String suffix();
219

220
    // The input string matches the regular expression.
221
    @Nullable
222
    abstract Pattern regEx();
223

224
    // The input string has this substring.
225
    @Nullable
226
    abstract String contains();
227

228
    // If true, exact/prefix/suffix matching should be case insensitive.
229
    abstract boolean ignoreCase();
230

231
    /** The input string should exactly matches the specified string. */
232
    public static StringMatcher forExact(String exact, boolean ignoreCase) {
233
      checkNotNull(exact, "exact");
1✔
234
      return StringMatcher.create(exact, null, null, null, null,
1✔
235
          ignoreCase);
236
    }
237

238
    /** The input string should have the prefix. */
239
    public static StringMatcher forPrefix(String prefix, boolean ignoreCase) {
240
      checkNotNull(prefix, "prefix");
1✔
241
      return StringMatcher.create(null, prefix, null, null, null,
1✔
242
          ignoreCase);
243
    }
244

245
    /** The input string should have the suffix. */
246
    public static StringMatcher forSuffix(String suffix, boolean ignoreCase) {
247
      checkNotNull(suffix, "suffix");
1✔
248
      return StringMatcher.create(null, null, suffix, null, null,
1✔
249
          ignoreCase);
250
    }
251

252
    /** The input string should match this pattern. */
253
    public static StringMatcher forSafeRegEx(Pattern regEx) {
254
      checkNotNull(regEx, "regEx");
1✔
255
      return StringMatcher.create(null, null, null, regEx, null,
1✔
256
          false/* doesn't matter */);
257
    }
258

259
    /** The input string should contain this substring. */
260
    public static StringMatcher forContains(String contains) {
261
      checkNotNull(contains, "contains");
1✔
262
      return StringMatcher.create(null, null, null, null, contains,
1✔
263
          false/* doesn't matter */);
264
    }
265

266
    /** Returns the matching result for this string. */
267
    public boolean matches(String args) {
268
      if (args == null) {
1✔
269
        return false;
1✔
270
      }
271
      if (exact() != null) {
1✔
272
        return ignoreCase()
1✔
273
            ? exact().equalsIgnoreCase(args)
1✔
274
            : exact().equals(args);
1✔
275
      } else if (prefix() != null) {
1✔
276
        return ignoreCase()
1✔
277
            ? args.toLowerCase(Locale.ROOT).startsWith(prefix().toLowerCase(Locale.ROOT))
1✔
278
            : args.startsWith(prefix());
1✔
279
      } else if (suffix() != null) {
1✔
280
        return ignoreCase()
1✔
281
            ? args.toLowerCase(Locale.ROOT).endsWith(suffix().toLowerCase(Locale.ROOT))
1✔
282
            : args.endsWith(suffix());
1✔
283
      } else if (contains() != null) {
1✔
284
        return args.contains(contains());
1✔
285
      }
286
      return regEx().matches(args);
1✔
287
    }
288

289
    private static StringMatcher create(@Nullable String exact, @Nullable String prefix,
290
        @Nullable String suffix, @Nullable Pattern regEx, @Nullable String contains,
291
        boolean ignoreCase) {
292
      return new AutoValue_Matchers_StringMatcher(exact, prefix, suffix, regEx, contains,
1✔
293
          ignoreCase);
294
    }
295
  }
296

297
  /** Matcher to evaluate whether an IPv4 or IPv6 address is within a CIDR range. */
298
  @AutoValue
299
  public abstract static class CidrMatcher {
1✔
300

301
    abstract InetAddress addressPrefix();
302

303
    abstract int prefixLen();
304

305
    /** Returns matching result for this address. */
306
    public boolean matches(InetAddress address) {
307
      if (address == null) {
1✔
308
        return false;
1✔
309
      }
310
      byte[] cidr = addressPrefix().getAddress();
1✔
311
      byte[] addr = address.getAddress();
1✔
312
      if (addr.length != cidr.length) {
1✔
313
        return false;
1✔
314
      }
315
      BigInteger cidrInt = new BigInteger(cidr);
1✔
316
      BigInteger addrInt = new BigInteger(addr);
1✔
317

318
      int shiftAmount = 8 * cidr.length - prefixLen();
1✔
319

320
      cidrInt = cidrInt.shiftRight(shiftAmount);
1✔
321
      addrInt = addrInt.shiftRight(shiftAmount);
1✔
322
      return cidrInt.equals(addrInt);
1✔
323
    }
324

325
    /** Constructs a CidrMatcher with this prefix and prefix length.
326
     * Do not provide string addressPrefix constructor to avoid IO exception handling.
327
     * */
328
    public static CidrMatcher create(InetAddress addressPrefix, int prefixLen) {
329
      return new AutoValue_Matchers_CidrMatcher(addressPrefix, prefixLen);
1✔
330
    }
331
  }
332
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc