Skip to content

Commit e28c00c

Browse files
Improve SentryTraceHeader constructor parameter validation
1 parent b2ab4eb commit e28c00c

File tree

2 files changed

+90
-12
lines changed

2 files changed

+90
-12
lines changed

sentry/src/main/java/io/sentry/SentryTraceHeader.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import io.sentry.exception.InvalidSentryTraceHeaderException;
44
import io.sentry.protocol.SentryId;
5+
import java.util.regex.Matcher;
6+
import java.util.regex.Pattern;
57
import org.jetbrains.annotations.NotNull;
68
import org.jetbrains.annotations.Nullable;
79

@@ -13,6 +15,11 @@ public final class SentryTraceHeader {
1315
private final @NotNull SpanId spanId;
1416
private final @Nullable Boolean sampled;
1517

18+
final Pattern SENTRY_TRACEPARENT_HEADER_REGEX =
19+
Pattern.compile(
20+
"^[ \\t]*(?<traceId>[0-9a-f]{32})-(?<spanId>[0-9a-f]{16})-?(?<sampled>[01])?[ \\t]*$",
21+
Pattern.CASE_INSENSITIVE);
22+
1623
public SentryTraceHeader(
1724
final @NotNull SentryId traceId,
1825
final @NotNull SpanId spanId,
@@ -23,20 +30,16 @@ public SentryTraceHeader(
2330
}
2431

2532
public SentryTraceHeader(final @NotNull String value) throws InvalidSentryTraceHeaderException {
26-
final String[] parts = value.split("-", -1);
27-
if (parts.length < 2) {
33+
Matcher matcher = SENTRY_TRACEPARENT_HEADER_REGEX.matcher(value);
34+
boolean matchesExist = matcher.matches();
35+
36+
if (!matchesExist || matcher.group("traceId") == null || matcher.group("spanId") == null) {
2837
throw new InvalidSentryTraceHeaderException(value);
29-
} else if (parts.length == 3) {
30-
this.sampled = "1".equals(parts[2]);
31-
} else {
32-
this.sampled = null;
33-
}
34-
try {
35-
this.traceId = new SentryId(parts[0]);
36-
this.spanId = new SpanId(parts[1]);
37-
} catch (Throwable e) {
38-
throw new InvalidSentryTraceHeaderException(value, e);
3938
}
39+
40+
this.traceId = new SentryId(matcher.group("traceId"));
41+
this.spanId = new SpanId(matcher.group("spanId"));
42+
this.sampled = matcher.group("sampled") == null ? null : "1".equals(matcher.group("sampled"));
4043
}
4144

4245
public @NotNull String getName() {

sentry/src/test/java/io/sentry/SentryTraceHeaderTest.kt

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import kotlin.test.Test
66
import kotlin.test.assertEquals
77
import kotlin.test.assertFailsWith
88
import kotlin.test.assertNull
9+
import kotlin.text.substring
910

1011
class SentryTraceHeaderTest {
1112
@Test
@@ -15,6 +16,80 @@ class SentryTraceHeaderTest {
1516
assertEquals("sentry-trace header does not conform to expected format: $sentryId", ex.message)
1617
}
1718

19+
@Test
20+
fun `when trace-id has less than 32 characters throws exception`() {
21+
val sentryId = SentryId().toString().substring(0, 8)
22+
val spanId = SpanId()
23+
val ex =
24+
assertFailsWith<InvalidSentryTraceHeaderException> { SentryTraceHeader("$sentryId-$spanId") }
25+
assertEquals(
26+
"sentry-trace header does not conform to expected format: $sentryId-$spanId",
27+
ex.message,
28+
)
29+
}
30+
31+
@Test
32+
fun `when trace-id has more than 32 characters throws exception`() {
33+
val sentryId = SentryId().toString() + "abc"
34+
val spanId = SpanId()
35+
val ex =
36+
assertFailsWith<InvalidSentryTraceHeaderException> { SentryTraceHeader("$sentryId-$spanId") }
37+
assertEquals(
38+
"sentry-trace header does not conform to expected format: $sentryId-$spanId",
39+
ex.message,
40+
)
41+
}
42+
43+
@Test
44+
fun `when trace-id contains invalid characters throws exception`() {
45+
var sentryId = SentryId().toString()
46+
sentryId = sentryId.substring(0, 8) + "g" + sentryId.substring(8)
47+
val spanId = SpanId()
48+
val ex =
49+
assertFailsWith<InvalidSentryTraceHeaderException> { SentryTraceHeader("$sentryId-$spanId") }
50+
assertEquals(
51+
"sentry-trace header does not conform to expected format: $sentryId-$spanId",
52+
ex.message,
53+
)
54+
}
55+
56+
@Test
57+
fun `when span-id has less than 16 characters throws exception`() {
58+
val sentryId = SentryId()
59+
val spanId = SpanId().toString().substring(0, 8)
60+
val ex =
61+
assertFailsWith<InvalidSentryTraceHeaderException> { SentryTraceHeader("$sentryId-$spanId") }
62+
assertEquals(
63+
"sentry-trace header does not conform to expected format: $sentryId-$spanId",
64+
ex.message,
65+
)
66+
}
67+
68+
@Test
69+
fun `when span-id has more than 32 characters throws exception`() {
70+
val sentryId = SentryId()
71+
val spanId = SpanId().toString() + "abc"
72+
val ex =
73+
assertFailsWith<InvalidSentryTraceHeaderException> { SentryTraceHeader("$sentryId-$spanId") }
74+
assertEquals(
75+
"sentry-trace header does not conform to expected format: $sentryId-$spanId",
76+
ex.message,
77+
)
78+
}
79+
80+
@Test
81+
fun `when span-id contains invalid characters throws exception`() {
82+
val sentryId = SentryId()
83+
var spanId = SpanId().toString()
84+
spanId = spanId.substring(0, 8) + "g" + spanId.substring(8)
85+
val ex =
86+
assertFailsWith<InvalidSentryTraceHeaderException> { SentryTraceHeader("$sentryId-$spanId") }
87+
assertEquals(
88+
"sentry-trace header does not conform to expected format: $sentryId-$spanId",
89+
ex.message,
90+
)
91+
}
92+
1893
@Test
1994
fun `handles header with positive sampling decision`() {
2095
val sentryId = SentryId()

0 commit comments

Comments
 (0)