File tree Expand file tree Collapse file tree 5 files changed +51
-8
lines changed Expand file tree Collapse file tree 5 files changed +51
-8
lines changed Original file line number Diff line number Diff line change @@ -43,6 +43,7 @@ The next release will require at least [Go 1.23].
43
43
44
44
- 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 )
45
45
- 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 )
46
47
47
48
<!-- Released section -->
48
49
<!-- Don't change this section unless doing release -->
Original file line number Diff line number Diff line change @@ -13,6 +13,7 @@ import (
13
13
"strconv"
14
14
"strings"
15
15
"time"
16
+ "unicode"
16
17
17
18
"google.golang.org/grpc"
18
19
"google.golang.org/grpc/credentials"
@@ -442,13 +443,15 @@ func convHeaders(s string) (map[string]string, error) {
442
443
continue
443
444
}
444
445
445
- escKey , e := url .PathUnescape (rawKey )
446
- if e != nil {
446
+ key := strings .TrimSpace (rawKey )
447
+
448
+ // Validate the key.
449
+ if ! isValidHeaderKey (key ) {
447
450
err = errors .Join (err , fmt .Errorf ("invalid header key: %s" , rawKey ))
448
451
continue
449
452
}
450
- key := strings .TrimSpace (escKey )
451
453
454
+ // Only decode the value.
452
455
escVal , e := url .PathUnescape (rawVal )
453
456
if e != nil {
454
457
err = errors .Join (err , fmt .Errorf ("invalid header value: %s" , rawVal ))
@@ -651,3 +654,22 @@ func fallback[T any](val T) resolver[T] {
651
654
return s
652
655
}
653
656
}
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
+ }
Original file line number Diff line number Diff line change @@ -357,7 +357,6 @@ func TestNewConfig(t *testing.T) {
357
357
`tls: failed to find any PEM data in certificate input` ,
358
358
`invalid OTEL_EXPORTER_OTLP_LOGS_HEADERS value a,%ZZ=valid,key=%ZZ:` ,
359
359
`invalid header: a` ,
360
- `invalid header key: %ZZ` ,
361
360
`invalid header value: %ZZ` ,
362
361
`invalid OTEL_EXPORTER_OTLP_LOGS_COMPRESSION value xz: unknown compression: xz` ,
363
362
`invalid OTEL_EXPORTER_OTLP_LOGS_TIMEOUT value 100 seconds: strconv.Atoi: parsing "100 seconds": invalid syntax` ,
Original file line number Diff line number Diff line change @@ -14,6 +14,7 @@ import (
14
14
"strconv"
15
15
"strings"
16
16
"time"
17
+ "unicode"
17
18
18
19
"go.opentelemetry.io/otel"
19
20
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp/internal/retry"
@@ -544,13 +545,15 @@ func convHeaders(s string) (map[string]string, error) {
544
545
continue
545
546
}
546
547
547
- escKey , e := url .PathUnescape (rawKey )
548
- if e != nil {
548
+ key := strings .TrimSpace (rawKey )
549
+
550
+ // Validate the key.
551
+ if ! isValidHeaderKey (key ) {
549
552
err = errors .Join (err , fmt .Errorf ("invalid header key: %s" , rawKey ))
550
553
continue
551
554
}
552
- key := strings .TrimSpace (escKey )
553
555
556
+ // Only decode the value.
554
557
escVal , e := url .PathUnescape (rawVal )
555
558
if e != nil {
556
559
err = errors .Join (err , fmt .Errorf ("invalid header value: %s" , rawVal ))
@@ -600,3 +603,22 @@ func fallback[T any](val T) resolver[T] {
600
603
return s
601
604
}
602
605
}
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
+ }
Original file line number Diff line number Diff line change @@ -364,7 +364,6 @@ func TestNewConfig(t *testing.T) {
364
364
`tls: failed to find any PEM data in certificate input` ,
365
365
`invalid OTEL_EXPORTER_OTLP_LOGS_HEADERS value a,%ZZ=valid,key=%ZZ:` ,
366
366
`invalid header: a` ,
367
- `invalid header key: %ZZ` ,
368
367
`invalid header value: %ZZ` ,
369
368
`invalid OTEL_EXPORTER_OTLP_LOGS_COMPRESSION value xz: unknown compression: xz` ,
370
369
`invalid OTEL_EXPORTER_OTLP_LOGS_TIMEOUT value 100 seconds: strconv.Atoi: parsing "100 seconds": invalid syntax` ,
You can’t perform that action at this time.
0 commit comments