@marcel91200 / caddy Mirror · read-only / commits / c12bf405
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 14, 05:15 UTC

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

Matthew Holt committed Sep 28, 2019 at 21:18 UTC c12bf4054c37b88eb37f7c59631d55ae512bcd29
2 files changed +66 -21
caddyconfig/caddyfile/lexer.go
+27 -18
@@ -26,9 +26,10 @@ type (
26 // are separated by whitespace. A word can be enclosed
27 // in quotes if it contains whitespace.
28 lexer struct {
29 - reader *bufio.Reader
30 - token Token
31 - line int
29 + reader *bufio.Reader
30 + token Token
31 + line int
32 + skippedLines int
33 }
34
35 // Token represents a single parsable unit.
@@ -91,27 +92,30 @@ func (l *lexer) next() bool {
92 panic(err)
93 }
94
95 + if !escaped && ch == '\\' {
96 + escaped = true
97 + continue
98 + }
99 +
100 if quoted {
95 - if !escaped {
96 - if ch == '\\' {
97 - escaped = true
98 - continue
99 - } else if ch == '"' {
101 + if escaped {
102 + // all is literal in quoted area,
103 + // so only escape quotes
104 + if ch != '"' {
105 + val = append(val, '\\')
106 + }
107 + escaped = false
108 + } else {
109 + if ch == '"' {
110 quoted = false
111 return makeToken()
112 }
113 }
114 if ch == '\n' {
105 - l.line++
106 - }
107 - if escaped {
108 - // only escape quotes and newlines
109 - if ch != '"' && ch != '\n' {
110 - val = append(val, '\\')
111 - }
115 + l.line += 1 + l.skippedLines
116 + l.skippedLines = 0
117 }
118 val = append(val, ch)
114 - escaped = false
119 continue
120 }
121
@@ -120,7 +124,13 @@ func (l *lexer) next() bool {
124 continue
125 }
126 if ch == '\n' {
123 - l.line++
127 + if escaped {
128 + l.skippedLines++
129 + escaped = false
130 + } else {
131 + l.line += 1 + l.skippedLines
132 + l.skippedLines = 0
133 + }
134 comment = false
135 }
136 if len(val) > 0 {
@@ -132,7 +142,6 @@ func (l *lexer) next() bool {
142 if ch == '#' {
143 comment = true
144 }
135 -
145 if comment {
146 continue
147 }
caddyconfig/caddyfile/lexer_test.go
+39 -3
@@ -96,13 +96,49 @@ func TestLexer(t *testing.T) {
96 },
97 },
98 {
99 - input: "A \"newline \\\ninside\" quotes",
99 + input: "An escaped \"newline\\\ninside\" quotes",
100 expected: []Token{
101 - {Line: 1, Text: "A"},
102 - {Line: 1, Text: "newline \ninside"},
101 + {Line: 1, Text: "An"},
102 + {Line: 1, Text: "escaped"},
103 + {Line: 1, Text: "newline\\\ninside"},
104 {Line: 2, Text: "quotes"},
105 },
106 },
107 + {
108 + input: "An escaped newline\\\noutside quotes",
109 + expected: []Token{
110 + {Line: 1, Text: "An"},
111 + {Line: 1, Text: "escaped"},
112 + {Line: 1, Text: "newline"},
113 + {Line: 1, Text: "outside"},
114 + {Line: 1, Text: "quotes"},
115 + },
116 + },
117 + {
118 + input: "line1\\\nescaped\nline2\nline3",
119 + expected: []Token{
120 + {Line: 1, Text: "line1"},
121 + {Line: 1, Text: "escaped"},
122 + {Line: 3, Text: "line2"},
123 + {Line: 4, Text: "line3"},
124 + },
125 + },
126 + {
127 + input: "line1\\\nescaped1\\\nescaped2\nline4\nline5",
128 + expected: []Token{
129 + {Line: 1, Text: "line1"},
130 + {Line: 1, Text: "escaped1"},
131 + {Line: 1, Text: "escaped2"},
132 + {Line: 4, Text: "line4"},
133 + {Line: 5, Text: "line5"},
134 + },
135 + },
136 + {
137 + input: `"unescapable\ in quotes"`,
138 + expected: []Token{
139 + {Line: 1, Text: `unescapable\ in quotes`},
140 + },
141 + },
142 {
143 input: `"don't\escape"`,
144 expected: []Token{