@@ -338,7 +338,7 @@ func TestNewConfig(t *testing.T) {
338338 name : "InvalidEnvironmentVariables" ,
339339 envars : map [string ]string {
340340 "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT" : "%invalid" ,
341- "OTEL_EXPORTER_OTLP_LOGS_HEADERS" : "a,%ZZ=valid, key=%ZZ " ,
341+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS" : "invalid key=value " ,
342342 "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION" : "xz" ,
343343 "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT" : "100 seconds" ,
344344 "OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE" : "invalid_cert" ,
@@ -355,10 +355,7 @@ func TestNewConfig(t *testing.T) {
355355 `failed to load TLS:` ,
356356 `certificate not added` ,
357357 `tls: failed to find any PEM data in certificate input` ,
358- `invalid OTEL_EXPORTER_OTLP_LOGS_HEADERS value a,%ZZ=valid,key=%ZZ:` ,
359- `invalid header: a` ,
360- `invalid header key: %ZZ` ,
361- `invalid header value: %ZZ` ,
358+ `invalid OTEL_EXPORTER_OTLP_LOGS_HEADERS value invalid key=value: invalid header key: invalid key` ,
362359 `invalid OTEL_EXPORTER_OTLP_LOGS_COMPRESSION value xz: unknown compression: xz` ,
363360 `invalid OTEL_EXPORTER_OTLP_LOGS_TIMEOUT value 100 seconds: strconv.Atoi: parsing "100 seconds": invalid syntax` ,
364361 },
@@ -440,6 +437,47 @@ func TestNewConfig(t *testing.T) {
440437 timeout : newSetting (defaultTimeout ),
441438 },
442439 },
440+ {
441+ name : "with percent-encoded headers" ,
442+ envars : map [string ]string {
443+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT" : "https://env.endpoint:8080/prefix" ,
444+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS" : "user%2Did=42,user%20name=alice%20smith" ,
445+ "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION" : "gzip" ,
446+ "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT" : "15000" ,
447+ "OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE" : "cert_path" ,
448+ "OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE" : "cert_path" ,
449+ "OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY" : "key_path" ,
450+ },
451+ want : config {
452+ endpoint : newSetting ("env.endpoint:8080" ),
453+ insecure : newSetting (false ),
454+ tlsCfg : newSetting (tlsCfg ),
455+ headers : newSetting (map [string ]string {"user%2Did" : "42" , "user%20name" : "alice smith" }),
456+ compression : newSetting (GzipCompression ),
457+ timeout : newSetting (15 * time .Second ),
458+ retryCfg : newSetting (defaultRetryCfg ),
459+ },
460+ },
461+ {
462+ name : "with invalid header key" ,
463+ envars : map [string ]string {
464+ "OTEL_EXPORTER_OTLP_LOGS_ENDPOINT" : "https://env.endpoint:8080/prefix" ,
465+ "OTEL_EXPORTER_OTLP_LOGS_HEADERS" : "valid-key=value,invalid key=value" ,
466+ "OTEL_EXPORTER_OTLP_LOGS_COMPRESSION" : "gzip" ,
467+ "OTEL_EXPORTER_OTLP_LOGS_TIMEOUT" : "15000" ,
468+ "OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE" : "cert_path" ,
469+ "OTEL_EXPORTER_OTLP_LOGS_CLIENT_CERTIFICATE" : "cert_path" ,
470+ "OTEL_EXPORTER_OTLP_LOGS_CLIENT_KEY" : "key_path" ,
471+ },
472+ want : config {
473+ endpoint : newSetting ("env.endpoint:8080" ),
474+ insecure : newSetting (false ),
475+ tlsCfg : newSetting (tlsCfg ),
476+ compression : newSetting (GzipCompression ),
477+ timeout : newSetting (15 * time .Second ),
478+ retryCfg : newSetting (defaultRetryCfg ),
479+ },
480+ },
443481 }
444482
445483 for _ , tc := range testcases {
@@ -494,3 +532,88 @@ func assertTLSConfig(t *testing.T, want, got setting[*tls.Config]) {
494532 }
495533 assert .Equal (t , want .Value .Certificates , got .Value .Certificates , "Certificates" )
496534}
535+
536+ func TestConvHeaders (t * testing.T ) {
537+ tests := []struct {
538+ name string
539+ value string
540+ want map [string ]string
541+ wantErr bool
542+ }{
543+ {
544+ name : "simple test" ,
545+ value : "userId=alice" ,
546+ want : map [string ]string {"userId" : "alice" },
547+ wantErr : false ,
548+ },
549+ {
550+ name : "simple test with spaces" ,
551+ value : " userId = alice " ,
552+ want : map [string ]string {"userId" : "alice" },
553+ wantErr : false ,
554+ },
555+ {
556+ name : "simple header conforms to RFC 3986 spec" ,
557+ value : " userId = alice+test " ,
558+ want : map [string ]string {"userId" : "alice+test" },
559+ wantErr : false ,
560+ },
561+ {
562+ name : "multiple headers encoded" ,
563+ value : "userId=alice,serverNode=DF%3A28,isProduction=false" ,
564+ want : map [string ]string {
565+ "userId" : "alice" ,
566+ "serverNode" : "DF:28" ,
567+ "isProduction" : "false" ,
568+ },
569+ wantErr : false ,
570+ },
571+ {
572+ name : "multiple headers encoded per RFC 3986 spec" ,
573+ value : "userId=alice+test,serverNode=DF%3A28,isProduction=false,namespace=localhost/test" ,
574+ want : map [string ]string {
575+ "userId" : "alice+test" ,
576+ "serverNode" : "DF:28" ,
577+ "isProduction" : "false" ,
578+ "namespace" : "localhost/test" ,
579+ },
580+ wantErr : false ,
581+ },
582+ {
583+ name : "invalid headers format" ,
584+ value : "userId:alice" ,
585+ want : map [string ]string {},
586+ wantErr : true ,
587+ },
588+ {
589+ name : "invalid key" ,
590+ value : "%XX=missing,userId=alice" ,
591+ want : map [string ]string {
592+ "%XX" : "missing" ,
593+ "userId" : "alice" ,
594+ },
595+ wantErr : false ,
596+ },
597+ {
598+ name : "invalid value" ,
599+ value : "missing=%XX,userId=alice" ,
600+ want : map [string ]string {
601+ "userId" : "alice" ,
602+ },
603+ wantErr : true ,
604+ },
605+ }
606+
607+ for _ , tt := range tests {
608+ t .Run (tt .name , func (t * testing.T ) {
609+ keyValues , err := convHeaders (tt .value )
610+ assert .Equal (t , tt .want , keyValues )
611+
612+ if tt .wantErr {
613+ assert .Error (t , err , "expected an error but got nil" )
614+ } else {
615+ assert .NoError (t , err , "expected no error but got one" )
616+ }
617+ })
618+ }
619+ }
0 commit comments