Skip to content

Commit c524653

Browse files
author
Chris Stockton
committed
fix: preserve the dollar escaping semantics of godotenv
Also removes _any_ source data from showing from config errors.
1 parent cee6db1 commit c524653

2 files changed

Lines changed: 41 additions & 13 deletions

File tree

internal/conf/envparse/envparse.go

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ package envparse
3434
import (
3535
"bytes"
3636
"errors"
37-
"fmt"
3837
"io"
3938
"strings"
4039
"unicode"
@@ -158,10 +157,7 @@ loop:
158157
if unicode.IsLetter(rchar) || unicode.IsNumber(rchar) || rchar == '.' {
159158
continue
160159
}
161-
162-
return "", nil, fmt.Errorf(
163-
`unexpected character %q in variable name near %q`,
164-
string(char), string(src))
160+
return "", nil, errors.New("unexpected character in variable name")
165161
}
166162
}
167163

@@ -175,6 +171,26 @@ loop:
175171
return key, cutset, nil
176172
}
177173

174+
// expandDollarEscapes preserves godotenvs dollar escaping.
175+
func expandDollarEscapes(src []byte) []byte {
176+
var n int
177+
for r := 0; r < len(src); r++ {
178+
if src[r] != '$' {
179+
src[n] = src[r]
180+
n++
181+
continue
182+
}
183+
184+
if n > 0 && src[n-1] == '\\' {
185+
n--
186+
}
187+
188+
src[n] = '$'
189+
n++
190+
}
191+
return src[:n]
192+
}
193+
178194
// extractVarValue extracts variable value and returns rest of slice.
179195
func extractVarValue(src []byte) (value string, rest []byte, err error) {
180196
quote, hasPrefix := hasQuotePrefix(src)
@@ -211,8 +227,8 @@ func extractVarValue(src []byte) (value string, rest []byte, err error) {
211227
}
212228
}
213229

214-
trimmed := strings.TrimFunc(string(line[0:endOfVar]), isSpace)
215-
return trimmed, src[endOfLine:], nil
230+
trimmed := []byte(strings.TrimFunc(string(line[0:endOfVar]), isSpace))
231+
return string(expandDollarEscapes(trimmed)), src[endOfLine:], nil
216232
}
217233

218234
// lookup quoted string terminator
@@ -227,6 +243,7 @@ func extractVarValue(src []byte) (value string, rest []byte, err error) {
227243
valueBytes := src[1:i]
228244
if quote == prefixDoubleQuote {
229245
valueBytes = expandEscapes(valueBytes)
246+
valueBytes = expandDollarEscapes(valueBytes)
230247
}
231248

232249
value = string(valueBytes)
@@ -238,7 +255,7 @@ func extractVarValue(src []byte) (value string, rest []byte, err error) {
238255
if valEndIndex == -1 {
239256
valEndIndex = len(src)
240257
}
241-
return "", nil, fmt.Errorf("unterminated quoted value %s", src[:valEndIndex])
258+
return "", nil, errors.New("unterminated quoted value")
242259
}
243260

244261
func isEscaped(src []byte, index int) bool {

internal/conf/envparse/envparse_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import (
99
"github.com/stretchr/testify/require"
1010
)
1111

12-
func TestParseCompatibility(t *testing.T) {
13-
join := func(s ...string) []byte {
14-
return []byte(strings.Join(s, "\n"))
15-
}
12+
func join(s ...string) []byte {
13+
return []byte(strings.Join(s, "\n"))
14+
}
1615

16+
func TestParseCompatibility(t *testing.T) {
1717
type testCase struct {
1818
name string
1919
from []byte
@@ -35,6 +35,16 @@ func TestParseCompatibility(t *testing.T) {
3535
from: []byte("FOO=#bar"),
3636
exp: map[string]string{"FOO": "#bar"},
3737
},
38+
{
39+
name: "unterminated squote value",
40+
from: []byte(`FOO='bar`),
41+
errStr: "unterminated quoted value",
42+
},
43+
{
44+
name: "unterminated dquote value",
45+
from: []byte(`FOO="bar`),
46+
errStr: "unterminated quoted value",
47+
},
3848

3949
// godot - TestParse
4050
{
@@ -113,7 +123,7 @@ func TestParseCompatibility(t *testing.T) {
113123
`INVALID LINE`,
114124
`foo=bar`,
115125
),
116-
errStr: `unexpected character "\n" in variable name near "INVALID LINE\nfoo=bar"`,
126+
errStr: `unexpected character`,
117127
},
118128
{
119129
name: "godot - fixtures/plain.env",
@@ -449,6 +459,7 @@ func TestParseCompatibility(t *testing.T) {
449459

450460
runParseTest := func(t *testing.T, tt testCase) {
451461
check := func(t *testing.T, exp, got map[string]string, err error) {
462+
t.Helper()
452463
if tt.errStr != "" {
453464
require.Error(t, err)
454465
require.Contains(t, err.Error(), tt.errStr)

0 commit comments

Comments
 (0)