Skip to content

Commit 39fcf60

Browse files
committed
Use case-insensitive comparision when extracting headers
Run tests ./gradlew :sentry:test --tests="*NetworkDetailCaptureUtilsTest*"
1 parent 75a4e6c commit 39fcf60

File tree

2 files changed

+97
-8
lines changed

2 files changed

+97
-8
lines changed

sentry/src/main/java/io/sentry/util/network/NetworkDetailCaptureUtils.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package io.sentry.util.network;
22

3-
import java.util.HashMap;
3+
import java.util.HashSet;
4+
import java.util.LinkedHashMap;
45
import java.util.Map;
6+
import java.util.Set;
57
import org.jetbrains.annotations.NotNull;
68
import org.jetbrains.annotations.Nullable;
9+
import org.jetbrains.annotations.VisibleForTesting;
710

811
/**
912
* Utility class for network capture operations shared across HTTP client integrations. Provides
@@ -115,19 +118,24 @@ private static boolean shouldCaptureUrl(
115118
return false;
116119
}
117120

118-
private static @NotNull Map<String, String> getCaptureHeaders(
121+
@VisibleForTesting
122+
static @NotNull Map<String, String> getCaptureHeaders(
119123
@Nullable final Map<String, String> allHeaders, @NotNull final String[] allowedHeaders) {
120124

121-
Map<String, String> capturedHeaders = new HashMap<>();
122-
123-
if (allHeaders == null) {
125+
final Map<String, String> capturedHeaders = new LinkedHashMap<>();
126+
if (allHeaders == null || allowedHeaders.length == 0) {
124127
return capturedHeaders;
125128
}
126129

130+
// Convert to lowercase for case-insensitive matching
131+
Set<String> normalizedAllowed = new HashSet<>();
127132
for (String header : allowedHeaders) {
128-
String value = allHeaders.get(header);
129-
if (value != null) {
130-
capturedHeaders.put(header, value);
133+
normalizedAllowed.add(header.toLowerCase());
134+
}
135+
136+
for (Map.Entry<String, String> entry : allHeaders.entrySet()) {
137+
if (normalizedAllowed.contains(entry.getKey().toLowerCase())) {
138+
capturedHeaders.put(entry.getKey(), entry.getValue());
131139
}
132140
}
133141

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package io.sentry.util.network
2+
3+
import java.util.LinkedHashMap
4+
import kotlin.test.assertEquals
5+
import kotlin.test.assertTrue
6+
import org.junit.Test
7+
8+
class NetworkDetailCaptureUtilsTest {
9+
10+
@Test
11+
fun `getCaptureHeaders should match headers case-insensitively`() {
12+
// Setup: allHeaders with mixed case keys
13+
val allHeaders =
14+
LinkedHashMap<String, String>().apply {
15+
put("Content-Type", "application/json")
16+
put("Authorization", "Bearer token123")
17+
put("X-Custom-Header", "custom-value")
18+
put("accept", "application/json")
19+
}
20+
21+
// Test: allowedHeaders with different casing
22+
val allowedHeaders = arrayOf("content-type", "AUTHORIZATION", "x-custom-header", "ACCEPT")
23+
24+
val result = NetworkDetailCaptureUtils.getCaptureHeaders(allHeaders, allowedHeaders)
25+
26+
// All headers should be matched despite case differences
27+
assertEquals(4, result.size)
28+
29+
// Original casing should be preserved in output
30+
assertEquals("application/json", result["Content-Type"])
31+
assertEquals("Bearer token123", result["Authorization"])
32+
assertEquals("custom-value", result["X-Custom-Header"])
33+
assertEquals("application/json", result["accept"])
34+
35+
// Verify keys maintain original casing from allHeaders
36+
assertTrue(result.containsKey("Content-Type"))
37+
assertTrue(result.containsKey("Authorization"))
38+
assertTrue(result.containsKey("X-Custom-Header"))
39+
assertTrue(result.containsKey("accept"))
40+
}
41+
42+
@Test
43+
fun `getCaptureHeaders should handle null allHeaders`() {
44+
val allowedHeaders = arrayOf("content-type")
45+
46+
val result = NetworkDetailCaptureUtils.getCaptureHeaders(null, allowedHeaders)
47+
48+
assertTrue(result.isEmpty())
49+
}
50+
51+
@Test
52+
fun `getCaptureHeaders should handle empty allowedHeaders`() {
53+
val allHeaders = mapOf("Content-Type" to "application/json")
54+
val allowedHeaders = arrayOf<String>()
55+
56+
val result = NetworkDetailCaptureUtils.getCaptureHeaders(allHeaders, allowedHeaders)
57+
58+
assertTrue(result.isEmpty())
59+
}
60+
61+
@Test
62+
fun `getCaptureHeaders should only capture allowed headers`() {
63+
val allHeaders =
64+
mapOf(
65+
"Content-Type" to "application/json",
66+
"Authorization" to "Bearer token123",
67+
"X-Unwanted-Header" to "should-not-appear",
68+
)
69+
70+
val allowedHeaders = arrayOf("content-type", "authorization")
71+
72+
val result = NetworkDetailCaptureUtils.getCaptureHeaders(allHeaders, allowedHeaders)
73+
74+
assertEquals(2, result.size)
75+
assertEquals("application/json", result["Content-Type"])
76+
assertEquals("Bearer token123", result["Authorization"])
77+
78+
// Unwanted header should not be present
79+
assertTrue(!result.containsKey("X-Unwanted-Header"))
80+
}
81+
}

0 commit comments

Comments
 (0)