Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

- Configuration file can now be set via `OTEL_CONFIG_FILE` in `go.opentelemetry.io/contrib/otelconf`. (#8639)

### Changed

- Prepend `_` to the normalized environment variable name when the key starts with a digit in `go.opentelemetry.io/contrib/propagators/envcar`, ensuring POSIX compliance. (#8678)

### Fixed

- Limit the request body size at 1MB in `go.opentelemetry.io/contrib/zpages`. (#8656)
Expand Down
21 changes: 2 additions & 19 deletions propagators/envcar/carrier.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,6 @@ import (
"go.opentelemetry.io/otel/propagation"
)

// upperWithUnderscores converts a string so that A-Z and 0-9 and _ are kept
// as-is, a-z is uppercased, and all other characters are replaced with _.
func upperWithUnderscores(s string) string {
b := make([]byte, 0, len(s))
for _, r := range s {
switch {
case r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '_':
b = append(b, byte(r)) //nolint:gosec // G115: overflow is already checked.
case r >= 'a' && r <= 'z':
b = append(b, byte(r+'A'-'a'))
default:
b = append(b, '_')
}
}
return string(b)
}

// Carrier is a TextMapCarrier that uses the environment variables as a
// storage medium for propagated key-value pairs. The keys are normalised
// before being used to access the environment variables.
Expand Down Expand Up @@ -71,7 +54,7 @@ func (c *Carrier) fetch() {
// environment and all future reads will be from that store.
func (c *Carrier) Get(key string) string {
c.fetch()
return c.values[upperWithUnderscores(key)]
return c.values[normalize(key)]
}

// Set stores the key-value pair in the environment variable.
Expand All @@ -82,7 +65,7 @@ func (c *Carrier) Set(key, value string) {
if c.SetEnvFunc == nil {
return
}
k := upperWithUnderscores(key)
k := normalize(key)
c.SetEnvFunc(k, value)
}

Expand Down
48 changes: 48 additions & 0 deletions propagators/envcar/normalize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package envcar // import "go.opentelemetry.io/contrib/propagators/envcar"

import (
"unicode/utf8"
)

// normalize converts s to a valid POSIX environment variable name.
// The conversion rules are:
// - A–Z, 0–9, and _ are kept as-is.
// - a–z are uppercased.
// - All other characters are replaced with _.
// - If the result would start with a digit, an underscore is prepended.
func normalize(s string) string {
if s == "" {
return ""
}

// Pre-allocate the exact output length. If the first byte is a digit,
// the name must be prefixed with '_', so allocate one extra byte.
var b []byte
i := 0
if s[0] >= '0' && s[0] <= '9' {
b = make([]byte, utf8.RuneCountInString(s)+1)
b[0] = '_'
i = 1
} else {
b = make([]byte, utf8.RuneCountInString(s))
}

for _, r := range s {
switch {
case r >= 'A' && r <= 'Z', r >= '0' && r <= '9', r == '_':
// Uppercase letters, digits, and underscores are valid as-is.
b[i] = byte(r) //nolint:gosec // G115: overflow is not possible.
case r >= 'a' && r <= 'z':
// Lowercase letters are converted to uppercase.
b[i] = byte(r + 'A' - 'a')
default:
// All other characters (including non-ASCII runes) become underscores.
b[i] = '_'
}
i++
}
return string(b)
}
49 changes: 49 additions & 0 deletions propagators/envcar/normalize_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package envcar

import (
"testing"

"github.com/stretchr/testify/assert"
)

var normalizeCases = []struct {
in, want string
}{
{"", ""},
{"ABC", "ABC"},
{"abc", "ABC"},
{"01239", "_01239"},
{"0abc", "_0ABC"},
{"9", "_9"},
{"a_b_c", "A_B_C"},
{"hello-world", "HELLO_WORLD"},
{"foo.bar", "FOO_BAR"},
{"Content-Type", "CONTENT_TYPE"},
{"traceparent", "TRACEPARENT"},
{"key with spaces", "KEY_WITH_SPACES"},
{"MiXeD_123!", "MIXED_123_"},
{"🧳", "_"},
{"Mój Bagaż", "M_J_BAGA_"},
}

func TestNormalize(t *testing.T) {
for _, tc := range normalizeCases {
t.Run(tc.in, func(t *testing.T) {
assert.Equal(t, tc.want, normalize(tc.in))
})
}
}

func BenchmarkNormalize(b *testing.B) {
for _, tc := range normalizeCases {
b.Run(tc.in, func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
normalize(tc.in)
}
})
}
}
35 changes: 0 additions & 35 deletions propagators/envcar/upper_test.go

This file was deleted.

Loading