diff --git a/AUTHORS b/AUTHORS index 6fc4c6f7b..d4b321788 100644 --- a/AUTHORS +++ b/AUTHORS @@ -20,6 +20,7 @@ Frederick Mayle Gustavo Kristic Hanno Braun Henri Yandell +Hirotaka Yamamoto INADA Naoki James Harr Jian Zhen diff --git a/connection.go b/connection.go index caaae013f..ecadd6ba3 100644 --- a/connection.go +++ b/connection.go @@ -253,12 +253,8 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin if v == nil { buf = append(buf, "NULL"...) } else { - buf = append(buf, '\'') - if mc.status&statusNoBackslashEscapes == 0 { - buf = escapeBytesBackslash(buf, v) - } else { - buf = escapeBytesQuotes(buf, v) - } + buf = append(buf, "X'"...) + buf = escapeBytes(buf, v) buf = append(buf, '\'') } case string: diff --git a/utils.go b/utils.go index 6a26ad129..3e997b342 100644 --- a/utils.go +++ b/utils.go @@ -836,49 +836,26 @@ func reserveBuffer(buf []byte, appendSize int) []byte { return buf[:newSize] } -// escapeBytesBackslash escapes []byte with backslashes (\) -// This escapes the contents of a string (provided as []byte) by adding backslashes before special -// characters, and turning others into specific escape sequences, such as -// turning newlines into \n and null bytes into \0. -// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L823-L932 -func escapeBytesBackslash(buf, v []byte) []byte { +// escapeBytes escapes []byte as hex-digits. +// Hexadecimal literals is the only way to safely populate binary data +// since MySQL 5.6.27 (which fixes Bug #20238729). +// https://dev.mysql.com/doc/refman/5.6/en/hexadecimal-literals.html +func escapeBytes(buf, v []byte) []byte { pos := len(buf) buf = reserveBuffer(buf, len(v)*2) for _, c := range v { - switch c { - case '\x00': - buf[pos] = '\\' - buf[pos+1] = '0' - pos += 2 - case '\n': - buf[pos] = '\\' - buf[pos+1] = 'n' - pos += 2 - case '\r': - buf[pos] = '\\' - buf[pos+1] = 'r' - pos += 2 - case '\x1a': - buf[pos] = '\\' - buf[pos+1] = 'Z' - pos += 2 - case '\'': - buf[pos] = '\\' - buf[pos+1] = '\'' - pos += 2 - case '"': - buf[pos] = '\\' - buf[pos+1] = '"' - pos += 2 - case '\\': - buf[pos] = '\\' - buf[pos+1] = '\\' - pos += 2 - default: - buf[pos] = c - pos += 1 + fh := (c >> 4) + '0' + if fh > '9' { + fh += 7 // 'A' - '0' - 10 } + lh := (c & 15) + '0' + if lh > '9' { + lh += 7 // 'A' - '0' - 10 + } + buf[pos] = fh + buf[pos+1] = lh + pos += 2 } return buf[:pos] @@ -929,29 +906,6 @@ func escapeStringBackslash(buf []byte, v string) []byte { return buf[:pos] } -// escapeBytesQuotes escapes apostrophes in []byte by doubling them up. -// This escapes the contents of a string by doubling up any apostrophes that -// it contains. This is used when the NO_BACKSLASH_ESCAPES SQL_MODE is in -// effect on the server. -// https://github.com/mysql/mysql-server/blob/mysql-5.7.5/mysys/charset.c#L963-L1038 -func escapeBytesQuotes(buf, v []byte) []byte { - pos := len(buf) - buf = reserveBuffer(buf, len(v)*2) - - for _, c := range v { - if c == '\'' { - buf[pos] = '\'' - buf[pos+1] = '\'' - pos += 2 - } else { - buf[pos] = c - pos++ - } - } - - return buf[:pos] -} - // escapeStringQuotes is similar to escapeBytesQuotes but for string. func escapeStringQuotes(buf []byte, v string) []byte { pos := len(buf) diff --git a/utils_test.go b/utils_test.go index 79fbdd1eb..e6caf14ad 100644 --- a/utils_test.go +++ b/utils_test.go @@ -290,17 +290,24 @@ func TestFormatBinaryDateTime(t *testing.T) { expect("1978-12-30 15:46:23.987654", 11, 26) } -func TestEscapeBackslash(t *testing.T) { - expect := func(expected, value string) { - actual := string(escapeBytesBackslash([]byte{}, []byte(value))) +func TestEscapeBytes(t *testing.T) { + expect := func(expected string, value []byte) { + actual := string(escapeBytes([]byte{}, value)) if actual != expected { t.Errorf( "expected %s, got %s", expected, actual, ) } + } - actual = string(escapeStringBackslash([]byte{}, value)) + expect("666F6F00626172", []byte{'f', 'o', 'o', '\x00', 'b', 'a', 'r'}) + expect("012BFDC5", []byte{'\x01', '\x2b', '\xfd', '\xc5'}) +} + +func TestEscapeBackslash(t *testing.T) { + expect := func(expected, value string) { + actual := string(escapeStringBackslash([]byte{}, value)) if actual != expected { t.Errorf( "expected %s, got %s", @@ -320,15 +327,7 @@ func TestEscapeBackslash(t *testing.T) { func TestEscapeQuotes(t *testing.T) { expect := func(expected, value string) { - actual := string(escapeBytesQuotes([]byte{}, []byte(value))) - if actual != expected { - t.Errorf( - "expected %s, got %s", - expected, actual, - ) - } - - actual = string(escapeStringQuotes([]byte{}, value)) + actual := string(escapeStringQuotes([]byte{}, value)) if actual != expected { t.Errorf( "expected %s, got %s",