Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@
import io.opentelemetry.context.propagation.TextMapGetter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
Expand Down Expand Up @@ -46,7 +42,6 @@
*/
public final class EnvironmentGetter implements TextMapGetter<Map<String, String>> {

private static final Logger logger = Logger.getLogger(EnvironmentGetter.class.getName());
private static final EnvironmentGetter INSTANCE = new EnvironmentGetter();

private EnvironmentGetter() {}
Expand All @@ -61,11 +56,7 @@ public Iterable<String> keys(Map<String, String> carrier) {
if (carrier == null) {
return Collections.emptyList();
}
List<String> result = new ArrayList<>(carrier.size());
for (String key : carrier.keySet()) {
result.add(key.toLowerCase(Locale.ROOT));
}
return result;
return new ArrayList<>(carrier.keySet());
Comment thread
jack-berg marked this conversation as resolved.
Outdated
}

@Nullable
Expand All @@ -74,20 +65,8 @@ public String get(@Nullable Map<String, String> carrier, String key) {
if (carrier == null || key == null) {
return null;
}
// Spec recommends using uppercase and underscores for environment variable
// names for maximum
// cross-platform compatibility.
String sanitizedKey = key.replace('.', '_').replace('-', '_').toUpperCase(Locale.ROOT);
String value = carrier.get(sanitizedKey);
if (value != null && !EnvironmentSetter.isValidHttpHeaderValue(value)) {
logger.log(
Level.FINE,
"Ignoring environment variable '{0}': "
+ "value contains characters not valid in HTTP header fields per RFC 9110.",
sanitizedKey);
return null;
}
return value;
String normalizedKey = EnvironmentSetter.normalizeKey(key);
return carrier.get(normalizedKey);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@
package io.opentelemetry.api.incubator.propagation;

import io.opentelemetry.context.propagation.TextMapSetter;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.Nullable;

/**
Expand All @@ -30,8 +27,10 @@
* conventions:
*
* <ul>
* <li>Converts keys to uppercase (e.g., {@code traceparent} becomes {@code TRACEPARENT})
* <li>Replaces {@code .} and {@code -} with underscores
* <li>ASCII letters are converted to uppercase
* <li>Any character that is not an ASCII letter, digit, or underscore is replaced with an
* underscore
* <li>If the result would start with a digit, an underscore is prepended
* </ul>
*
* <p>Values are validated to contain only characters valid in HTTP header fields per <a
Expand All @@ -49,7 +48,6 @@
*/
public final class EnvironmentSetter implements TextMapSetter<Map<String, String>> {

private static final Logger logger = Logger.getLogger(EnvironmentSetter.class.getName());
private static final EnvironmentSetter INSTANCE = new EnvironmentSetter();

private EnvironmentSetter() {}
Expand All @@ -64,35 +62,36 @@ public void set(@Nullable Map<String, String> carrier, String key, String value)
if (carrier == null || key == null || value == null) {
return;
}
if (!isValidHttpHeaderValue(value)) {
logger.log(
Level.FINE,
"Skipping environment variable injection for key ''{0}'': "
+ "value contains characters not valid in HTTP header fields per RFC 9110.",
key);
return;
}
// Spec recommends using uppercase and underscores for environment variable
// names for maximum
// cross-platform compatibility.
String sanitizedKey = key.replace('.', '_').replace('-', '_').toUpperCase(Locale.ROOT);
carrier.put(sanitizedKey, value);
String normalizedKey = normalizeKey(key);
carrier.put(normalizedKey, value);
}

/**
* Checks whether a string contains only characters valid in HTTP header field values per <a
* href="https://datatracker.ietf.org/doc/html/rfc9110#section-5.5">RFC 9110 Section 5.5</a>.
* Valid characters are: visible ASCII (0x21-0x7E), space (0x20), and horizontal tab (0x09).
* Normalizes a key to be a valid environment variable name.
*
* <ul>
* <li>ASCII letters are converted to uppercase
* <li>Any character that is not an ASCII letter, digit, or underscore is replaced with an
* underscore (including {@code .}, {@code -}, whitespace, and control characters)
* <li>If the result would start with a digit, an underscore is prepended
* </ul>
*/
static boolean isValidHttpHeaderValue(String value) {
for (int i = 0; i < value.length(); i++) {
char ch = value.charAt(i);
// VCHAR (0x21-0x7E), SP (0x20), HTAB (0x09)
if (ch != '\t' && (ch < ' ' || ch > '~')) {
return false;
static String normalizeKey(String key) {
StringBuilder sb = new StringBuilder(key.length() + 1);
for (int i = 0; i < key.length(); i++) {
char ch = key.charAt(i);
if (ch >= 'a' && ch <= 'z') {
sb.append((char) (ch - 32));
} else if ((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_') {
sb.append(ch);
} else {
sb.append('_');
}
}
return true;
if (sb.length() > 0 && sb.charAt(0) >= '0' && sb.charAt(0) <= '9') {
sb.insert(0, '_');
}
return sb.toString();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,32 +50,29 @@ void keys() {
carrier.put("K1", "V1");
carrier.put("K2", "V2");

assertThat(EnvironmentGetter.getInstance().keys(carrier)).containsExactlyInAnyOrder("k1", "k2");
assertThat(EnvironmentGetter.getInstance().keys(carrier)).containsExactlyInAnyOrder("K1", "K2");
assertThat(EnvironmentGetter.getInstance().keys(null)).isEmpty();
}

@Test
void get_validHeaderValues() {
void get_valuesAreUnmodified() {
Map<String, String> carrier = new HashMap<>();
carrier.put("KEY1", "simple-value");
carrier.put("KEY2", "value with spaces");
carrier.put("KEY3", "value\twith\ttabs");
carrier.put("KEY4", "value\u0000with\u0001control");
carrier.put("KEY5", "value\nwith\nnewlines");
carrier.put("KEY6", "value\u0080non-ascii");

assertThat(EnvironmentGetter.getInstance().get(carrier, "key1")).isEqualTo("simple-value");
assertThat(EnvironmentGetter.getInstance().get(carrier, "key2")).isEqualTo("value with spaces");
assertThat(EnvironmentGetter.getInstance().get(carrier, "key3")).isEqualTo("value\twith\ttabs");
}

@Test
void get_invalidHeaderValues() {
Map<String, String> carrier = new HashMap<>();
carrier.put("KEY1", "value\u0000with\u0001control");
carrier.put("KEY2", "value\nwith\nnewlines");
carrier.put("KEY3", "value\u0080non-ascii");

assertThat(EnvironmentGetter.getInstance().get(carrier, "key1")).isNull();
assertThat(EnvironmentGetter.getInstance().get(carrier, "key2")).isNull();
assertThat(EnvironmentGetter.getInstance().get(carrier, "key3")).isNull();
assertThat(EnvironmentGetter.getInstance().get(carrier, "key4"))
.isEqualTo("value\u0000with\u0001control");
assertThat(EnvironmentGetter.getInstance().get(carrier, "key5"))
.isEqualTo("value\nwith\nnewlines");
assertThat(EnvironmentGetter.getInstance().get(carrier, "key6"))
.isEqualTo("value\u0080non-ascii");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,21 @@ void set_null() {
}

@Test
void set_validHeaderValues() {
void set_valuesAreUnmodified() {
Map<String, String> carrier = new HashMap<>();
// Printable ASCII and tab are valid per RFC 9110
EnvironmentSetter.getInstance().set(carrier, "key1", "simple-value");
EnvironmentSetter.getInstance().set(carrier, "key2", "value with spaces");
EnvironmentSetter.getInstance().set(carrier, "key3", "value\twith\ttabs");
EnvironmentSetter.getInstance().set(carrier, "key4", "value\u0000with\u0001control");
EnvironmentSetter.getInstance().set(carrier, "key5", "value\nwith\nnewlines");
EnvironmentSetter.getInstance().set(carrier, "key6", "value\u0080non-ascii");

assertThat(carrier).containsEntry("KEY1", "simple-value");
assertThat(carrier).containsEntry("KEY2", "value with spaces");
assertThat(carrier).containsEntry("KEY3", "value\twith\ttabs");
}

@Test
void set_invalidHeaderValues() {
Map<String, String> carrier = new HashMap<>();
// Control characters and non-ASCII are invalid per RFC 9110
EnvironmentSetter.getInstance().set(carrier, "key1", "value\u0000with\u0001control");
EnvironmentSetter.getInstance().set(carrier, "key2", "value\nwith\nnewlines");
EnvironmentSetter.getInstance().set(carrier, "key3", "value\rwith\rcarriage");
EnvironmentSetter.getInstance().set(carrier, "key4", "value\u0080non-ascii");

assertThat(carrier).isEmpty();
assertThat(carrier).containsEntry("KEY4", "value\u0000with\u0001control");
assertThat(carrier).containsEntry("KEY5", "value\nwith\nnewlines");
assertThat(carrier).containsEntry("KEY6", "value\u0080non-ascii");
}

@Test
Expand Down
Loading