diff --git a/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java b/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java index 386de4985de..5c1a8b3425f 100644 --- a/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java +++ b/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/W3CBaggagePropagator.java @@ -10,6 +10,7 @@ import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.baggage.BaggageBuilder; import io.opentelemetry.api.baggage.BaggageEntry; +import io.opentelemetry.api.internal.PercentEscaper; import io.opentelemetry.api.internal.StringUtils; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapGetter; diff --git a/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/PercentEscaper.java b/api/all/src/main/java/io/opentelemetry/api/internal/PercentEscaper.java similarity index 98% rename from api/all/src/main/java/io/opentelemetry/api/baggage/propagation/PercentEscaper.java rename to api/all/src/main/java/io/opentelemetry/api/internal/PercentEscaper.java index d1c79bbc651..257902b6beb 100644 --- a/api/all/src/main/java/io/opentelemetry/api/baggage/propagation/PercentEscaper.java +++ b/api/all/src/main/java/io/opentelemetry/api/internal/PercentEscaper.java @@ -18,9 +18,8 @@ * the License. */ -package io.opentelemetry.api.baggage.propagation; +package io.opentelemetry.api.internal; -import io.opentelemetry.api.internal.TemporaryBuffers; import javax.annotation.CheckForNull; /** @@ -59,10 +58,13 @@ *

Note: This escaper produces uppercase hexadecimal sequences. * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + * * @author David Beaumont * @since 15.0 */ -final class PercentEscaper { +public final class PercentEscaper { /** The amount of padding (chars) to use when growing the escape buffer. */ private static final int DEST_PAD = 32; @@ -86,7 +88,7 @@ final class PercentEscaper { private static final boolean[] safeOctets = createSafeOctets(SAFE_CHARS); /** The default {@link PercentEscaper} which will *not* replace spaces with plus signs. */ - static PercentEscaper create() { + public static PercentEscaper create() { return new PercentEscaper(); } @@ -109,7 +111,7 @@ private static boolean[] createSafeOctets(String safeChars) { } /** Escape the provided String, using percent-style URL Encoding. */ - String escape(String s) { + public String escape(String s) { int slen = s.length(); for (int index = 0; index < slen; index++) { char c = s.charAt(index); diff --git a/api/all/src/test/java/io/opentelemetry/api/baggage/propagation/PercentEscaperFuzzTest.java b/api/all/src/test/java/io/opentelemetry/api/baggage/propagation/PercentEscaperFuzzTest.java index 6c4117d7953..f24fe62d387 100644 --- a/api/all/src/test/java/io/opentelemetry/api/baggage/propagation/PercentEscaperFuzzTest.java +++ b/api/all/src/test/java/io/opentelemetry/api/baggage/propagation/PercentEscaperFuzzTest.java @@ -11,6 +11,7 @@ import edu.berkeley.cs.jqf.fuzz.JQF; import edu.berkeley.cs.jqf.fuzz.junit.GuidedFuzzing; import edu.berkeley.cs.jqf.fuzz.random.NoGuidance; +import io.opentelemetry.api.internal.PercentEscaper; import java.net.URLDecoder; import org.junit.jupiter.api.Test; import org.junit.runner.Result; diff --git a/sdk-extensions/autoconfigure/build.gradle.kts b/sdk-extensions/autoconfigure/build.gradle.kts index 515b27935eb..ddf06b89e3d 100644 --- a/sdk-extensions/autoconfigure/build.gradle.kts +++ b/sdk-extensions/autoconfigure/build.gradle.kts @@ -30,6 +30,7 @@ dependencies { testImplementation(project(":sdk:testing")) testImplementation("com.linecorp.armeria:armeria-junit5") testImplementation("com.linecorp.armeria:armeria-grpc") + testImplementation("edu.berkeley.cs.jqf:jqf-fuzz") testRuntimeOnly("io.grpc:grpc-netty-shaded") testImplementation("io.opentelemetry.proto:opentelemetry-proto") } diff --git a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java index 7c43cb7f42d..16c73da7ffd 100644 --- a/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java +++ b/sdk-extensions/autoconfigure/src/main/java/io/opentelemetry/sdk/autoconfigure/ResourceConfiguration.java @@ -8,12 +8,17 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; import io.opentelemetry.sdk.autoconfigure.spi.internal.ConditionalResourceProvider; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.ResourceBuilder; import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; import java.util.HashSet; +import java.util.Map; import java.util.Set; import java.util.function.BiFunction; @@ -68,7 +73,20 @@ private static Resource createEnvironmentResource(ConfigProperties config) { // visible for testing static Attributes getAttributes(ConfigProperties configProperties) { AttributesBuilder resourceAttributes = Attributes.builder(); - configProperties.getMap(ATTRIBUTE_PROPERTY).forEach(resourceAttributes::put); + try { + for (Map.Entry entry : + configProperties.getMap(ATTRIBUTE_PROPERTY).entrySet()) { + resourceAttributes.put( + entry.getKey(), + // Attributes specified via otel.resource.attributes follow the W3C Baggage spec and + // characters outside the baggage-octet range are percent encoded + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable + URLDecoder.decode(entry.getValue(), StandardCharsets.UTF_8.displayName())); + } + } catch (UnsupportedEncodingException e) { + // Should not happen since always using standard charset + throw new ConfigurationException("Unable to decode resource attributes.", e); + } String serviceName = configProperties.getString(SERVICE_NAME_PROPERTY); if (serviceName != null) { resourceAttributes.put(ResourceAttributes.SERVICE_NAME, serviceName); diff --git a/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/ResourceConfigurationFuzzTest.java b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/ResourceConfigurationFuzzTest.java new file mode 100644 index 00000000000..3199f7320d7 --- /dev/null +++ b/sdk-extensions/autoconfigure/src/test/java/io/opentelemetry/sdk/autoconfigure/ResourceConfigurationFuzzTest.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.sdk.autoconfigure; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static java.util.Collections.singletonMap; + +import edu.berkeley.cs.jqf.fuzz.Fuzz; +import edu.berkeley.cs.jqf.fuzz.JQF; +import edu.berkeley.cs.jqf.fuzz.junit.GuidedFuzzing; +import edu.berkeley.cs.jqf.fuzz.random.NoGuidance; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.internal.PercentEscaper; +import org.junit.jupiter.api.Test; +import org.junit.runner.Result; +import org.junit.runner.RunWith; + +@SuppressWarnings("SystemOut") +class ResourceConfigurationFuzzTest { + + @RunWith(JQF.class) + public static class TestCases { + + private static final PercentEscaper escaper = PercentEscaper.create(); + + @Fuzz + public void getAttributesWithRandomValues(String value1, String value2) { + Attributes attributes = + ResourceConfiguration.getAttributes( + DefaultConfigProperties.createForTest( + singletonMap( + ResourceConfiguration.ATTRIBUTE_PROPERTY, + "key1=" + escaper.escape(value1) + ",key2=" + escaper.escape(value2)))); + + assertThat(attributes).hasSize(2).containsEntry("key1", value1).containsEntry("key2", value2); + } + } + + // driver methods to avoid having to use the vintage junit engine, and to enable increasing the + // number of iterations: + + @Test + void getAttributesWithFuzzing() { + Result result = + GuidedFuzzing.run( + TestCases.class, + "getAttributesWithRandomValues", + new NoGuidance(10000, System.out), + System.out); + assertThat(result.wasSuccessful()).isTrue(); + } +}