Fix query sanitizer
...when query text has contains Unicode replacement character. uft8.RuneError actually is a valid character.
This commit is contained in:
@@ -18,6 +18,12 @@ type Query struct {
|
|||||||
Parts []Part
|
Parts []Part
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// utf.DecodeRune returns the utf8.RuneError for errors. But that is actually rune U+FFFD -- the unicode replacement
|
||||||
|
// character. utf8.RuneError is not an error if it is also width 3.
|
||||||
|
//
|
||||||
|
// https://github.com/jackc/pgx/issues/1380
|
||||||
|
const replacementcharacterwidth = 3
|
||||||
|
|
||||||
func (q *Query) Sanitize(args ...any) (string, error) {
|
func (q *Query) Sanitize(args ...any) (string, error) {
|
||||||
argUse := make([]bool, len(args))
|
argUse := make([]bool, len(args))
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
@@ -138,6 +144,7 @@ func rawState(l *sqlLexer) stateFn {
|
|||||||
return multilineCommentState
|
return multilineCommentState
|
||||||
}
|
}
|
||||||
case utf8.RuneError:
|
case utf8.RuneError:
|
||||||
|
if width != replacementcharacterwidth {
|
||||||
if l.pos-l.start > 0 {
|
if l.pos-l.start > 0 {
|
||||||
l.parts = append(l.parts, l.src[l.start:l.pos])
|
l.parts = append(l.parts, l.src[l.start:l.pos])
|
||||||
l.start = l.pos
|
l.start = l.pos
|
||||||
@@ -145,6 +152,7 @@ func rawState(l *sqlLexer) stateFn {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func singleQuoteState(l *sqlLexer) stateFn {
|
func singleQuoteState(l *sqlLexer) stateFn {
|
||||||
@@ -160,6 +168,7 @@ func singleQuoteState(l *sqlLexer) stateFn {
|
|||||||
}
|
}
|
||||||
l.pos += width
|
l.pos += width
|
||||||
case utf8.RuneError:
|
case utf8.RuneError:
|
||||||
|
if width != replacementcharacterwidth {
|
||||||
if l.pos-l.start > 0 {
|
if l.pos-l.start > 0 {
|
||||||
l.parts = append(l.parts, l.src[l.start:l.pos])
|
l.parts = append(l.parts, l.src[l.start:l.pos])
|
||||||
l.start = l.pos
|
l.start = l.pos
|
||||||
@@ -167,6 +176,7 @@ func singleQuoteState(l *sqlLexer) stateFn {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doubleQuoteState(l *sqlLexer) stateFn {
|
func doubleQuoteState(l *sqlLexer) stateFn {
|
||||||
@@ -182,6 +192,7 @@ func doubleQuoteState(l *sqlLexer) stateFn {
|
|||||||
}
|
}
|
||||||
l.pos += width
|
l.pos += width
|
||||||
case utf8.RuneError:
|
case utf8.RuneError:
|
||||||
|
if width != replacementcharacterwidth {
|
||||||
if l.pos-l.start > 0 {
|
if l.pos-l.start > 0 {
|
||||||
l.parts = append(l.parts, l.src[l.start:l.pos])
|
l.parts = append(l.parts, l.src[l.start:l.pos])
|
||||||
l.start = l.pos
|
l.start = l.pos
|
||||||
@@ -189,6 +200,7 @@ func doubleQuoteState(l *sqlLexer) stateFn {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// placeholderState consumes a placeholder value. The $ must have already has
|
// placeholderState consumes a placeholder value. The $ must have already has
|
||||||
@@ -228,6 +240,7 @@ func escapeStringState(l *sqlLexer) stateFn {
|
|||||||
}
|
}
|
||||||
l.pos += width
|
l.pos += width
|
||||||
case utf8.RuneError:
|
case utf8.RuneError:
|
||||||
|
if width != replacementcharacterwidth {
|
||||||
if l.pos-l.start > 0 {
|
if l.pos-l.start > 0 {
|
||||||
l.parts = append(l.parts, l.src[l.start:l.pos])
|
l.parts = append(l.parts, l.src[l.start:l.pos])
|
||||||
l.start = l.pos
|
l.start = l.pos
|
||||||
@@ -235,6 +248,7 @@ func escapeStringState(l *sqlLexer) stateFn {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func oneLineCommentState(l *sqlLexer) stateFn {
|
func oneLineCommentState(l *sqlLexer) stateFn {
|
||||||
@@ -249,6 +263,7 @@ func oneLineCommentState(l *sqlLexer) stateFn {
|
|||||||
case '\n', '\r':
|
case '\n', '\r':
|
||||||
return rawState
|
return rawState
|
||||||
case utf8.RuneError:
|
case utf8.RuneError:
|
||||||
|
if width != replacementcharacterwidth {
|
||||||
if l.pos-l.start > 0 {
|
if l.pos-l.start > 0 {
|
||||||
l.parts = append(l.parts, l.src[l.start:l.pos])
|
l.parts = append(l.parts, l.src[l.start:l.pos])
|
||||||
l.start = l.pos
|
l.start = l.pos
|
||||||
@@ -256,6 +271,7 @@ func oneLineCommentState(l *sqlLexer) stateFn {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func multilineCommentState(l *sqlLexer) stateFn {
|
func multilineCommentState(l *sqlLexer) stateFn {
|
||||||
@@ -283,6 +299,7 @@ func multilineCommentState(l *sqlLexer) stateFn {
|
|||||||
l.nested--
|
l.nested--
|
||||||
|
|
||||||
case utf8.RuneError:
|
case utf8.RuneError:
|
||||||
|
if width != replacementcharacterwidth {
|
||||||
if l.pos-l.start > 0 {
|
if l.pos-l.start > 0 {
|
||||||
l.parts = append(l.parts, l.src[l.start:l.pos])
|
l.parts = append(l.parts, l.src[l.start:l.pos])
|
||||||
l.start = l.pos
|
l.start = l.pos
|
||||||
@@ -290,6 +307,7 @@ func multilineCommentState(l *sqlLexer) stateFn {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SanitizeSQL replaces placeholder values with args. It quotes and escapes args
|
// SanitizeSQL replaces placeholder values with args. It quotes and escapes args
|
||||||
|
|||||||
@@ -88,6 +88,16 @@ func TestNewQuery(t *testing.T) {
|
|||||||
sql: "select 42, -- \\nis a Deep Thought's favorite number\r$1",
|
sql: "select 42, -- \\nis a Deep Thought's favorite number\r$1",
|
||||||
expected: sanitize.Query{Parts: []sanitize.Part{"select 42, -- \\nis a Deep Thought's favorite number\r", 1}},
|
expected: sanitize.Query{Parts: []sanitize.Part{"select 42, -- \\nis a Deep Thought's favorite number\r", 1}},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// https://github.com/jackc/pgx/issues/1380
|
||||||
|
sql: "select 'hello w�rld'",
|
||||||
|
expected: sanitize.Query{Parts: []sanitize.Part{"select 'hello w�rld'"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Unterminated quoted string
|
||||||
|
sql: "select 'hello world",
|
||||||
|
expected: sanitize.Query{Parts: []sanitize.Part{"select 'hello world"}},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, tt := range successTests {
|
for i, tt := range successTests {
|
||||||
|
|||||||
Reference in New Issue
Block a user