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

grpc / grpc-java / #18610

pending completion
#18610

push

github-actions

ejona86
xds: Avoid default locale and reset()

Found via linter

30837 of 34988 relevant lines covered (88.14%)

0.88 hits per line

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

46.07
/../xds/src/main/java/io/grpc/xds/RoutingUtils.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;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20

21
import com.google.common.base.Joiner;
22
import io.grpc.Metadata;
23
import io.grpc.xds.VirtualHost.Route.RouteMatch;
24
import io.grpc.xds.VirtualHost.Route.RouteMatch.PathMatcher;
25
import io.grpc.xds.internal.Matchers.FractionMatcher;
26
import io.grpc.xds.internal.Matchers.HeaderMatcher;
27
import java.util.List;
28
import java.util.Locale;
29
import javax.annotation.Nullable;
30

31
/**
32
 * Utilities for performing virtual host domain name matching and route matching.
33
 */
34
// TODO(chengyuanzhang): clean up implementations in XdsNameResolver.
35
final class RoutingUtils {
36
  // Prevent instantiation.
37
  private RoutingUtils() {
38
  }
39

40
  /**
41
   * Returns the {@link VirtualHost} with the best match domain for the given hostname.
42
   */
43
  @Nullable
44
  static VirtualHost findVirtualHostForHostName(List<VirtualHost> virtualHosts, String hostName) {
45
    // Domain search order:
46
    //  1. Exact domain names: ``www.foo.com``.
47
    //  2. Suffix domain wildcards: ``*.foo.com`` or ``*-bar.foo.com``.
48
    //  3. Prefix domain wildcards: ``foo.*`` or ``foo-*``.
49
    //  4. Special wildcard ``*`` matching any domain.
50
    //
51
    //  The longest wildcards match first.
52
    //  Assuming only a single virtual host in the entire route configuration can match
53
    //  on ``*`` and a domain must be unique across all virtual hosts.
54
    int matchingLen = -1; // longest length of wildcard pattern that matches host name
1✔
55
    boolean exactMatchFound = false;  // true if a virtual host with exactly matched domain found
1✔
56
    VirtualHost targetVirtualHost = null;  // target VirtualHost with longest matched domain
1✔
57
    for (VirtualHost vHost : virtualHosts) {
1✔
58
      for (String domain : vHost.domains()) {
1✔
59
        boolean selected = false;
1✔
60
        if (matchHostName(hostName, domain)) { // matching
1✔
61
          if (!domain.contains("*")) { // exact matching
1✔
62
            exactMatchFound = true;
1✔
63
            targetVirtualHost = vHost;
1✔
64
            break;
1✔
65
          } else if (domain.length() > matchingLen) { // longer matching pattern
1✔
66
            selected = true;
1✔
67
          } else if (domain.length() == matchingLen && domain.startsWith("*")) { // suffix matching
×
68
            selected = true;
×
69
          }
70
        }
71
        if (selected) {
1✔
72
          matchingLen = domain.length();
1✔
73
          targetVirtualHost = vHost;
1✔
74
        }
75
      }
1✔
76
      if (exactMatchFound) {
1✔
77
        break;
1✔
78
      }
79
    }
1✔
80
    return targetVirtualHost;
1✔
81
  }
82

83
  /**
84
   * Returns {@code true} iff {@code hostName} matches the domain name {@code pattern} with
85
   * case-insensitive.
86
   *
87
   * <p>Wildcard pattern rules:
88
   * <ol>
89
   * <li>A single asterisk (*) matches any domain.</li>
90
   * <li>Asterisk (*) is only permitted in the left-most or the right-most part of the pattern,
91
   *     but not both.</li>
92
   * </ol>
93
   */
94
  private static boolean matchHostName(String hostName, String pattern) {
95
    checkArgument(hostName.length() != 0 && !hostName.startsWith(".") && !hostName.endsWith("."),
1✔
96
        "Invalid host name");
97
    checkArgument(pattern.length() != 0 && !pattern.startsWith(".") && !pattern.endsWith("."),
1✔
98
        "Invalid pattern/domain name");
99

100
    hostName = hostName.toLowerCase(Locale.US);
1✔
101
    pattern = pattern.toLowerCase(Locale.US);
1✔
102
    // hostName and pattern are now in lower case -- domain names are case-insensitive.
103

104
    if (!pattern.contains("*")) {
1✔
105
      // Not a wildcard pattern -- hostName and pattern must match exactly.
106
      return hostName.equals(pattern);
1✔
107
    }
108
    // Wildcard pattern
109

110
    if (pattern.length() == 1) {
1✔
111
      return true;
1✔
112
    }
113

114
    int index = pattern.indexOf('*');
×
115

116
    // At most one asterisk (*) is allowed.
117
    if (pattern.indexOf('*', index + 1) != -1) {
×
118
      return false;
×
119
    }
120

121
    // Asterisk can only match prefix or suffix.
122
    if (index != 0 && index != pattern.length() - 1) {
×
123
      return false;
×
124
    }
125

126
    // HostName must be at least as long as the pattern because asterisk has to
127
    // match one or more characters.
128
    if (hostName.length() < pattern.length()) {
×
129
      return false;
×
130
    }
131

132
    if (index == 0 && hostName.endsWith(pattern.substring(1))) {
×
133
      // Prefix matching fails.
134
      return true;
×
135
    }
136

137
    // Pattern matches hostname if suffix matching succeeds.
138
    return index == pattern.length() - 1
×
139
        && hostName.startsWith(pattern.substring(0, pattern.length() - 1));
×
140
  }
141

142
  /**
143
   * Returns {@code true} iff the given {@link RouteMatch} matches the RPC's full method name and
144
   * headers.
145
   */
146
  static boolean matchRoute(RouteMatch routeMatch, String fullMethodName,
147
      Metadata headers, ThreadSafeRandom random) {
148
    if (!matchPath(routeMatch.pathMatcher(), fullMethodName)) {
1✔
149
      return false;
1✔
150
    }
151
    for (HeaderMatcher headerMatcher : routeMatch.headerMatchers()) {
1✔
152
      if (!matchHeader(headerMatcher, getHeaderValue(headers, headerMatcher.name()))) {
×
153
        return false;
×
154
      }
155
    }
×
156
    FractionMatcher fraction = routeMatch.fractionMatcher();
1✔
157
    return fraction == null || random.nextInt(fraction.denominator()) < fraction.numerator();
1✔
158
  }
159

160
  private static boolean matchPath(PathMatcher pathMatcher, String fullMethodName) {
161
    if (pathMatcher.path() != null) {
1✔
162
      return pathMatcher.caseSensitive()
1✔
163
          ? pathMatcher.path().equals(fullMethodName)
1✔
164
          : pathMatcher.path().equalsIgnoreCase(fullMethodName);
×
165
    } else if (pathMatcher.prefix() != null) {
1✔
166
      return pathMatcher.caseSensitive()
1✔
167
          ? fullMethodName.startsWith(pathMatcher.prefix())
×
168
          : fullMethodName.toLowerCase(Locale.US).startsWith(
1✔
169
              pathMatcher.prefix().toLowerCase(Locale.US));
1✔
170
    }
171
    return pathMatcher.regEx().matches(fullMethodName);
×
172
  }
173

174
  private static boolean matchHeader(HeaderMatcher headerMatcher, @Nullable String value) {
175
    if (headerMatcher.present() != null) {
×
176
      return (value == null) == headerMatcher.present().equals(headerMatcher.inverted());
×
177
    }
178
    if (value == null) {
×
179
      return false;
×
180
    }
181
    boolean baseMatch;
182
    if (headerMatcher.exactValue() != null) {
×
183
      baseMatch = headerMatcher.exactValue().equals(value);
×
184
    } else if (headerMatcher.safeRegEx() != null) {
×
185
      baseMatch = headerMatcher.safeRegEx().matches(value);
×
186
    } else if (headerMatcher.range() != null) {
×
187
      long numValue;
188
      try {
189
        numValue = Long.parseLong(value);
×
190
        baseMatch = numValue >= headerMatcher.range().start()
×
191
            && numValue <= headerMatcher.range().end();
×
192
      } catch (NumberFormatException ignored) {
×
193
        baseMatch = false;
×
194
      }
×
195
    } else if (headerMatcher.prefix() != null) {
×
196
      baseMatch = value.startsWith(headerMatcher.prefix());
×
197
    } else {
198
      baseMatch = value.endsWith(headerMatcher.suffix());
×
199
    }
200
    return baseMatch != headerMatcher.inverted();
×
201
  }
202

203
  @Nullable
204
  private static String getHeaderValue(Metadata headers, String headerName) {
205
    if (headerName.endsWith(Metadata.BINARY_HEADER_SUFFIX)) {
×
206
      return null;
×
207
    }
208
    if (headerName.equals("content-type")) {
×
209
      return "application/grpc";
×
210
    }
211
    Metadata.Key<String> key;
212
    try {
213
      key = Metadata.Key.of(headerName, Metadata.ASCII_STRING_MARSHALLER);
×
214
    } catch (IllegalArgumentException e) {
×
215
      return null;
×
216
    }
×
217
    Iterable<String> values = headers.getAll(key);
×
218
    return values == null ? null : Joiner.on(",").join(values);
×
219
  }
220
}
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