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

grpc / grpc-java / #20388

03 Aug 2026 06:02PM UTC coverage: 89.118% (-0.03%) from 89.149%
#20388

push

github

jdcormie
api: Remove io.grpc.Uri#isAbsolute()

Javadoc says this method only exists for compatibility with java.net.URI
but the meaning of "absolute" actually changed from RFC 2396 to 3986 so
isAbsolute() is more of a trap than a convenience.

io.grpc.Uri intentionally only models URIs, not URI references. So under 
the RFC 2396 definition of absolute, every instance is absolute because it
has a scheme.

Removing isAbsolute() also avoids confusion with absolute paths, an entirely 
different concept.

38296 of 42972 relevant lines covered (89.12%)

0.89 hits per line

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

98.06
/../api/src/main/java/io/grpc/Uri.java
1
/*
2
 * Copyright 2025 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;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static com.google.common.base.Preconditions.checkState;
22

23
import com.google.common.base.VerifyException;
24
import com.google.common.collect.ImmutableList;
25
import com.google.common.net.InetAddresses;
26
import com.google.errorprone.annotations.CanIgnoreReturnValue;
27
import java.net.InetAddress;
28
import java.net.URISyntaxException;
29
import java.nio.ByteBuffer;
30
import java.nio.CharBuffer;
31
import java.nio.charset.CharacterCodingException;
32
import java.nio.charset.CharsetEncoder;
33
import java.nio.charset.CodingErrorAction;
34
import java.nio.charset.MalformedInputException;
35
import java.nio.charset.StandardCharsets;
36
import java.util.BitSet;
37
import java.util.List;
38
import java.util.Locale;
39
import java.util.Objects;
40
import javax.annotation.Nullable;
41

42
/**
43
 * A not-quite-general-purpose representation of a Uniform Resource Identifier (URI), as defined by
44
 * <a href="https://datatracker.ietf.org/doc/html/rfc3986">RFC 3986</a>.
45
 *
46
 * <h1>The URI</h1>
47
 *
48
 * <p>A URI identifies a resource by its name or location or both. The resource could be a file,
49
 * service, or some other abstract entity.
50
 *
51
 * <h2>Examples</h2>
52
 *
53
 * <ul>
54
 *   <li><code>http://admin@example.com:8080/controlpanel?filter=users#settings</code>
55
 *   <li><code>ftp://[2001:db8::7]/docs/report.pdf</code>
56
 *   <li><code>file:///My%20Computer/Documents/letter.doc</code>
57
 *   <li><code>dns://8.8.8.8/storage.googleapis.com</code>
58
 *   <li><code>mailto:John.Doe@example.com</code>
59
 *   <li><code>tel:+1-206-555-1212</code>
60
 *   <li><code>urn:isbn:978-1492082798</code>
61
 * </ul>
62
 *
63
 * <h2>Limitations</h2>
64
 *
65
 * <p>This class aims to meet the needs of grpc-java itself and RPC related code that depend on it.
66
 * It isn't quite general-purpose. It definitely would not be suitable for building an HTTP user
67
 * agent or proxy server. In particular, it:
68
 *
69
 * <ul>
70
 *   <li>Can only represent a URI, not a "URI-reference" or "relative reference". In other words, a
71
 *       "scheme" is always required.
72
 *   <li>Has no knowledge of the particulars of any scheme, with respect to normalization and
73
 *       comparison. We don't know <code>https://google.com</code> is the same as <code>
74
 *       https://google.com:443</code>, that <code>file:///</code> is the same as <code>
75
 *       file://localhost</code>, or that <code>joe@example.com</code> is the same as <code>
76
 *       joe@EXAMPLE.COM</code>. No one class can or should know everything about every scheme so
77
 *       all this is better handled at a higher layer.
78
 *   <li>Implements {@link #equals(Object)} as a char-by-char comparison. Expect false negatives.
79
 *   <li>Does not support "IPvFuture" literal addresses.
80
 *   <li>Does not reflect how web browsers parse user input or the <a
81
 *       href="https://url.spec.whatwg.org/">URL Living Standard</a>.
82
 *   <li>Does not support different character encodings. Assumes UTF-8 in several places.
83
 * </ul>
84
 *
85
 * <h2>Migrating from RFC 2396 and {@link java.net.URI}</h2>
86
 *
87
 * <p>Those migrating from {@link java.net.URI} and/or its primary specification in RFC 2396 should
88
 * note some differences.
89
 *
90
 * <h3>Uniform Hierarchical Syntax</h3>
91
 *
92
 * <p>RFC 3986 unifies the older ideas of "hierarchical" and "opaque" URIs into a single generic
93
 * syntax. What RFC 2396 called an opaque "scheme-specific part" is always broken out by RFC 3986
94
 * into an authority and path hierarchy, followed by query and fragment components. Accordingly,
95
 * this class has only getters for those components but no {@link
96
 * java.net.URI#getSchemeSpecificPart()} analog.
97
 *
98
 * <p>The RFC 3986 definition of path is now more liberal to accommodate this:
99
 *
100
 * <ul>
101
 *   <li>Path doesn't have to start with a slash. For example, the path of <code>
102
 *       urn:isbn:978-1492082798</code> is <code>isbn:978-1492082798</code> even though it doesn't
103
 *       look much like a file system path.
104
 *   <li>The path can now be empty. So Android's <code>
105
 *       intent:#Intent;action=MAIN;category=LAUNCHER;end</code> is now a valid {@link Uri}. Even
106
 *       the scheme-only <code>about:</code> is now valid.
107
 * </ul>
108
 *
109
 * <p>The uniform syntax always understands what follows a '?' to be a query string. For example,
110
 * <code>mailto:me@example.com?subject=foo</code> now has a query component whereas RFC 2396
111
 * considered everything after the <code>mailto:</code> scheme to be opaque.
112
 *
113
 * <p>Same goes for fragment. <code>data:image/png;...#xywh=0,0,10,10</code> now has a fragment
114
 * whereas RFC 2396 considered everything after the scheme to be opaque.
115
 *
116
 * <h3>Uniform Authority Syntax</h3>
117
 *
118
 * <p>RFC 2396 tried to guess if an authority was a "server" (host:port) or "registry-based"
119
 * (arbitrary string) based on its contents. RFC 3986 expects every authority to look like
120
 * [userinfo@]host[:port] and loosens the definition of a "host" to accommodate. Accordingly, this
121
 * class has no equivalent to {@link java.net.URI#parseServerAuthority()} -- authority was parsed
122
 * into its components and checked for validity when the {@link Uri} was created.
123
 *
124
 * <h3>Other Specific Differences</h3>
125
 *
126
 * <p>RFC 2396 does not allow underscores in a host name, meaning {@link java.net.URI} switches to
127
 * opaque mode when it sees one. {@link Uri} does allow underscores in host, to accommodate
128
 * registries other than DNS. So <code>http://my_site.com:8080/index.html</code> now parses as a
129
 * host, port and path rather than a single opaque scheme-specific part.
130
 *
131
 * <p>{@link Uri} strictly *requires* square brackets in the query string and fragment to be
132
 * percent-encoded whereas RFC 2396 merely recommended doing so.
133
 *
134
 * <p>Other URx classes are "liberal in what they accept and strict in what they produce." {@link
135
 * Uri#parse(String)} and {@link Uri#create(String)}, however, are strict in what they accept and
136
 * transparent when asked to reproduce it via {@link Uri#toString()}. The former policy may be
137
 * appropriate for parsing user input or web content, but this class is meant for gRPC clients,
138
 * servers and plugins like name resolvers where human error at runtime is less likely and best
139
 * detected early. {@link java.net.URI#create(String)} is similarly strict, which makes migration
140
 * easy, except for the server/registry-based ambiguity addressed by {@link
141
 * java.net.URI#parseServerAuthority()}.
142
 *
143
 * <p>{@link java.net.URI} and {@link Uri} both support IPv6 literals in square brackets as defined
144
 * by RFC 2732.
145
 *
146
 * <p>{@link java.net.URI} supports IPv6 scope IDs but accepts and emits a non-standard syntax.
147
 * {@link Uri} implements the newer RFC 6874, which percent encodes scope IDs and the % delimiter
148
 * itself. RFC 9844 claims to obsolete RFC 6874 because web browsers would not support it. This
149
 * class implements RFC 6874 anyway, mostly to avoid creating a barrier to migration away from
150
 * {@link java.net.URI}.
151
 *
152
 * <p>Some URI components, e.g. scheme, are required while others may or may not be present, e.g.
153
 * authority. {@link Uri} is careful to preserve the distinction between an absent string component
154
 * (getter returns null) and one with an empty value (getter returns ""). {@link java.net.URI} makes
155
 * this distinction too, *except* when it comes to the authority and host components: {@link
156
 * java.net.URI#getAuthority()} and {@link java.net.URI#getHost()} return null when an authority is
157
 * absent, e.g. <code>file:/path</code> as expected. But these methods surprisingly also return null
158
 * when the authority is the empty string, e.g.<code>file:///path</code>. {@link Uri}'s getters
159
 * correctly return null and "" in these cases, respectively, as one would expect.
160
 */
161
@Internal
162
public final class Uri {
163
  // Components are stored percent-encoded, just as originally parsed for transparent parse/toString
164
  // round-tripping.
165
  private final String scheme; // != null since we don't support relative references.
166
  @Nullable private final String userInfo;
167
  @Nullable private final String host;
168
  @Nullable private final String port;
169
  private final String path; // In RFC 3986, path is always defined (but can be empty).
170
  @Nullable private final String query;
171
  @Nullable private final String fragment;
172

173
  private Uri(Builder builder) {
1✔
174
    this.scheme = checkNotNull(builder.scheme, "scheme");
1✔
175
    this.userInfo = builder.userInfo;
1✔
176
    this.host = builder.host;
1✔
177
    this.port = builder.port;
1✔
178
    this.path = builder.path;
1✔
179
    this.query = builder.query;
1✔
180
    this.fragment = builder.fragment;
1✔
181

182
    // Checks common to the parse() and Builder code paths.
183
    if (hasAuthority()) {
1✔
184
      if (!path.isEmpty() && !path.startsWith("/")) {
1✔
185
        throw new IllegalArgumentException("Has authority -- Non-empty path must start with '/'");
1✔
186
      }
187
    } else {
188
      if (path.startsWith("//")) {
1✔
189
        throw new IllegalArgumentException("No authority -- Path cannot start with '//'");
1✔
190
      }
191
    }
192
  }
1✔
193

194
  /**
195
   * Parses a URI from its string form.
196
   *
197
   * @throws URISyntaxException if 's' is not a valid RFC 3986 URI.
198
   */
199
  public static Uri parse(String s) throws URISyntaxException {
200
    try {
201
      return create(s);
1✔
202
    } catch (IllegalArgumentException e) {
1✔
203
      throw new URISyntaxException(s, e.getMessage());
1✔
204
    }
205
  }
206

207
  /**
208
   * Creates a URI from a string assumed to be valid.
209
   *
210
   * <p>Useful for defining URI constants in code. Not for user input.
211
   *
212
   * @throws IllegalArgumentException if 's' is not a valid RFC 3986 URI.
213
   */
214
  public static Uri create(String s) {
215
    Builder builder = new Builder();
1✔
216
    int i = 0;
1✔
217
    final int n = s.length();
1✔
218

219
    // 3.1. Scheme: Look for a ':' before '/', '?', or '#'.
220
    int schemeColon = -1;
1✔
221
    for (; i < n; ++i) {
1✔
222
      char c = s.charAt(i);
1✔
223
      if (c == ':') {
1✔
224
        schemeColon = i;
1✔
225
        break;
1✔
226
      } else if (c == '/' || c == '?' || c == '#') {
1✔
227
        break;
1✔
228
      }
229
    }
230
    if (schemeColon < 0) {
1✔
231
      throw new IllegalArgumentException("Missing required scheme.");
1✔
232
    }
233
    builder.setRawScheme(s.substring(0, schemeColon));
1✔
234

235
    // 3.2. Authority. Look for '//' then keep scanning until '/', '?', or '#'.
236
    i = schemeColon + 1;
1✔
237
    if (i + 1 < n && s.charAt(i) == '/' && s.charAt(i + 1) == '/') {
1✔
238
      // "//" just means we have an authority. Skip over it.
239
      i += 2;
1✔
240

241
      int authorityStart = i;
1✔
242
      for (; i < n; ++i) {
1✔
243
        char c = s.charAt(i);
1✔
244
        if (c == '/' || c == '?' || c == '#') {
1✔
245
          break;
1✔
246
        }
247
      }
248
      builder.setRawAuthority(s.substring(authorityStart, i));
1✔
249
    }
250

251
    // 3.3. Path: Whatever is left before '?' or '#'.
252
    int pathStart = i;
1✔
253
    for (; i < n; ++i) {
1✔
254
      char c = s.charAt(i);
1✔
255
      if (c == '?' || c == '#') {
1✔
256
        break;
1✔
257
      }
258
    }
259
    builder.setRawPath(s.substring(pathStart, i));
1✔
260

261
    // 3.4. Query, if we stopped at '?'.
262
    if (i < n && s.charAt(i) == '?') {
1✔
263
      i++; // Skip '?'
1✔
264
      int queryStart = i;
1✔
265
      for (; i < n; ++i) {
1✔
266
        char c = s.charAt(i);
1✔
267
        if (c == '#') {
1✔
268
          break;
1✔
269
        }
270
      }
271
      builder.setRawQuery(s.substring(queryStart, i));
1✔
272
    }
273

274
    // 3.5. Fragment, if we stopped at '#'.
275
    if (i < n && s.charAt(i) == '#') {
1✔
276
      ++i; // Skip '#'
1✔
277
      builder.setRawFragment(s.substring(i));
1✔
278
    }
279

280
    return builder.build();
1✔
281
  }
282

283
  private static int findPortStartColon(String authority, int hostStart) {
284
    for (int i = authority.length() - 1; i >= hostStart; --i) {
1✔
285
      char c = authority.charAt(i);
1✔
286
      if (c == ':') {
1✔
287
        return i;
1✔
288
      }
289
      if (c == ']') {
1✔
290
        // Hit the end of IP-literal. Any further colon is inside it and couldn't indicate a port.
291
        break;
1✔
292
      }
293
      if (!digitChars.get(c)) {
1✔
294
        // Found a non-digit, non-colon, non-bracket.
295
        // This means there is no valid port (e.g. host is "example.com")
296
        break;
1✔
297
      }
298
    }
299
    return -1;
1✔
300
  }
301

302
  // Checks a raw path for validity and parses it into segments. Let 'out' be null to just validate.
303
  private static void parseAssumedUtf8PathIntoSegments(
304
      String path, ImmutableList.Builder<String> out) {
305
    // Skip the first slash so it doesn't count as an empty segment at the start.
306
    // (e.g., "/a" -> ["a"], not ["", "a"])
307
    int start = path.startsWith("/") ? 1 : 0;
1✔
308

309
    for (int i = start; i < path.length(); ) {
1✔
310
      int nextSlash = path.indexOf('/', i);
1✔
311
      String segment;
312
      if (nextSlash >= 0) {
1✔
313
        // Typical segment case (e.g., "foo" in "/foo/bar").
314
        segment = path.substring(i, nextSlash);
1✔
315
        i = nextSlash + 1;
1✔
316
      } else {
317
        // Final segment case (e.g., "bar" in "/foo/bar").
318
        segment = path.substring(i);
1✔
319
        i = path.length();
1✔
320
      }
321
      if (out != null) {
1✔
322
        out.add(percentDecodeAssumedUtf8(segment));
1✔
323
      } else {
324
        checkPercentEncodedArg(segment, "path segment", pChars);
1✔
325
      }
326
    }
1✔
327

328
    // RFC 3986 says a trailing slash creates a final empty segment.
329
    // (e.g., "/foo/" -> ["foo", ""])
330
    if (path.endsWith("/") && out != null) {
1✔
331
      out.add("");
1✔
332
    }
333
  }
1✔
334

335
  /** Returns the scheme of this URI. */
336
  public String getScheme() {
337
    return scheme;
1✔
338
  }
339

340
  /**
341
   * Returns the percent-decoded "Authority" component of this URI, or null if not present.
342
   *
343
   * <p>NB: This method's decoding is lossy -- It only exists for compatibility with {@link
344
   * java.net.URI}. Prefer {@link #getRawAuthority()} or work instead with authority in terms of its
345
   * individual components ({@link #getUserInfo()}, {@link #getHost()} and {@link #getPort()}). The
346
   * problem with getAuthority() is that it returns the delimited concatenation of the percent-
347
   * decoded userinfo, host and port components. But both userinfo and host can contain the '@'
348
   * character, which becomes indistinguishable from the userinfo/host delimiter after decoding. For
349
   * example, URIs <code>scheme://x@y%40z</code> and <code>scheme://x%40y@z</code> have different
350
   * userinfo and host components but getAuthority() returns "x@y@z" for both of them.
351
   *
352
   * <p>NB: This method assumes the "host" component was encoded as UTF-8, as mandated by RFC 3986.
353
   * This method also assumes the "user information" part of authority was encoded as UTF-8,
354
   * although RFC 3986 doesn't specify an encoding.
355
   *
356
   * <p>Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
357
   * the output. Callers who want to detect and handle errors in some other way should call {@link
358
   * #getRawAuthority()}, {@link #percentDecode(CharSequence)}, then decode the bytes for
359
   * themselves.
360
   */
361
  @Nullable
362
  public String getAuthority() {
363
    return percentDecodeAssumedUtf8(getRawAuthority());
1✔
364
  }
365

366
  private boolean hasAuthority() {
367
    return host != null;
1✔
368
  }
369

370
  /**
371
   * Returns the "authority" component of this URI in its originally parsed, possibly
372
   * percent-encoded form.
373
   */
374
  @Nullable
375
  public String getRawAuthority() {
376
    if (hasAuthority()) {
1✔
377
      StringBuilder sb = new StringBuilder();
1✔
378
      appendAuthority(sb);
1✔
379
      return sb.toString();
1✔
380
    }
381
    return null;
1✔
382
  }
383

384
  private void appendAuthority(StringBuilder sb) {
385
    if (userInfo != null) {
1✔
386
      sb.append(userInfo).append('@');
1✔
387
    }
388
    if (host != null) {
1✔
389
      sb.append(host);
1✔
390
    }
391
    if (port != null) {
1✔
392
      sb.append(':').append(port);
1✔
393
    }
394
  }
1✔
395

396
  /**
397
   * Returns the percent-decoded "User Information" component of this URI, or null if not present.
398
   *
399
   * <p>NB: This method *assumes* this component was encoded as UTF-8, although RFC 3986 doesn't
400
   * specify an encoding.
401
   *
402
   * <p>Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
403
   * the output. Callers who want to detect and handle errors in some other way should call {@link
404
   * #getRawUserInfo()}, {@link #percentDecode(CharSequence)}, then decode the bytes for themselves.
405
   */
406
  @Nullable
407
  public String getUserInfo() {
408
    return percentDecodeAssumedUtf8(userInfo);
1✔
409
  }
410

411
  /**
412
   * Returns the "User Information" component of this URI in its originally parsed, possibly
413
   * percent-encoded form.
414
   */
415
  @Nullable
416
  public String getRawUserInfo() {
417
    return userInfo;
1✔
418
  }
419

420
  /**
421
   * Returns the percent-decoded "host" component of this URI, or null if not present.
422
   *
423
   * <p>This method assumes the host was encoded as UTF-8, as mandated by RFC 3986.
424
   *
425
   * <p>Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
426
   * the output. Callers who want to detect and handle errors in some other way should call {@link
427
   * #getRawHost()}, {@link #percentDecode(CharSequence)}, then decode the bytes for themselves.
428
   */
429
  @Nullable
430
  public String getHost() {
431
    return percentDecodeAssumedUtf8(host);
1✔
432
  }
433

434
  /**
435
   * Returns the host component of this URI in its originally parsed, possibly percent-encoded form.
436
   */
437
  @Nullable
438
  public String getRawHost() {
439
    return host;
1✔
440
  }
441

442
  /** Returns the "port" component of this URI, or -1 if empty or not present. */
443
  public int getPort() {
444
    return port != null && !port.isEmpty() ? Integer.parseInt(port) : -1;
1✔
445
  }
446

447
  /** Returns the raw port component of this URI in its originally parsed form. */
448
  @Nullable
449
  public String getRawPort() {
450
    return port;
1✔
451
  }
452

453
  /**
454
   * Returns the (possibly empty) percent-decoded "path" component of this URI.
455
   *
456
   * <p>NB: This method *assumes* the path was encoded as UTF-8, although RFC 3986 doesn't specify
457
   * an encoding.
458
   *
459
   * <p>Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
460
   * the output. Callers who want to detect and handle errors in some other way should call {@link
461
   * #getRawPath()}, {@link #percentDecode(CharSequence)}, then decode the bytes for themselves.
462
   *
463
   * <p>NB: Prefer {@link #getPathSegments()} because this method's decoding is lossy. For example,
464
   * consider these (different) URIs:
465
   *
466
   * <ul>
467
   *   <li>file:///home%2Ffolder/my%20file
468
   *   <li>file:///home/folder/my%20file
469
   * </ul>
470
   *
471
   * <p>Calling getPath() on each returns the same string: <code>/home/folder/my file</code>. You
472
   * can't tell whether the second '/' character is part of the first path segment or separates the
473
   * first and second path segments. This method only exists to ease migration from {@link
474
   * java.net.URI}.
475
   */
476
  public String getPath() {
477
    return percentDecodeAssumedUtf8(path);
1✔
478
  }
479

480
  /**
481
   * Returns this URI's path as a list of path segments not including the '/' segment delimiters.
482
   *
483
   * <p>Prefer this method over {@link #getPath()} because it preserves the distinction between
484
   * segment separators and literal '/'s within a path segment.
485
   *
486
   * <p>A trailing '/' delimiter in the path results in the empty string as the last element in the
487
   * returned list. For example, <code>file://localhost/foo/bar/</code> has path segments <code>
488
   * ["foo", "bar", ""]</code>
489
   *
490
   * <p>A leading '/' delimiter cannot be detected using this method. For example, both <code>
491
   * dns:example.com</code> and <code>dns:///example.com</code> have the same list of path segments:
492
   * <code>["example.com"]</code>. Use {@link #isPathAbsolute()} or {@link #isPathRootless()} to
493
   * distinguish these cases.
494
   *
495
   * <p>The returned list is immutable.
496
   */
497
  public List<String> getPathSegments() {
498
    // Returned list must be immutable but we intentionally keep guava out of the public API.
499
    ImmutableList.Builder<String> segmentsBuilder = ImmutableList.builder();
1✔
500
    parseAssumedUtf8PathIntoSegments(path, segmentsBuilder);
1✔
501
    return segmentsBuilder.build();
1✔
502
  }
503

504
  /**
505
   * Returns true iff this URI's path component starts with a path segment (rather than the '/'
506
   * segment delimiter).
507
   *
508
   * <p>The path of an RFC 3986 URI is either empty, absolute (starts with the '/' segment
509
   * delimiter) or rootless (starts with a path segment). For example, <code>tel:+1-206-555-1212
510
   * </code>, <code>mailto:me@example.com</code> and <code>urn:isbn:978-1492082798</code> all have
511
   * rootless paths. <code>mailto:%2Fdev%2Fnull@example.com</code> is also rootless because its
512
   * percent-encoded slashes are not segment delimiters but rather part of the first and only path
513
   * segment.
514
   *
515
   * <p>Contrast rootless paths with absolute ones (see {@link #isPathAbsolute()}.
516
   */
517
  public boolean isPathRootless() {
518
    return !path.isEmpty() && !path.startsWith("/");
1✔
519
  }
520

521
  /**
522
   * Returns true iff this URI's path component starts with the '/' segment delimiter (rather than a
523
   * path segment).
524
   *
525
   * <p>The path of an RFC 3986 URI is either empty, absolute (starts with the '/' segment
526
   * delimiter) or rootless (starts with a path segment). For example, <code>file:///resume.txt
527
   * </code>, <code>file:/resume.txt</code> and <code>file://localhost/</code> all have absolute
528
   * paths while <code>tel:+1-206-555-1212</code>'s path is not absolute. <code>
529
   * mailto:%2Fdev%2Fnull@example.com</code> is also not absolute because its percent-encoded
530
   * slashes are not segment delimiters but rather part of the first and only path segment.
531
   *
532
   * <p>Contrast absolute paths with rootless ones (see {@link #isPathRootless()}.
533
   */
534
  public boolean isPathAbsolute() {
535
    return path.startsWith("/");
1✔
536
  }
537

538
  /**
539
   * Returns the path component of this URI in its originally parsed, possibly percent-encoded form.
540
   */
541
  public String getRawPath() {
542
    return path;
1✔
543
  }
544

545
  /**
546
   * Returns the query component of this URI in its originally parsed, possibly percent-encoded
547
   * form, without any leading '?' character, or null if not present.
548
   *
549
   * <p>The query component can only be read in its raw form. That’s because virtually everyone uses
550
   * query as a container for structured data, with some additional layer of encoding not present in
551
   * RFC-3986. Like 'application/x-www-form-urlencoded', which encodes key/value pairs like so:
552
   * <code>?k1=v1&k2=v+2</code>. The encoding of these containers always has characters that take on
553
   * a special delimiter meaning when not percent-encoded and a literal meaning when they are (like
554
   * '&', '=' and '+' above). Since it matters whether a character was percent encoded or not,
555
   * offering a '#getQuery()' method that percent-decodes everything like we do for other components
556
   * would be error-prone.
557
   */
558
  @Nullable
559
  public String getRawQuery() {
560
    return query;
1✔
561
  }
562

563
  /**
564
   * Returns the percent-decoded "fragment" component of this URI, or null if not present.
565
   *
566
   * <p>NB: This method assumes the fragment was encoded as UTF-8, although RFC 3986 doesn't specify
567
   * an encoding.
568
   *
569
   * <p>Decoding errors are indicated by a {@code '\u005CuFFFD'} unicode replacement character in
570
   * the output. Callers who want to detect and handle errors in some other way should call {@link
571
   * #getRawFragment()}, {@link #percentDecode(CharSequence)}, then decode the bytes for themselves.
572
   *
573
   * <p>NB: Choose carefully between this method and {@link #getRawFragment()}. Many URI schemes
574
   * embed further structure inside the fragment that isn't part of the RFC 3986 generic syntax. For
575
   * example, Android uses the fragment to encode the many fields of an Intent, like {@code
576
   * intent:#Intent;S.key=val;end;}. And the URI of a JSON resource may use RFC 6901 in its fragment
577
   * to point at a particular node, e.g. {@code
578
   * file:/etc/config/service.json#/methodConfig/0/retryPolicy/maxBackoff}.
579
   *
580
   * <p>When percent-encoding is used to escape internal delimiters, like a literal ';' and '=' in
581
   * an `intent:`, call {@link #getRawFragment()} to preserve that percent-encoding, or risk
582
   * corruption. Conversely, use *this* method when percent-decoding is needed *before* any further
583
   * interpretation, like with a JSON pointer, which must be percent-encoded in a URI fragment but
584
   * uses a completely different method of escaping literal '/' characters.
585
   */
586
  @Nullable
587
  public String getFragment() {
588
    return percentDecodeAssumedUtf8(fragment);
1✔
589
  }
590

591
  /**
592
   * Returns the fragment component of this URI in its original, possibly percent-encoded form, and
593
   * without any leading '#' character.
594
   *
595
   * <p>NB: Choose carefully between this method and {@link #getFragment()}. See that Javadoc for
596
   * details.
597
   */
598
  @Nullable
599
  public String getRawFragment() {
600
    return fragment;
1✔
601
  }
602

603
  /**
604
   * {@inheritDoc}
605
   *
606
   * <p>If this URI was created by {@link #parse(String)} or {@link #create(String)}, then the
607
   * returned string will match that original input exactly.
608
   */
609
  @Override
610
  public String toString() {
611
    // https://datatracker.ietf.org/doc/html/rfc3986#section-5.3
612
    StringBuilder sb = new StringBuilder();
1✔
613
    sb.append(scheme).append(':');
1✔
614
    if (hasAuthority()) {
1✔
615
      sb.append("//");
1✔
616
      appendAuthority(sb);
1✔
617
    }
618
    sb.append(path);
1✔
619
    if (query != null) {
1✔
620
      sb.append('?').append(query);
1✔
621
    }
622
    if (fragment != null) {
1✔
623
      sb.append('#').append(fragment);
1✔
624
    }
625
    return sb.toString();
1✔
626
  }
627

628

629
  /**
630
   * {@inheritDoc}
631
   *
632
   * <p>Two instances of {@link Uri} are equal if and only if they have the same string
633
   * representation, which RFC 3986 calls "Simple String Comparison" (6.2.1). Callers with a higher
634
   * layer expectation of equality (e.g. <code>http://some%2Dhost:80/foo/./bar.txt</code> ~= <code>
635
   * http://some-host/foo/bar.txt</code>) will experience false negatives.
636
   */
637
  @Override
638
  public boolean equals(Object otherObj) {
639
    if (!(otherObj instanceof Uri)) {
1✔
640
      return false;
1✔
641
    }
642
    Uri other = (Uri) otherObj;
1✔
643
    return Objects.equals(scheme, other.scheme)
1✔
644
        && Objects.equals(userInfo, other.userInfo)
1✔
645
        && Objects.equals(host, other.host)
1✔
646
        && Objects.equals(port, other.port)
1✔
647
        && Objects.equals(path, other.path)
1✔
648
        && Objects.equals(query, other.query)
1✔
649
        && Objects.equals(fragment, other.fragment);
1✔
650
  }
651

652
  @Override
653
  public int hashCode() {
654
    return Objects.hash(scheme, userInfo, host, port, path, query, fragment);
1✔
655
  }
656

657
  /** Returns a new Builder initialized with the fields of this URI. */
658
  public Builder toBuilder() {
659
    return new Builder(this);
1✔
660
  }
661

662
  /** Creates a new {@link Builder} with all fields uninitialized or set to their default values. */
663
  public static Builder newBuilder() {
664
    return new Builder();
1✔
665
  }
666

667
  /** Builder for {@link Uri}. */
668
  public static final class Builder {
669
    private String scheme;
670
    private String path = "";
1✔
671
    private String query;
672
    private String fragment;
673
    private String userInfo;
674
    private String host;
675
    private String port;
676

677
    private Builder() {}
1✔
678

679
    Builder(Uri prototype) {
1✔
680
      this.scheme = prototype.scheme;
1✔
681
      this.userInfo = prototype.userInfo;
1✔
682
      this.host = prototype.host;
1✔
683
      this.port = prototype.port;
1✔
684
      this.path = prototype.path;
1✔
685
      this.query = prototype.query;
1✔
686
      this.fragment = prototype.fragment;
1✔
687
    }
1✔
688

689
    /**
690
     * Sets the scheme, e.g. "https", "dns" or "xds".
691
     *
692
     * <p>This field is required.
693
     *
694
     * @return this, for fluent building
695
     * @throws IllegalArgumentException if the scheme is invalid.
696
     */
697
    @CanIgnoreReturnValue
698
    public Builder setScheme(String scheme) {
699
      return setRawScheme(scheme.toLowerCase(Locale.ROOT));
1✔
700
    }
701

702
    @CanIgnoreReturnValue
703
    Builder setRawScheme(String scheme) {
704
      if (scheme.isEmpty() || !alphaChars.get(scheme.charAt(0))) {
1✔
705
        throw new IllegalArgumentException("Scheme must start with an alphabetic char");
1✔
706
      }
707
      for (int i = 0; i < scheme.length(); i++) {
1✔
708
        char c = scheme.charAt(i);
1✔
709
        if (!schemeChars.get(c)) {
1✔
710
          throw new IllegalArgumentException("Invalid character in scheme at index " + i);
1✔
711
        }
712
      }
713
      this.scheme = scheme;
1✔
714
      return this;
1✔
715
    }
716

717
    /**
718
     * Specifies the new URI's path component as a string of zero or more '/' delimited segments.
719
     *
720
     * <p>Path segments can consist of any string of codepoints. Codepoints that can't be encoded
721
     * literally will be percent-encoded for you.
722
     *
723
     * <p>If a URI contains an authority component, then the path component must either be empty or
724
     * begin with a slash ("/") character. If a URI does not contain an authority component, then
725
     * the path cannot begin with two slash characters ("//").
726
     *
727
     * <p>This method interprets all '/' characters in 'path' as segment delimiters. If any of your
728
     * segments contain literal '/' characters, call {@link #setRawPath(String)} instead.
729
     *
730
     * <p>See <a href="https://datatracker.ietf.org/doc/html/rfc3986#section-3.3">RFC 3986 3.3</a>
731
     * for more.
732
     *
733
     * <p>This field is required but can be empty (its default value).
734
     *
735
     * @param path the new path
736
     * @return this, for fluent building
737
     */
738
    @CanIgnoreReturnValue
739
    public Builder setPath(String path) {
740
      checkArgument(path != null, "Path can be empty but not null");
1✔
741
      this.path = percentEncode(path, pCharsAndSlash);
1✔
742
      return this;
1✔
743
    }
744

745
    /**
746
     * Specifies the new URI's path component as a string of zero or more '/' delimited segments.
747
     *
748
     * <p>Path segments can consist of any string of codepoints but the caller must first percent-
749
     * encode anything other than RFC 3986's "pchar" character class using UTF-8.
750
     *
751
     * <p>If a URI contains an authority component, then the path component must either be empty or
752
     * begin with a slash ("/") character. If a URI does not contain an authority component, then
753
     * the path cannot begin with two slash characters ("//").
754
     *
755
     * <p>This method interprets all '/' characters in 'path' as segment delimiters. If any of your
756
     * segments contain literal '/' characters, you must percent-encode them.
757
     *
758
     * <p>See <a href="https://datatracker.ietf.org/doc/html/rfc3986#section-3.3">RFC 3986 3.3</a>
759
     * for more.
760
     *
761
     * <p>This field is required but can be empty (its default value).
762
     *
763
     * @param path the new path, a string consisting of characters from "pchar"
764
     * @return this, for fluent building
765
     */
766
    @CanIgnoreReturnValue
767
    public Builder setRawPath(String path) {
768
      checkArgument(path != null, "Path can be empty but not null");
1✔
769
      parseAssumedUtf8PathIntoSegments(path, null);
1✔
770
      this.path = path;
1✔
771
      return this;
1✔
772
    }
773

774
    /**
775
     * Specifies the query component of the new URI, possibly percent-encoded, exactly as it will
776
     * appear in the string form of the built URI.
777
     *
778
     * <p>'query' must only contain codepoints from RFC 3986's "query" character class. Any other
779
     * characters must be percent-encoded using UTF-8. Do not include the leading '?' delimiter.
780
     *
781
     * <p>The query component can only be provided in its raw form. That’s because virtually
782
     * everyone uses query as a container for structured data, with some additional layer of
783
     * encoding not present in RFC-3986. Like 'application/x-www-form-urlencoded', which encodes
784
     * key/value pairs like so: <code>?k1=v1&k2=v+2</code>. The encoding of these containers always
785
     * has characters that take on a special delimiter meaning when not percent-encoded and a
786
     * literal meaning when they are (like '&', '=' and '+' above). Since 'query' must have already
787
     * been carefully percent-encoded externally, a '#setQuery(String)' method that percent-encodes
788
     * an assumed-cooked string would be error-prone.
789
     *
790
     * <p>This field is optional.
791
     *
792
     * @param query the new query component, or null to clear this field
793
     * @return this, for fluent building
794
     */
795
    @CanIgnoreReturnValue
796
    public Builder setRawQuery(@Nullable String query) {
797
      if (query != null) {
1✔
798
        checkPercentEncodedArg(query, "query", queryChars);
1✔
799
      }
800
      this.query = query;
1✔
801
      return this;
1✔
802
    }
803

804
    /**
805
     * Specifies the fragment component of the new URI (not including the leading '#').
806
     *
807
     * <p>The fragment can contain any string of codepoints. Codepoints that can't be encoded
808
     * literally will be percent-encoded for you as UTF-8.
809
     *
810
     * <p>NB: Choose carefully between this method and {@link #setRawFragment(String)}. Many URI
811
     * schemes embed further structure in the fragment that isn't part of the RFC 3986 generic
812
     * syntax. These schemes often use internal delimiters that must be carefully percent-encoded in
813
     * ways that this method doesn't understand. See {@link #getFragment()} for an example. In that
814
     * case, callers should percent-encode externally then call {@link #setRawFragment(String)}
815
     * instead.
816
     *
817
     * <p>This field is optional.
818
     *
819
     * @param fragment the new fragment component, or null to clear this field
820
     * @return this, for fluent building
821
     */
822
    @CanIgnoreReturnValue
823
    public Builder setFragment(@Nullable String fragment) {
824
      this.fragment = percentEncode(fragment, fragmentChars);
1✔
825
      return this;
1✔
826
    }
827

828
    /**
829
     * Specifies the fragment component of the new URI, already percent-encoded, exactly as it will
830
     * appear after the '#' delimiter in the string form of the built URI.
831
     *
832
     * <p>NB: Choose carefully between this method and {@link #setFragment(String)}. {@code
833
     * fragment} must only contain codepoints from RFC 3986's "fragment" character class. Use
834
     * percent-encoding and UTF-8 to represent anything else. In certain cases, you can use {@link
835
     * #setFragment(String)} to have the fragment percent-encoded for you instead, but see that
836
     * method's Javadoc for its limitations.
837
     *
838
     * <p>This field is optional.
839
     *
840
     * @param fragment the new fragment component, or null to clear this field
841
     * @return this, for fluent building
842
     * @throws IllegalArgumentException if 'fragment' contains forbidden characters
843
     */
844
    @CanIgnoreReturnValue
845
    public Builder setRawFragment(@Nullable String fragment) {
846
      if (fragment != null) {
1✔
847
        checkPercentEncodedArg(fragment, "fragment", fragmentChars);
1✔
848
      }
849
      this.fragment = fragment;
1✔
850
      return this;
1✔
851
    }
852

853
    /**
854
     * Set the "user info" component of the new URI, e.g. "username:password", not including the
855
     * trailing '@' character.
856
     *
857
     * <p>User info can contain any string of codepoints. Codepoints that can't be encoded literally
858
     * will be percent-encoded for you as UTF-8.
859
     *
860
     * <p>This field is optional.
861
     *
862
     * @param userInfo the new "user info" component, or null to clear this field
863
     * @return this, for fluent building
864
     */
865
    @CanIgnoreReturnValue
866
    public Builder setUserInfo(@Nullable String userInfo) {
867
      this.userInfo = percentEncode(userInfo, userInfoChars);
1✔
868
      return this;
1✔
869
    }
870

871
    @CanIgnoreReturnValue
872
    Builder setRawUserInfo(String userInfo) {
873
      checkPercentEncodedArg(userInfo, "userInfo", userInfoChars);
1✔
874
      this.userInfo = userInfo;
1✔
875
      return this;
1✔
876
    }
877

878
    /**
879
     * Specifies the "host" component of the new URI in its "registered name" form (usually DNS),
880
     * e.g. "server.com".
881
     *
882
     * <p>The registered name can contain any string of codepoints. Codepoints that can't be encoded
883
     * literally will be percent-encoded for you as UTF-8.
884
     *
885
     * <p>This field is optional.
886
     *
887
     * @param regName the new host component in "registered name" form, or null to clear this field
888
     * @return this, for fluent building
889
     */
890
    @CanIgnoreReturnValue
891
    public Builder setHost(@Nullable String regName) {
892
      if (regName != null) {
1✔
893
        regName = regName.toLowerCase(Locale.ROOT);
1✔
894
        regName = percentEncode(regName, regNameChars);
1✔
895
      }
896
      this.host = regName;
1✔
897
      return this;
1✔
898
    }
899

900
    /**
901
     * Specifies the "host" component of the new URI as an IP address.
902
     *
903
     * <p>This field is optional.
904
     *
905
     * @param addr the new "host" component in InetAddress form, or null to clear this field
906
     * @return this, for fluent building
907
     */
908
    @CanIgnoreReturnValue
909
    public Builder setHost(@Nullable InetAddress addr) {
910
      this.host = addr != null ? toUriString(addr) : null;
1✔
911
      return this;
1✔
912
    }
913

914
    private static String toUriString(InetAddress addr) {
915
      // InetAddresses.toUriString(addr) is almost enough but neglects RFC 6874 percent encoding.
916
      String inetAddrStr = InetAddresses.toUriString(addr);
1✔
917
      int percentIndex = inetAddrStr.indexOf('%');
1✔
918
      if (percentIndex < 0) {
1✔
919
        return inetAddrStr;
1✔
920
      }
921

922
      String scope = inetAddrStr.substring(percentIndex, inetAddrStr.length() - 1);
1✔
923
      return inetAddrStr.substring(0, percentIndex) + percentEncode(scope, unreservedChars) + "]";
1✔
924
    }
925

926
    @CanIgnoreReturnValue
927
    Builder setRawHost(String host) {
928
      if (host.startsWith("[") && host.endsWith("]")) {
1✔
929
        // IP-literal: Guava's isUriInetAddress() is almost enough but it doesn't check the scope.
930
        int percentIndex = host.indexOf('%');
1✔
931
        if (percentIndex > 0) {
1✔
932
          String scope = host.substring(percentIndex, host.length() - 1);
1✔
933
          checkPercentEncodedArg(scope, "scope", unreservedChars);
1✔
934
        }
935
      }
936
      // IP-literal validation is complicated so we delegate it to Guava. We use this particular
937
      // method of InetAddresses because it doesn't try to match interfaces on the local machine.
938
      // (The validity of a URI should be the same no matter which machine does the parsing.)
939
      // TODO(jdcormie): IPFuture
940
      if (!InetAddresses.isUriInetAddress(host)) {
1✔
941
        // Must be a "registered name".
942
        checkPercentEncodedArg(host, "host", regNameChars);
1✔
943
      }
944
      this.host = host;
1✔
945
      return this;
1✔
946
    }
947

948
    /**
949
     * Specifies the "port" component of the new URI, e.g. "8080".
950
     *
951
     * <p>The port can be any non-negative integer. A negative value represents "no port".
952
     *
953
     * <p>This field is optional.
954
     *
955
     * @param port the new "port" component, or -1 to clear this field
956
     * @return this, for fluent building
957
     */
958
    @CanIgnoreReturnValue
959
    public Builder setPort(int port) {
960
      this.port = port < 0 ? null : Integer.toString(port);
1✔
961
      return this;
1✔
962
    }
963

964
    @CanIgnoreReturnValue
965
    Builder setRawPort(String port) {
966
      if (port != null && !port.isEmpty()) {
1✔
967
        try {
968
          Integer.parseInt(port); // Result unused.
1✔
969
        } catch (NumberFormatException e) {
×
970
          throw new IllegalArgumentException("Invalid port", e);
×
971
        }
1✔
972
      }
973
      this.port = port;
1✔
974
      return this;
1✔
975
    }
976

977
    /**
978
     * Specifies the userinfo, host and port URI components all at once using a single string.
979
     *
980
     * <p>This setter is "raw" in the sense that special characters in userinfo and host must be
981
     * passed in percent-encoded. See <a
982
     * href="https://datatracker.ietf.org/doc/html/rfc3986#section-3.2">RFC 3986 3.2</a> for the set
983
     * of characters allowed in each component of an authority.
984
     *
985
     * <p>There's no "cooked" method to set authority like for other URI components because
986
     * authority is a *compound* URI component whose userinfo, host and port components are
987
     * delimited with special characters '@' and ':'. But the first two of those components can
988
     * themselves contain these delimiters so we need percent-encoding to parse them unambiguously.
989
     *
990
     * @param authority an RFC 3986 authority string that will be used to set userinfo, host and
991
     *     port, or null to clear all three of those components
992
     */
993
    @CanIgnoreReturnValue
994
    public Builder setRawAuthority(@Nullable String authority) {
995
      if (authority == null) {
1✔
996
        setUserInfo(null);
1✔
997
        setHost((String) null);
1✔
998
        setPort(-1);
1✔
999
      } else {
1000
        // UserInfo. Easy because '@' cannot appear unencoded inside userinfo or host.
1001
        int userInfoEnd = authority.indexOf('@');
1✔
1002
        if (userInfoEnd >= 0) {
1✔
1003
          setRawUserInfo(authority.substring(0, userInfoEnd));
1✔
1004
        } else {
1005
          setUserInfo(null);
1✔
1006
        }
1007

1008
        // Host/Port.
1009
        int hostStart = userInfoEnd >= 0 ? userInfoEnd + 1 : 0;
1✔
1010
        int portStartColon = findPortStartColon(authority, hostStart);
1✔
1011
        if (portStartColon < 0) {
1✔
1012
          setRawHost(authority.substring(hostStart));
1✔
1013
          setPort(-1);
1✔
1014
        } else {
1015
          setRawHost(authority.substring(hostStart, portStartColon));
1✔
1016
          setRawPort(authority.substring(portStartColon + 1));
1✔
1017
        }
1018
      }
1019
      return this;
1✔
1020
    }
1021

1022
    /** Builds a new instance of {@link Uri} as specified by the setters. */
1023
    public Uri build() {
1024
      checkState(scheme != null, "Missing required scheme.");
1✔
1025
      if (host == null) {
1✔
1026
        checkState(port == null, "Cannot set port without host.");
1✔
1027
        checkState(userInfo == null, "Cannot set userInfo without host.");
1✔
1028
      }
1029
      return new Uri(this);
1✔
1030
    }
1031
  }
1032

1033
  /**
1034
   * Decodes a string of characters in the range [U+0000, U+007F] to bytes.
1035
   *
1036
   * <p>Each percent-encoded sequence (e.g. "%F0" or "%2a", as defined by RFC 3986 2.1) is decoded
1037
   * to the octet it encodes. Other characters are decoded to their code point's single byte value.
1038
   * A literal % character must be encoded as %25.
1039
   *
1040
   * @throws IllegalArgumentException if 's' contains characters out of range or invalid percent
1041
   *     encoding sequences.
1042
   */
1043
  public static ByteBuffer percentDecode(CharSequence s) {
1044
    // This is large enough because each input character needs *at most* one byte of output.
1045
    ByteBuffer outBuf = ByteBuffer.allocate(s.length());
1✔
1046
    percentDecode(s, "input", null, outBuf);
1✔
1047
    outBuf.flip();
1✔
1048
    return outBuf;
1✔
1049
  }
1050

1051
  private static void percentDecode(
1052
      CharSequence s, String what, BitSet allowedChars, ByteBuffer outBuf) {
1053
    for (int i = 0; i < s.length(); i++) {
1✔
1054
      char c = s.charAt(i);
1✔
1055
      if (c == '%') {
1✔
1056
        if (i + 2 >= s.length()) {
1✔
1057
          throw new IllegalArgumentException(
1✔
1058
              "Invalid percent-encoding at index " + i + " of " + what + ": " + s);
1059
        }
1060
        int h1 = Character.digit(s.charAt(i + 1), 16);
1✔
1061
        int h2 = Character.digit(s.charAt(i + 2), 16);
1✔
1062
        if (h1 == -1 || h2 == -1) {
1✔
1063
          throw new IllegalArgumentException(
1✔
1064
              "Invalid hex digit in " + what + " at index " + i + " of: " + s);
1065
        }
1066
        if (outBuf != null) {
1✔
1067
          outBuf.put((byte) (h1 << 4 | h2));
1✔
1068
        }
1069
        i += 2;
1✔
1070
      } else if (allowedChars == null || allowedChars.get(c)) {
1✔
1071
        if (outBuf != null) {
1✔
1072
          outBuf.put((byte) c);
1✔
1073
        }
1074
      } else {
1075
        throw new IllegalArgumentException("Invalid character in " + what + " at index " + i);
1✔
1076
      }
1077
    }
1078
  }
1✔
1079

1080
  @Nullable
1081
  private static String percentDecodeAssumedUtf8(@Nullable String s) {
1082
    if (s == null || s.indexOf('%') == -1) {
1✔
1083
      return s;
1✔
1084
    }
1085

1086
    ByteBuffer utf8Bytes = percentDecode(s);
1✔
1087
    try {
1088
      return StandardCharsets.UTF_8
1✔
1089
          .newDecoder()
1✔
1090
          .onMalformedInput(CodingErrorAction.REPLACE)
1✔
1091
          .onUnmappableCharacter(CodingErrorAction.REPLACE)
1✔
1092
          .decode(utf8Bytes)
1✔
1093
          .toString();
1✔
1094
    } catch (CharacterCodingException e) {
×
1095
      throw new VerifyException(e); // Should not happen in REPLACE mode.
×
1096
    }
1097
  }
1098

1099
  @Nullable
1100
  private static String percentEncode(String s, BitSet allowedCodePoints) {
1101
    if (s == null) {
1✔
1102
      return null;
1✔
1103
    }
1104
    CharsetEncoder encoder =
1✔
1105
        StandardCharsets.UTF_8
1106
            .newEncoder()
1✔
1107
            .onMalformedInput(CodingErrorAction.REPORT)
1✔
1108
            .onUnmappableCharacter(CodingErrorAction.REPORT);
1✔
1109
    ByteBuffer utf8Bytes;
1110
    try {
1111
      utf8Bytes = encoder.encode(CharBuffer.wrap(s));
1✔
1112
    } catch (MalformedInputException e) {
1✔
1113
      throw new IllegalArgumentException("Malformed input", e); // Must be a broken surrogate pair.
1✔
1114
    } catch (CharacterCodingException e) {
×
1115
      throw new VerifyException(e); // Should not happen when encoding to UTF-8.
×
1116
    }
1✔
1117

1118
    StringBuilder sb = new StringBuilder();
1✔
1119
    while (utf8Bytes.hasRemaining()) {
1✔
1120
      int b = 0xff & utf8Bytes.get();
1✔
1121
      if (allowedCodePoints.get(b)) {
1✔
1122
        sb.append((char) b);
1✔
1123
      } else {
1124
        sb.append('%');
1✔
1125
        sb.append(hexDigitsByVal[(b & 0xF0) >> 4]);
1✔
1126
        sb.append(hexDigitsByVal[b & 0x0F]);
1✔
1127
      }
1128
    }
1✔
1129
    return sb.toString();
1✔
1130
  }
1131

1132
  private static void checkPercentEncodedArg(String s, String what, BitSet allowedChars) {
1133
    percentDecode(s, what, allowedChars, null);
1✔
1134
  }
1✔
1135

1136
  // See UriTest for how these were computed from the ABNF constants in RFC 3986.
1137
  static final BitSet digitChars = BitSet.valueOf(new long[] {0x3ff000000000000L});
1✔
1138
  static final BitSet alphaChars = BitSet.valueOf(new long[] {0L, 0x7fffffe07fffffeL});
1✔
1139
  // scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
1140
  static final BitSet schemeChars =
1✔
1141
      BitSet.valueOf(new long[] {0x3ff680000000000L, 0x7fffffe07fffffeL});
1✔
1142
  // unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
1143
  static final BitSet unreservedChars =
1✔
1144
      BitSet.valueOf(new long[] {0x3ff600000000000L, 0x47fffffe87fffffeL});
1✔
1145
  // gen-delims    = ":" / "/" / "?" / "#" / "[" / "]" / "@"
1146
  static final BitSet genDelimsChars =
1✔
1147
      BitSet.valueOf(new long[] {0x8400800800000000L, 0x28000001L});
1✔
1148
  // sub-delims    = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
1149
  static final BitSet subDelimsChars = BitSet.valueOf(new long[] {0x28001fd200000000L});
1✔
1150
  // reserved      = gen-delims / sub-delims
1151
  static final BitSet reservedChars = BitSet.valueOf(new long[] {0xac009fda00000000L, 0x28000001L});
1✔
1152
  // reg-name      = *( unreserved / pct-encoded / sub-delims )
1153
  static final BitSet regNameChars =
1✔
1154
      BitSet.valueOf(new long[] {0x2bff7fd200000000L, 0x47fffffe87fffffeL});
1✔
1155
  // userinfo      = *( unreserved / pct-encoded / sub-delims / ":" )
1156
  static final BitSet userInfoChars =
1✔
1157
      BitSet.valueOf(new long[] {0x2fff7fd200000000L, 0x47fffffe87fffffeL});
1✔
1158
  // pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
1159
  static final BitSet pChars =
1✔
1160
      BitSet.valueOf(new long[] {0x2fff7fd200000000L, 0x47fffffe87ffffffL});
1✔
1161
  static final BitSet pCharsAndSlash =
1✔
1162
      BitSet.valueOf(new long[] {0x2fffffd200000000L, 0x47fffffe87ffffffL});
1✔
1163
  //  query         = *( pchar / "/" / "?" )
1164
  static final BitSet queryChars =
1✔
1165
      BitSet.valueOf(new long[] {0xafffffd200000000L, 0x47fffffe87ffffffL});
1✔
1166
  // fragment      = *( pchar / "/" / "?" )
1167
  static final BitSet fragmentChars = queryChars;
1✔
1168

1169
  private static final char[] hexDigitsByVal = "0123456789ABCDEF".toCharArray();
1✔
1170
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc