Skip to content

Commit 96c1fb1

Browse files
committed
Merge upstream/master into xds-composite-filter
2 parents dc297fe + 7b0a7eb commit 96c1fb1

46 files changed

Lines changed: 1873 additions & 320 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MODULE.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ IO_GRPC_GRPC_JAVA_ARTIFACTS = [
4747
]
4848
# GRPC_DEPS_END
4949

50+
bazel_dep(name = "abseil-cpp", version = "20250512.1")
5051
bazel_dep(name = "bazel_jar_jar", version = "0.1.11.bcr.1")
5152
bazel_dep(name = "bazel_skylib", version = "1.7.1")
5253
bazel_dep(name = "googleapis", version = "0.0.0-20240326-1c8d509c5", repo_name = "com_google_googleapis")
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/*
2+
* Copyright 2026 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.checkNotNull;
20+
21+
import com.google.common.base.Splitter;
22+
import java.io.UnsupportedEncodingException;
23+
import java.net.URLDecoder;
24+
import java.net.URLEncoder;
25+
import java.util.ArrayList;
26+
import java.util.List;
27+
import java.util.Objects;
28+
import javax.annotation.Nullable;
29+
30+
/**
31+
* A parser and mutable container class for {@code application/x-www-form-urlencoded}-style URL
32+
* parameters as conceived by <a href="https://datatracker.ietf.org/doc/html/rfc1866#section-8.2.1">
33+
* RFC 1866 Section 8.2.1</a>.
34+
*
35+
* <p>For example, a URI like {@code "http://who?name=John+Doe&role=admin&role=user&active"} has:
36+
*
37+
* <ul>
38+
* <li>A key {@code name} with value {@code John Doe}
39+
* <li>A key {@code role} with value {@code admin}
40+
* <li>A second key named {@code role} with value {@code user}
41+
* <li>"Lone" key {@code active} without a value.
42+
* </ul>
43+
*
44+
* <p>This class is meant to be used with {@link io.grpc.Uri}. For example:
45+
*
46+
* <pre>{@code
47+
* Uri uri = Uri.parse("http://who?name=John+Doe&role=admin&role=user&active");
48+
* QueryParams params = QueryParams.fromRawQuery(uri.getRawQuery());
49+
* params.asList().removeIf(e -> "role".equals(e.getKey()) && "admin".equals(e.getValue()));
50+
*
51+
* Uri modifiedUri = uri.toBuilder().setRawQuery(params.toRawQuery()).build();
52+
* }</pre>
53+
*
54+
* <p>Note that the empty collection is encoded as a null raw query string, which means "absent" to
55+
* {@link io.grpc.Uri.Builder#setRawQuery}. An empty string query component (""), on the other hand,
56+
* is modeled as an instance of QueryParams containing a single lone (empty) key. It must be this
57+
* way if we are to simultaneously 1) support lone keys, 2) have parse/toRawQuery round-trip
58+
* transparency, and 3) never fail to parse a valid RFC 3986 query component.
59+
*
60+
* <p>This container and its {@link Entry} take the same position as {@link io.grpc.Uri} on
61+
* equality: raw keys and values must match exactly to be equal. Most callers won't care about how
62+
* keys and values are encoded on the wire and will work with the getters for cooked keys and values
63+
* instead.
64+
*
65+
* <p>Instances are not safe for concurrent access by multiple threads, including by way of the
66+
* {@link #asList()} view method.
67+
*/
68+
@Internal
69+
public final class QueryParams {
70+
71+
private static final String UTF_8 = "UTF-8";
72+
private final List<Entry> entries = new ArrayList<>();
73+
74+
/** Creates a new, empty {@code QueryParams} instance. */
75+
public QueryParams() {}
76+
77+
/**
78+
* Parses a raw query string into a {@code QueryParams} instance.
79+
*
80+
* <p>The input is split on {@code '&'} and each parameter is parsed as either a key/value pair
81+
* (if it contains an equals sign) or a "lone" key (if it does not).
82+
*
83+
* <p>No valid RFC 3986 query component will fail to parse. For example, {@code ===} is parsed as
84+
* a single parameter with "" as the key and "==" as the value. {@code &&&} is parsed as three
85+
* lone keys named "". And so on. If {@code rawQuery} is not a valid RFC 3986 query component, the
86+
* behavior is undefined. But if you are starting with a {@link io.grpc.Uri}, passing the value
87+
* returned by {@link io.grpc.Uri#getRawQuery()} is always well-defined and will never fail.
88+
*
89+
* <p>Calling {@link #toRawQuery()} on the returned object is guaranteed to return exactly {@code
90+
* rawQuery}.
91+
*
92+
* @param rawQuery the raw query component to parse, or null to return an empty container
93+
* @return a new instance of {@code QueryParams} representing the input
94+
*/
95+
public static QueryParams fromRawQuery(@Nullable String rawQuery) {
96+
QueryParams params = new QueryParams();
97+
if (rawQuery != null) {
98+
for (String part : Splitter.on('&').split(rawQuery)) {
99+
int equalsIndex = part.indexOf('=');
100+
if (equalsIndex == -1) {
101+
params.entries.add(Entry.forRawLoneKey(part));
102+
} else {
103+
String rawKey = part.substring(0, equalsIndex);
104+
String rawValue = part.substring(equalsIndex + 1);
105+
params.entries.add(Entry.forRawKeyValue(rawKey, rawValue));
106+
}
107+
}
108+
}
109+
return params;
110+
}
111+
112+
/**
113+
* Returns a mutable list view of the query parameters.
114+
*
115+
* @return the mutable list of entries
116+
*/
117+
public List<Entry> asList() {
118+
return entries;
119+
}
120+
121+
/**
122+
* Returns the "raw" query string representation of these parameters, suitable for passing to the
123+
* {@link io.grpc.Uri.Builder#setRawQuery} method.
124+
*
125+
* @return the raw query string
126+
*/
127+
@Nullable
128+
public String toRawQuery() {
129+
if (entries.isEmpty()) {
130+
return null;
131+
}
132+
StringBuilder resultBuilder = new StringBuilder();
133+
boolean first = true;
134+
for (Entry entry : entries) {
135+
if (!first) {
136+
resultBuilder.append('&');
137+
}
138+
entry.appendToRawQueryStringBuilder(resultBuilder);
139+
first = false;
140+
}
141+
return resultBuilder.toString();
142+
}
143+
144+
@Override
145+
public String toString() {
146+
return entries.toString();
147+
}
148+
149+
@Override
150+
public boolean equals(Object o) {
151+
if (this == o) {
152+
return true;
153+
}
154+
if (!(o instanceof QueryParams)) {
155+
return false;
156+
}
157+
QueryParams other = (QueryParams) o;
158+
return entries.equals(other.entries);
159+
}
160+
161+
@Override
162+
public int hashCode() {
163+
return entries.hashCode();
164+
}
165+
166+
/** A single query parameter entry. */
167+
public static final class Entry {
168+
private final String rawKey;
169+
@Nullable private final String rawValue;
170+
private final String key;
171+
@Nullable private final String value;
172+
173+
private Entry(String rawKey, @Nullable String rawValue, String key, @Nullable String value) {
174+
this.rawKey = checkNotNull(rawKey, "rawKey");
175+
this.rawValue = rawValue;
176+
this.key = checkNotNull(key, "key");
177+
this.value = value;
178+
}
179+
180+
/**
181+
* Returns the key.
182+
*
183+
* <p>Any characters that needed URL encoding have already been decoded.
184+
*/
185+
public String getKey() {
186+
return key;
187+
}
188+
189+
/**
190+
* Returns the value, or {@code null} if this is a "lone" key.
191+
*
192+
* <p>Any characters that needed URL encoding have already been decoded.
193+
*/
194+
@Nullable
195+
public String getValue() {
196+
return value;
197+
}
198+
199+
/** Returns {@code true} if this entry has a value, {@code false} if it is a "lone" key. */
200+
public boolean hasValue() {
201+
return value != null;
202+
}
203+
204+
/**
205+
* Creates a new key/value pair entry.
206+
*
207+
* <p>Both key and value can contain any character. They will be URL encoded for you if
208+
* necessary.
209+
*/
210+
public static Entry forKeyValue(String key, String value) {
211+
checkNotNull(key, "key");
212+
checkNotNull(value, "value");
213+
return new Entry(encode(key), encode(value), key, value);
214+
}
215+
216+
/**
217+
* Creates a new query parameter with a "lone" key.
218+
*
219+
* <p>'key' can contain any character. It will be URL encoded for you later, as necessary.
220+
*
221+
* @param key the decoded key, must not be null
222+
* @return a new {@code Entry}
223+
*/
224+
public static Entry forLoneKey(String key) {
225+
checkNotNull(key, "key");
226+
return new Entry(encode(key), null, key, null);
227+
}
228+
229+
static Entry forRawKeyValue(String rawKey, String rawValue) {
230+
checkNotNull(rawKey, "rawKey");
231+
checkNotNull(rawValue, "rawValue");
232+
return new Entry(rawKey, rawValue, decode(rawKey), decode(rawValue));
233+
}
234+
235+
static Entry forRawLoneKey(String rawKey) {
236+
checkNotNull(rawKey, "rawKey");
237+
return new Entry(rawKey, null, decode(rawKey), null);
238+
}
239+
240+
void appendToRawQueryStringBuilder(StringBuilder sb) {
241+
sb.append(rawKey);
242+
if (rawValue != null) {
243+
sb.append('=').append(rawValue);
244+
}
245+
}
246+
247+
@Override
248+
public boolean equals(Object o) {
249+
if (this == o) {
250+
return true;
251+
}
252+
if (!(o instanceof Entry)) {
253+
return false;
254+
}
255+
Entry entry = (Entry) o;
256+
return Objects.equals(rawKey, entry.rawKey) && Objects.equals(rawValue, entry.rawValue);
257+
}
258+
259+
@Override
260+
public int hashCode() {
261+
return Objects.hash(rawKey, rawValue);
262+
}
263+
264+
@Override
265+
public String toString() {
266+
StringBuilder sb = new StringBuilder();
267+
appendToRawQueryStringBuilder(sb);
268+
return sb.toString();
269+
}
270+
}
271+
272+
private static String decode(String s) {
273+
try {
274+
// TODO: Use URLDecoder.decode(String, Charset) when available
275+
return URLDecoder.decode(s, UTF_8);
276+
} catch (UnsupportedEncodingException impossible) {
277+
throw new AssertionError("UTF-8 is not supported", impossible);
278+
}
279+
}
280+
281+
private static String encode(String s) {
282+
try {
283+
// TODO: Use URLEncoder.encode(String, Charset) when available
284+
return URLEncoder.encode(s, UTF_8);
285+
} catch (UnsupportedEncodingException impossible) {
286+
throw new AssertionError("UTF-8 is not supported", impossible);
287+
}
288+
}
289+
}

0 commit comments

Comments
 (0)