Skip to content

Commit 16c6a5f

Browse files
feat(core): Add items and bytes limits to baggage extraction (#11265)
feat(core): Add items and bytes limits to baggage extraction Co-authored-by: bruce.bujon <bruce.bujon@datadoghq.com>
1 parent c2c4dfc commit 16c6a5f

2 files changed

Lines changed: 91 additions & 26 deletions

File tree

dd-trace-core/src/main/java/datadog/trace/core/baggage/BaggagePropagator.java

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package datadog.trace.core.baggage;
22

3-
import static java.util.Collections.emptyMap;
4-
53
import datadog.context.Context;
64
import datadog.context.propagation.CarrierSetter;
75
import datadog.context.propagation.CarrierVisitor;
@@ -19,6 +17,7 @@
1917
import java.util.HashMap;
2018
import java.util.Map;
2119
import java.util.function.BiConsumer;
20+
import javax.annotation.Nullable;
2221
import javax.annotation.ParametersAreNonnullByDefault;
2322
import org.slf4j.Logger;
2423
import org.slf4j.LoggerFactory;
@@ -142,8 +141,7 @@ public <C> Context extract(Context context, C carrier, CarrierVisitor<C> visitor
142141
private class BaggageExtractor implements BiConsumer<String, String> {
143142
private static final char KEY_VALUE_SEPARATOR = '=';
144143
private static final char PAIR_SEPARATOR = ',';
145-
private Baggage extracted;
146-
private String w3cHeader;
144+
@Nullable private Baggage extracted;
147145

148146
/** URL decode value */
149147
private String decode(final String value) {
@@ -156,41 +154,42 @@ private String decode(final String value) {
156154
return decoded;
157155
}
158156

159-
private Map<String, String> parseBaggageHeaders(String input) {
157+
private Baggage parseBaggageHeaders(String input) {
160158
Map<String, String> baggage = new HashMap<>();
161159
int start = 0;
162-
boolean truncatedCache = false;
160+
String w3cHeader = input;
163161
int pairSeparatorInd = input.indexOf(PAIR_SEPARATOR);
164162
pairSeparatorInd = pairSeparatorInd == -1 ? input.length() : pairSeparatorInd;
165163
int kvSeparatorInd = input.indexOf(KEY_VALUE_SEPARATOR);
166164
while (kvSeparatorInd != -1) {
167165
int end = pairSeparatorInd;
166+
boolean limitReached = baggage.size() >= maxItems || end > maxBytes;
167+
if (limitReached) {
168+
// if header was not invalidated already, and we go out of range:
169+
// - fully invalidate if it's after the first k/v pair,
170+
// - otherwise ignore from the current k/v separator
171+
w3cHeader = (w3cHeader == null || start == 0) ? null : w3cHeader.substring(0, start - 1);
172+
break;
173+
}
168174
if (kvSeparatorInd > end) {
169175
LOG.debug(
170176
"Dropping baggage headers due to key with no value {}", input.substring(start, end));
171177
BAGGAGE_METRICS.onBaggageMalformed();
172-
return emptyMap();
178+
return null;
173179
}
174180
String key = decode(input.substring(start, kvSeparatorInd).trim());
175181
String value = decode(input.substring(kvSeparatorInd + 1, end).trim());
176182
if (key.isEmpty() || value.isEmpty()) {
177183
LOG.debug("Dropping baggage headers due to empty k/v {}:{}", key, value);
178184
BAGGAGE_METRICS.onBaggageMalformed();
179-
return emptyMap();
185+
return null;
180186
}
181187
baggage.put(key, value);
182188

183189
// need to percent-encode non-ascii headers we pass down
184-
if (UTF_ESCAPER.keyNeedsEncoding(key) || UTF_ESCAPER.valNeedsEncoding(value)) {
185-
truncatedCache = true;
186-
this.w3cHeader = null;
187-
} else if (!truncatedCache && (end > maxBytes || baggage.size() > maxItems)) {
188-
if (start == 0) { // if we go out of range after first k/v pair, there is no cache
189-
this.w3cHeader = null;
190-
} else {
191-
this.w3cHeader = input.substring(0, start - 1); // -1 to ignore the k/v separator
192-
}
193-
truncatedCache = true;
190+
if (w3cHeader != null
191+
&& (UTF_ESCAPER.keyNeedsEncoding(key) || UTF_ESCAPER.valNeedsEncoding(value))) {
192+
w3cHeader = null;
194193
}
195194

196195
kvSeparatorInd = input.indexOf(KEY_VALUE_SEPARATOR, pairSeparatorInd + 1);
@@ -199,20 +198,16 @@ private Map<String, String> parseBaggageHeaders(String input) {
199198
start = end + 1;
200199
}
201200

202-
if (!truncatedCache) {
203-
this.w3cHeader = input;
204-
}
205-
206-
return baggage;
201+
return baggage.isEmpty() ? null : Baggage.create(baggage, w3cHeader);
207202
}
208203

209204
@Override
210205
public void accept(String key, String value) {
211206
// Only process tags that are relevant to baggage
212207
if (BAGGAGE_KEY.equalsIgnoreCase(key)) {
213-
Map<String, String> baggage = parseBaggageHeaders(value);
214-
if (!baggage.isEmpty()) {
215-
this.extracted = Baggage.create(baggage, this.w3cHeader);
208+
Baggage parsed = parseBaggageHeaders(value);
209+
if (parsed != null) {
210+
this.extracted = parsed;
216211
}
217212
}
218213
}

dd-trace-core/src/test/groovy/datadog/trace/core/baggage/BaggagePropagatorTest.groovy

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,4 +222,74 @@ class BaggagePropagatorTest extends DDSpecification {
222222
"key1=val1,key2=val2" | "key1=val1,key2=val2"
223223
"key1=val1,key2=val2,key3=val3" | "key1=val1,key2=val2"
224224
}
225+
226+
def "test baggage extract items limit"() {
227+
setup:
228+
propagator = new BaggagePropagator(true, true, 2, DEFAULT_TRACE_BAGGAGE_MAX_BYTES) //creating a new instance after injecting config
229+
def headers = [
230+
(BAGGAGE_KEY) : baggageHeader,
231+
]
232+
233+
when:
234+
context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap())
235+
236+
then: 'parsing stops once the item limit is exceeded'
237+
Baggage.fromContext(context).asMap() == baggageMap
238+
239+
where:
240+
baggageHeader | baggageMap
241+
"key1=val1" | [key1: "val1"]
242+
"key1=val1,key2=val2" | [key1: "val1", key2: "val2"]
243+
"key1=val1,key2=val2,key3=val3" | [key1: "val1", key2: "val2"]
244+
}
245+
246+
def "test baggage extract bytes limit"() {
247+
setup:
248+
propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 20) //creating a new instance after injecting config
249+
def headers = [
250+
(BAGGAGE_KEY) : baggageHeader,
251+
]
252+
253+
when:
254+
context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap())
255+
256+
then: 'parsing stops once the byte limit is exceeded'
257+
Baggage.fromContext(context).asMap() == baggageMap
258+
259+
where:
260+
baggageHeader | baggageMap
261+
"key1=val1" | [key1: "val1"]
262+
"key1=val1,key2=val2" | [key1: "val1", key2: "val2"]
263+
"key1=val1,key2=val2,key3=val3" | [key1: "val1", key2: "val2"]
264+
}
265+
266+
def "test baggage extract 0 item limit"() {
267+
setup:
268+
propagator = new BaggagePropagator(true, true, 0, DEFAULT_TRACE_BAGGAGE_MAX_BYTES) //creating a new instance after injecting config
269+
def headers = [
270+
(BAGGAGE_KEY) : "key1=value1",
271+
]
272+
273+
when:
274+
context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap())
275+
276+
then:
277+
Baggage.fromContext(context) == null
278+
}
279+
280+
281+
282+
def "test baggage extract 0 byte limit"() {
283+
setup:
284+
propagator = new BaggagePropagator(true, true, DEFAULT_TRACE_BAGGAGE_MAX_ITEMS, 0) //creating a new instance after injecting config
285+
def headers = [
286+
(BAGGAGE_KEY) : "key1=value1",
287+
]
288+
289+
when:
290+
context = this.propagator.extract(context, headers, ContextVisitors.stringValuesMap())
291+
292+
then:
293+
Baggage.fromContext(context) == null
294+
}
225295
}

0 commit comments

Comments
 (0)