Skip to content

Commit

Permalink
Fix to properly deal with block scopes in lex.
Browse files Browse the repository at this point in the history
We had a bug that was reported that was not handling a block scope where the terminating '}' was on a new line.

Signed-off-by: Derek Collison <derek@nats.io>
  • Loading branch information
derekcollison committed May 10, 2024
1 parent 006593f commit fa170aa
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 1 deletion.
68 changes: 67 additions & 1 deletion conf/lex.go
Expand Up @@ -263,7 +263,8 @@ func lexTop(lx *lexer) stateFn {

switch r {
case topOptStart:
return lexSkip(lx, lexTop)
lx.push(lexTop)
return lexSkip(lx, lexBlockStart)
case commentHashStart:
lx.push(lexTop)
return lexCommentStart
Expand Down Expand Up @@ -318,6 +319,71 @@ func lexTopValueEnd(lx *lexer) stateFn {
"comment or EOF, but got '%v' instead.", r)
}

func lexBlockStart(lx *lexer) stateFn {
r := lx.next()
if unicode.IsSpace(r) {
return lexSkip(lx, lexBlockStart)
}

switch r {
case topOptStart:
lx.push(lexBlockEnd)
return lexSkip(lx, lexBlockStart)
case commentHashStart:
lx.push(lexBlockEnd)
return lexCommentStart
case commentSlashStart:
rn := lx.next()
if rn == commentSlashStart {
lx.push(lexBlockEnd)
return lexCommentStart
}
lx.backup()
fallthrough
case eof:
if lx.pos > lx.start {
return lx.errorf("Unexpected EOF.")
}
lx.emit(itemEOF)
return nil
}

// At this point, the only valid item can be a key, so we back up
// and let the key lexer do the rest.
lx.backup()
lx.push(lexBlockEnd)
return lexKeyStart
}

// lexBlockEnd is entered whenever a block-level value has been consumed.
// It must see only whitespace, and will turn back to lexTop upon a "}".
func lexBlockEnd(lx *lexer) stateFn {
r := lx.next()
switch {
case r == commentHashStart:
// a comment will read to a new line for us.
lx.push(lexBlockEnd)
return lexCommentStart
case r == commentSlashStart:
rn := lx.next()
if rn == commentSlashStart {
lx.push(lexBlockEnd)
return lexCommentStart
}
lx.backup()
fallthrough
case isNL(r) || isWhitespace(r):
return lexBlockEnd
case r == optValTerm || r == topOptValTerm:
lx.ignore()
return lexBlockStart
case r == topOptTerm:
lx.ignore()
return lx.pop()
}
return lx.errorf("Expected a block-level value to end with a '}', but got '%v' instead.", r)
}

// lexKeyStart consumes a key name up until the first non-whitespace character.
// lexKeyStart will ignore whitespace. It will also eat enclosing quotes.
func lexKeyStart(lx *lexer) stateFn {
Expand Down
75 changes: 75 additions & 0 deletions conf/parse_test.go
Expand Up @@ -740,3 +740,78 @@ func TestJSONParseCompat(t *testing.T) {
})
}
}

func TestBlocks(t *testing.T) {
for _, test := range []struct {
name string
input string
expected map[string]any
err string
linepos string
}{
{
"inline block",
`{ listen: 0.0.0.0:4222 }`,
map[string]any{
"listen": "0.0.0.0:4222",
},
"", "",
},
{
"newline block",
`{
listen: 0.0.0.0:4222
}`,
map[string]any{
"listen": "0.0.0.0:4222",
},
"", "",
},
{
"newline block with trailing comment",
`
{
listen: 0.0.0.0:4222
}
# wibble
`,
map[string]any{
"listen": "0.0.0.0:4222",
},
"", "",
},
{
"nested newline blocks with trailing comment",
`
{
{
listen: 0.0.0.0:4222 // random comment
}
# wibble1
}
# wibble2
`,
map[string]any{
"listen": "0.0.0.0:4222",
},
"", "",
},
} {
t.Run(test.name, func(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "nats.conf-")
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(f.Name(), []byte(test.input), 066); err != nil {
t.Error(err)
}
if m, err := ParseFile(f.Name()); err == nil {
if !reflect.DeepEqual(m, test.expected) {
t.Fatalf("Not Equal:\nReceived: '%+v'\nExpected: '%+v'\n", m, test.expected)
}
} else if !strings.Contains(err.Error(), test.err) || !strings.Contains(err.Error(), test.linepos) {
t.Errorf("expected invalid conf error, got: %v", err)
}
})
}
}
18 changes: 18 additions & 0 deletions server/server_test.go
Expand Up @@ -2121,3 +2121,21 @@ func TestServerAuthBlockAndSysAccounts(t *testing.T) {
_, err = nats.Connect(s.ClientURL())
require_Error(t, err, nats.ErrAuthorization, errors.New("nats: Authorization Violation"))
}

// https://github.com/nats-io/nats-server/issues/5396
func TestServerConfigLastLineComments(t *testing.T) {
conf := createConfFile(t, []byte(`
{
"listen": "0.0.0.0:4222"
}
# wibble
`))

s, _ := RunServerWithConfig(conf)
defer s.Shutdown()

// This should work of course.
nc, err := nats.Connect(s.ClientURL())
require_NoError(t, err)
defer nc.Close()
}

0 comments on commit fa170aa

Please sign in to comment.