Browse Source

caddyfile: Fix lexer behavior with regards to escaped newlines

Newlines (\n) can be escaped outside of quoted areas and the newline
will be treated as whitespace but not as an actual line break. Escaping
newlines inside a quoted area is not necessary, and because quotes
trigger literal interpretation of the contents, the escaping backslash
will be parsed as a literal backslash, and the newline will not be
escaped.

Caveat: When a newline is escaped, tokens after it until an unescaped
newline will appear to the parser be on the same line as the initial
token after the last unescaped newline. This may technically lead to
some false line numbers if errors are given, but escaped newlines are
counted so that the next token after an unescaped newline is correct.

See #2766
master
Matthew Holt 6 years ago
parent
commit
c12bf4054c
No known key found for this signature in database GPG Key ID: 2A349DD577D586A5
  1. 45
      caddyconfig/caddyfile/lexer.go
  2. 42
      caddyconfig/caddyfile/lexer_test.go

45
caddyconfig/caddyfile/lexer.go

@ -26,9 +26,10 @@ type (
// are separated by whitespace. A word can be enclosed // are separated by whitespace. A word can be enclosed
// in quotes if it contains whitespace. // in quotes if it contains whitespace.
lexer struct { lexer struct {
reader *bufio.Reader reader *bufio.Reader
token Token token Token
line int line int
skippedLines int
} }
// Token represents a single parsable unit. // Token represents a single parsable unit.
@ -91,27 +92,30 @@ func (l *lexer) next() bool {
panic(err) panic(err)
} }
if !escaped && ch == '\\' {
escaped = true
continue
}
if quoted { if quoted {
if !escaped { if escaped {
if ch == '\\' { // all is literal in quoted area,
escaped = true // so only escape quotes
continue if ch != '"' {
} else if ch == '"' { val = append(val, '\\')
}
escaped = false
} else {
if ch == '"' {
quoted = false quoted = false
return makeToken() return makeToken()
} }
} }
if ch == '\n' { if ch == '\n' {
l.line++ l.line += 1 + l.skippedLines
} l.skippedLines = 0
if escaped {
// only escape quotes and newlines
if ch != '"' && ch != '\n' {
val = append(val, '\\')
}
} }
val = append(val, ch) val = append(val, ch)
escaped = false
continue continue
} }
@ -120,7 +124,13 @@ func (l *lexer) next() bool {
continue continue
} }
if ch == '\n' { if ch == '\n' {
l.line++ if escaped {
l.skippedLines++
escaped = false
} else {
l.line += 1 + l.skippedLines
l.skippedLines = 0
}
comment = false comment = false
} }
if len(val) > 0 { if len(val) > 0 {
@ -132,7 +142,6 @@ func (l *lexer) next() bool {
if ch == '#' { if ch == '#' {
comment = true comment = true
} }
if comment { if comment {
continue continue
} }

42
caddyconfig/caddyfile/lexer_test.go

@ -96,13 +96,49 @@ func TestLexer(t *testing.T) {
}, },
}, },
{ {
input: "A \"newline \\\ninside\" quotes", input: "An escaped \"newline\\\ninside\" quotes",
expected: []Token{ expected: []Token{
{Line: 1, Text: "A"}, {Line: 1, Text: "An"},
{Line: 1, Text: "newline \ninside"}, {Line: 1, Text: "escaped"},
{Line: 1, Text: "newline\\\ninside"},
{Line: 2, Text: "quotes"}, {Line: 2, Text: "quotes"},
}, },
}, },
{
input: "An escaped newline\\\noutside quotes",
expected: []Token{
{Line: 1, Text: "An"},
{Line: 1, Text: "escaped"},
{Line: 1, Text: "newline"},
{Line: 1, Text: "outside"},
{Line: 1, Text: "quotes"},
},
},
{
input: "line1\\\nescaped\nline2\nline3",
expected: []Token{
{Line: 1, Text: "line1"},
{Line: 1, Text: "escaped"},
{Line: 3, Text: "line2"},
{Line: 4, Text: "line3"},
},
},
{
input: "line1\\\nescaped1\\\nescaped2\nline4\nline5",
expected: []Token{
{Line: 1, Text: "line1"},
{Line: 1, Text: "escaped1"},
{Line: 1, Text: "escaped2"},
{Line: 4, Text: "line4"},
{Line: 5, Text: "line5"},
},
},
{
input: `"unescapable\ in quotes"`,
expected: []Token{
{Line: 1, Text: `unescapable\ in quotes`},
},
},
{ {
input: `"don't\escape"`, input: `"don't\escape"`,
expected: []Token{ expected: []Token{

Loading…
Cancel
Save