|
11 | 11 | import io.opentelemetry.common.ComponentLoader; |
12 | 12 | import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; |
13 | 13 | import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; |
14 | | -import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; |
15 | 14 | import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; |
16 | 15 | import io.opentelemetry.sdk.autoconfigure.spi.internal.ConditionalResourceProvider; |
17 | 16 | import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties; |
18 | 17 | import io.opentelemetry.sdk.resources.Resource; |
19 | 18 | import io.opentelemetry.sdk.resources.ResourceBuilder; |
20 | | -import java.io.UnsupportedEncodingException; |
21 | | -import java.net.URLDecoder; |
22 | 19 | import java.nio.charset.StandardCharsets; |
23 | 20 | import java.util.Collections; |
24 | 21 | import java.util.HashSet; |
@@ -68,18 +65,13 @@ public static Resource createEnvironmentResource() { |
68 | 65 | @SuppressWarnings("JdkObsolete") // Recommended alternative was introduced in java 10 |
69 | 66 | public static Resource createEnvironmentResource(ConfigProperties config) { |
70 | 67 | AttributesBuilder resourceAttributes = Attributes.builder(); |
71 | | - try { |
72 | | - for (Map.Entry<String, String> entry : config.getMap(ATTRIBUTE_PROPERTY).entrySet()) { |
73 | | - resourceAttributes.put( |
74 | | - entry.getKey(), |
75 | | - // Attributes specified via otel.resource.attributes follow the W3C Baggage spec and |
76 | | - // characters outside the baggage-octet range are percent encoded |
77 | | - // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable |
78 | | - URLDecoder.decode(entry.getValue(), StandardCharsets.UTF_8.name())); |
79 | | - } |
80 | | - } catch (UnsupportedEncodingException e) { |
81 | | - // Should not happen since always using standard charset |
82 | | - throw new ConfigurationException("Unable to decode resource attributes.", e); |
| 68 | + for (Map.Entry<String, String> entry : config.getMap(ATTRIBUTE_PROPERTY).entrySet()) { |
| 69 | + resourceAttributes.put( |
| 70 | + entry.getKey(), |
| 71 | + // Attributes specified via otel.resource.attributes follow the W3C Baggage spec and |
| 72 | + // characters outside the baggage-octet range are percent encoded |
| 73 | + // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/resource/sdk.md#specifying-resource-information-via-an-environment-variable |
| 74 | + decodeResourceAttributes(entry.getValue())); |
83 | 75 | } |
84 | 76 | String serviceName = config.getString(SERVICE_NAME_PROPERTY); |
85 | 77 | if (serviceName != null) { |
@@ -133,5 +125,52 @@ static Resource filterAttributes(Resource resource, ConfigProperties configPrope |
133 | 125 | return builder.build(); |
134 | 126 | } |
135 | 127 |
|
| 128 | + /** |
| 129 | + * Decodes percent-encoded characters in resource attribute values per W3C Baggage spec. |
| 130 | + * |
| 131 | + * <p>Unlike {@link java.net.URLDecoder}, this method: |
| 132 | + * |
| 133 | + * <ul> |
| 134 | + * <li>Preserves '+' as a literal plus sign (URLDecoder decodes '+' as space) |
| 135 | + * <li>Preserves invalid percent sequences as literals (e.g., "%2G", "%", "%2") |
| 136 | + * <li>Supports multi-byte UTF-8 sequences (e.g., "%C3%A9" decodes to "é") |
| 137 | + * </ul> |
| 138 | + * |
| 139 | + * @param value the percent-encoded string |
| 140 | + * @return the decoded string |
| 141 | + */ |
| 142 | + private static String decodeResourceAttributes(String value) { |
| 143 | + // no percent signs means nothing to decode |
| 144 | + if (value.indexOf('%') < 0) { |
| 145 | + return value; |
| 146 | + } |
| 147 | + |
| 148 | + int n = value.length(); |
| 149 | + // Use byte array to properly handle multi-byte UTF-8 sequences |
| 150 | + byte[] bytes = new byte[n]; |
| 151 | + int pos = 0; |
| 152 | + |
| 153 | + for (int i = 0; i < n; i++) { |
| 154 | + char c = value.charAt(i); |
| 155 | + // Check for percent-encoded sequence i.e. '%' followed by two hex digits |
| 156 | + if (c == '%' && i + 2 < n) { |
| 157 | + int d1 = Character.digit(value.charAt(i + 1), 16); |
| 158 | + int d2 = Character.digit(value.charAt(i + 2), 16); |
| 159 | + // Valid hex digits return 0-15, invalid returns -1 |
| 160 | + if (d1 != -1 && d2 != -1) { |
| 161 | + // Combine two hex digits into a single byte (e.g., "2F" becomes 0x2F) |
| 162 | + bytes[pos++] = (byte) ((d1 << 4) + d2); |
| 163 | + // Skip the two hex digits (loop will also do i++) |
| 164 | + i += 2; |
| 165 | + continue; |
| 166 | + } |
| 167 | + } |
| 168 | + // Keep '+' as '+' (unlike URLDecoder) and preserve invalid percent sequences which will be |
| 169 | + // treated as literals |
| 170 | + bytes[pos++] = (byte) c; |
| 171 | + } |
| 172 | + return new String(bytes, 0, pos, StandardCharsets.UTF_8); |
| 173 | + } |
| 174 | + |
136 | 175 | private ResourceConfiguration() {} |
137 | 176 | } |
0 commit comments