Skip to content

Commit dd3776a

Browse files
committed
bug fix
1 parent f04e951 commit dd3776a

File tree

5 files changed

+51
-8
lines changed

5 files changed

+51
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ The next release will require at least [Go 1.23].
4343

4444
- Eliminate goroutine leak for the processor returned by `NewSimpleSpanProcessor` when `Shutdown` is called and the passed `ctx` is canceled and `SpanExporter.Shutdown` has not returned. (#6368)
4545
- Eliminate goroutine leak for the processor returned by `NewBatchSpanProcessor` when `ForceFlush` is called and the passed `ctx` is canceled and `SpanExporter.Export` has not returned. (#6369)
46+
- Eliminate percent decoding for the header keys when parsing OTEL_EXPORTER_OTLP_LOGS_HEADERS env vars. (#6392)
4647

4748
<!-- Released section -->
4849
<!-- Don't change this section unless doing release -->

exporters/otlp/otlplog/otlploggrpc/config.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"strconv"
1414
"strings"
1515
"time"
16+
"unicode"
1617

1718
"google.golang.org/grpc"
1819
"google.golang.org/grpc/credentials"
@@ -442,13 +443,15 @@ func convHeaders(s string) (map[string]string, error) {
442443
continue
443444
}
444445

445-
escKey, e := url.PathUnescape(rawKey)
446-
if e != nil {
446+
key := strings.TrimSpace(rawKey)
447+
448+
// Validate the key.
449+
if !isValidHeaderKey(key) {
447450
err = errors.Join(err, fmt.Errorf("invalid header key: %s", rawKey))
448451
continue
449452
}
450-
key := strings.TrimSpace(escKey)
451453

454+
// Only decode the value.
452455
escVal, e := url.PathUnescape(rawVal)
453456
if e != nil {
454457
err = errors.Join(err, fmt.Errorf("invalid header value: %s", rawVal))
@@ -651,3 +654,22 @@ func fallback[T any](val T) resolver[T] {
651654
return s
652655
}
653656
}
657+
658+
func isValidHeaderKey(key string) bool {
659+
if key == "" {
660+
return false
661+
}
662+
for _, c := range key {
663+
if !isTokenChar(c) {
664+
return false
665+
}
666+
}
667+
return true
668+
}
669+
670+
func isTokenChar(c rune) bool {
671+
return c <= unicode.MaxASCII && (unicode.IsLetter(c) ||
672+
unicode.IsDigit(c) ||
673+
c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
674+
c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~')
675+
}

exporters/otlp/otlplog/otlploggrpc/config_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,6 @@ func TestNewConfig(t *testing.T) {
357357
`tls: failed to find any PEM data in certificate input`,
358358
`invalid OTEL_EXPORTER_OTLP_LOGS_HEADERS value a,%ZZ=valid,key=%ZZ:`,
359359
`invalid header: a`,
360-
`invalid header key: %ZZ`,
361360
`invalid header value: %ZZ`,
362361
`invalid OTEL_EXPORTER_OTLP_LOGS_COMPRESSION value xz: unknown compression: xz`,
363362
`invalid OTEL_EXPORTER_OTLP_LOGS_TIMEOUT value 100 seconds: strconv.Atoi: parsing "100 seconds": invalid syntax`,

exporters/otlp/otlplog/otlploghttp/config.go

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"strconv"
1515
"strings"
1616
"time"
17+
"unicode"
1718

1819
"go.opentelemetry.io/otel"
1920
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/retry"
@@ -544,13 +545,15 @@ func convHeaders(s string) (map[string]string, error) {
544545
continue
545546
}
546547

547-
escKey, e := url.PathUnescape(rawKey)
548-
if e != nil {
548+
key := strings.TrimSpace(rawKey)
549+
550+
// Validate the key.
551+
if !isValidHeaderKey(key) {
549552
err = errors.Join(err, fmt.Errorf("invalid header key: %s", rawKey))
550553
continue
551554
}
552-
key := strings.TrimSpace(escKey)
553555

556+
// Only decode the value.
554557
escVal, e := url.PathUnescape(rawVal)
555558
if e != nil {
556559
err = errors.Join(err, fmt.Errorf("invalid header value: %s", rawVal))
@@ -600,3 +603,22 @@ func fallback[T any](val T) resolver[T] {
600603
return s
601604
}
602605
}
606+
607+
func isValidHeaderKey(key string) bool {
608+
if key == "" {
609+
return false
610+
}
611+
for _, c := range key {
612+
if !isTokenChar(c) {
613+
return false
614+
}
615+
}
616+
return true
617+
}
618+
619+
func isTokenChar(c rune) bool {
620+
return c <= unicode.MaxASCII && (unicode.IsLetter(c) ||
621+
unicode.IsDigit(c) ||
622+
c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
623+
c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~')
624+
}

exporters/otlp/otlplog/otlploghttp/config_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,6 @@ func TestNewConfig(t *testing.T) {
364364
`tls: failed to find any PEM data in certificate input`,
365365
`invalid OTEL_EXPORTER_OTLP_LOGS_HEADERS value a,%ZZ=valid,key=%ZZ:`,
366366
`invalid header: a`,
367-
`invalid header key: %ZZ`,
368367
`invalid header value: %ZZ`,
369368
`invalid OTEL_EXPORTER_OTLP_LOGS_COMPRESSION value xz: unknown compression: xz`,
370369
`invalid OTEL_EXPORTER_OTLP_LOGS_TIMEOUT value 100 seconds: strconv.Atoi: parsing "100 seconds": invalid syntax`,

0 commit comments

Comments
 (0)