Skip to content

Commit

Permalink
Verify server response cap flags to fail earlier
Browse files Browse the repository at this point in the history
  • Loading branch information
tz70s committed Aug 30, 2020
1 parent 89bdedf commit b819a69
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 24 deletions.
2 changes: 1 addition & 1 deletion connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ func (mc *mysqlConn) readIgnoreColumns(rows *textRows, resLen int) (*textRows, e
rows.rs.columnNames = make([]string, resLen)
return rows, nil
}
return nil, ErrOptionalResultSet
return nil, ErrOptionalResultSetPkt
}

// Gets the value of the given MySQL System Variable
Expand Down
2 changes: 1 addition & 1 deletion connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (c *connector) Connect(ctx context.Context) (driver.Conn, error) {
mc.resultSetMetadata = resultSetMetadataFull
default:
mc.Close()
return nil, ErrOptionalResultSet
return nil, ErrOptionalResultSetPkt
}
}

Expand Down
17 changes: 11 additions & 6 deletions driver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ func (dbt *DBTest) mustQuery(query string, args ...interface{}) (rows *sql.Rows)
func maybeSkip(t *testing.T, err error, skipErrno uint16) {
mySQLErr, ok := err.(*MySQLError)
if !ok {
errLog.Print("non match")
return
}

Expand Down Expand Up @@ -1348,9 +1349,11 @@ func TestFoundRows(t *testing.T) {
func TestOptionalResultSetMetadata(t *testing.T) {
runTests(t, dsn+"&resultSetMetadata=none", func(dbt *DBTest) {
_, err := dbt.db.Exec("CREATE TABLE test (id INT NOT NULL ,data INT NOT NULL)")
// Error 1193: Unknown system variable 'resultset_metadata' => skip test,
// MySQL server version is too old
maybeSkip(t, err, 1193)
if err == ErrNoOptionalResultSet {
t.Skip("server does not support resultset metadata")
} else if err != nil {
dbt.Fatal(err)
}
dbt.mustExec("INSERT INTO test (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)")

row := dbt.db.QueryRow("SELECT id, data FROM test WHERE id = 1")
Expand All @@ -1366,9 +1369,11 @@ func TestOptionalResultSetMetadata(t *testing.T) {
})
runTests(t, dsn+"&resultSetMetadata=full", func(dbt *DBTest) {
_, err := dbt.db.Exec("CREATE TABLE test (id INT NOT NULL ,data INT NOT NULL)")
// Error 1193: Unknown system variable 'resultset_metadata' => skip test,
// MySQL server version is too old
maybeSkip(t, err, 1193)
if err == ErrNoOptionalResultSet {
t.Skip("server does not support resultset metadata")
} else if err != nil {
dbt.Fatal(err)
}
dbt.mustExec("INSERT INTO test (id, data) VALUES (0, 0),(0, 0),(1, 0),(1, 0),(1, 1)")

row := dbt.db.QueryRow("SELECT id, data FROM test WHERE id = 1")
Expand Down
27 changes: 14 additions & 13 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,20 @@ import (

// Various errors the driver might return. Can change between driver versions.
var (
ErrInvalidConn = errors.New("invalid connection")
ErrMalformPkt = errors.New("malformed packet")
ErrNoTLS = errors.New("TLS requested but server does not support TLS")
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
ErrNativePassword = errors.New("this user requires mysql native password authentication")
ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
ErrPktSync = errors.New("commands out of sync. You can't run this command now")
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
ErrBusyBuffer = errors.New("busy buffer")
ErrOptionalResultSet = errors.New("malformed optional resultset metadata packets")
ErrInvalidConn = errors.New("invalid connection")
ErrMalformPkt = errors.New("malformed packet")
ErrNoTLS = errors.New("TLS requested but server does not support TLS")
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN")
ErrNativePassword = errors.New("this user requires mysql native password authentication")
ErrOldPassword = errors.New("this user requires old password authentication. If you still want to use it, please add 'allowOldPasswords=1' to your DSN. See also https://github.com/go-sql-driver/mysql/wiki/old_passwords")
ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+")
ErrPktSync = errors.New("commands out of sync. You can't run this command now")
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
ErrBusyBuffer = errors.New("busy buffer")
ErrNoOptionalResultSet = errors.New("requested optional resultset metadata but server does not support")
ErrOptionalResultSetPkt = errors.New("malformed optional resultset metadata packets")

// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
// If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
Expand Down
14 changes: 11 additions & 3 deletions packets.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,18 @@ func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err erro
if len(data) > pos {
// character set [1 byte]
// status flags [2 bytes]
pos += 1 + 2
// capability flags (upper 2 bytes) [2 bytes]
upperFlags := clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
mc.flags |= upperFlags << 16
pos += 2
if mc.flags&clientOptionalResultSetMetadata == 0 && mc.cfg.ResultSetMetadata != "" {
return nil, "", ErrNoOptionalResultSet
}

// length of auth-plugin-data [1 byte]
// reserved (all [00]) [10 bytes]
pos += 1 + 2 + 2 + 1 + 10
pos += 1 + 10

// second part of the password cipher [mininum 13 bytes],
// where len=MAX(13, length of auth-plugin-data - 8)
Expand Down Expand Up @@ -562,10 +570,10 @@ func (mc *mysqlConn) readResultSetHeaderPacket() (int, error) {
// Sniff one extra byte for resultset metadata if we set capability
// CLIENT_OPTIONAL_RESULTSET_METADTA
// https://dev.mysql.com/worklog/task/?id=8134
if len(data) == 2 {
if len(data) == 2 && mc.flags&clientOptionalResultSetMetadata != 0 {
// ResultSet metadata flag check
if mc.resultSetMetadata != data[1] {
return 0, ErrOptionalResultSet
return 0, ErrOptionalResultSetPkt
}
return int(num), nil
}
Expand Down

0 comments on commit b819a69

Please sign in to comment.