1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 05:09:42 +02:00

updated mysql driver and fixed pinned card scrolling

This commit is contained in:
Harvey Kandola 2016-12-14 16:54:12 +00:00
parent 5cd4497467
commit b6f74069b5
20 changed files with 1308 additions and 739 deletions

View file

@ -8,13 +8,13 @@ The mission is to bring software dev inspired features (refactoring, testing, li
## Latest version ## Latest version
v0.37.0 v0.38.0
## OS Support ## OS Support
- Windows - Windows
- Linux - Linux
- OSX - macOS
## Tech stack ## Tech stack

View file

@ -9,7 +9,7 @@
z-index: 999; z-index: 999;
overflow-x: hidden; overflow-x: hidden;
> .bottom-zone, > .bottom-zone,
> .top-zone { > .top-zone {
position: absolute; position: absolute;
padding: 0; padding: 0;
@ -104,8 +104,9 @@
top: 220px; top: 220px;
padding: 0; padding: 0;
margin: 0; margin: 0;
width: 100%; width: 80px;
overflow: scroll; overflow-x: hidden;
overflow-y: scroll;
> .pin { > .pin {
cursor: pointer; cursor: pointer;

View file

@ -1,6 +1,6 @@
{ {
"name": "documize", "name": "documize",
"version": "0.37.0", "version": "0.38.0",
"description": "The Document IDE", "description": "The Document IDE",
"private": true, "private": true,
"repository": "", "repository": "",

View file

@ -26,7 +26,7 @@ type ProdInfo struct {
// Product returns product edition details // Product returns product edition details
func Product() (p ProdInfo) { func Product() (p ProdInfo) {
p.Major = "0" p.Major = "0"
p.Minor = "37" p.Minor = "38"
p.Patch = "0" p.Patch = "0"
p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch) p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch)
p.Edition = "Community" p.Edition = "Community"

File diff suppressed because one or more lines are too long

View file

@ -31,19 +31,23 @@ Julien Lefevre <julien.lefevr at gmail.com>
Julien Schmidt <go-sql-driver at julienschmidt.com> Julien Schmidt <go-sql-driver at julienschmidt.com>
Kamil Dziedzic <kamil at klecza.pl> Kamil Dziedzic <kamil at klecza.pl>
Kevin Malachowski <kevin at chowski.com> Kevin Malachowski <kevin at chowski.com>
Lennart Rudolph <lrudolph at hmc.edu>
Leonardo YongUk Kim <dalinaum at gmail.com> Leonardo YongUk Kim <dalinaum at gmail.com>
Luca Looz <luca.looz92 at gmail.com> Luca Looz <luca.looz92 at gmail.com>
Lucas Liu <extrafliu at gmail.com> Lucas Liu <extrafliu at gmail.com>
Luke Scott <luke at webconnex.com> Luke Scott <luke at webconnex.com>
Michael Woolnough <michael.woolnough at gmail.com> Michael Woolnough <michael.woolnough at gmail.com>
Nicola Peduzzi <thenikso at gmail.com> Nicola Peduzzi <thenikso at gmail.com>
Olivier Mengué <dolmen at cpan.org>
Paul Bonser <misterpib at gmail.com> Paul Bonser <misterpib at gmail.com>
Runrioter Wung <runrioter at gmail.com> Runrioter Wung <runrioter at gmail.com>
Soroush Pour <me at soroushjp.com> Soroush Pour <me at soroushjp.com>
Stan Putrya <root.vagner at gmail.com> Stan Putrya <root.vagner at gmail.com>
Stanley Gunawan <gunawan.stanley at gmail.com> Stanley Gunawan <gunawan.stanley at gmail.com>
Xiangyu Hu <xiangyu.hu at outlook.com>
Xiaobing Jiang <s7v7nislands at gmail.com> Xiaobing Jiang <s7v7nislands at gmail.com>
Xiuming Chen <cc at cxm.cc> Xiuming Chen <cc at cxm.cc>
Zhenye Xie <xiezhenye at gmail.com>
# Organizations # Organizations

View file

@ -1,18 +1,35 @@
## HEAD ## Version 1.3 (2016-12-01)
Changes: Changes:
- Go 1.1 is no longer supported - Go 1.1 is no longer supported
- Use decimals field from MySQL to format time types (#249) - Use decimals fields in MySQL to format time types (#249)
- Buffer optimizations (#269) - Buffer optimizations (#269)
- TLS ServerName defaults to the host (#283) - TLS ServerName defaults to the host (#283)
- Refactoring (#400, #410, #437)
- Adjusted documentation for second generation CloudSQL (#485)
- Documented DSN system var quoting rules (#502)
- Made statement.Close() calls idempotent to avoid errors in Go 1.6+ (#512)
New Features:
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
- Support for returning table alias on Columns() (#289, #359, #382)
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318, #490)
- Support for uint64 parameters with high bit set (#332, #345)
- Cleartext authentication plugin support (#327)
- Exported ParseDSN function and the Config struct (#403, #419, #429)
- Read / Write timeouts (#401)
- Support for JSON field type (#414)
- Support for multi-statements and multi-results (#411, #431)
- DSN parameter to set the driver-side max_allowed_packet value manually (#489)
- Native password authentication plugin support (#494, #524)
Bugfixes: Bugfixes:
- Enable microsecond resolution on TIME, DATETIME and TIMESTAMP (#249)
- Fixed handling of queries without columns and rows (#255) - Fixed handling of queries without columns and rows (#255)
- Fixed a panic when SetKeepAlive() failed (#298) - Fixed a panic when SetKeepAlive() failed (#298)
- Support receiving ERR packet while reading rows (#321) - Handle ERR packets while reading rows (#321)
- Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349) - Fixed reading NULL length-encoded integers in MySQL 5.6+ (#349)
- Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356) - Fixed absolute paths support in LOAD LOCAL DATA INFILE (#356)
- Actually zero out bytes in handshake response (#378) - Actually zero out bytes in handshake response (#378)
@ -20,13 +37,12 @@ Bugfixes:
- Fixed tests with MySQL 5.7.9+ (#380) - Fixed tests with MySQL 5.7.9+ (#380)
- QueryUnescape TLS config names (#397) - QueryUnescape TLS config names (#397)
- Fixed "broken pipe" error by writing to closed socket (#390) - Fixed "broken pipe" error by writing to closed socket (#390)
- Fixed LOAD LOCAL DATA INFILE buffering (#424)
New Features: - Fixed parsing of floats into float64 when placeholders are used (#434)
- Support for returning table alias on Columns() (#289, #359, #382) - Fixed DSN tests with Go 1.7+ (#459)
- Placeholder interpolation, can be actived with the DSN parameter `interpolateParams=true` (#309, #318) - Handle ERR packets while waiting for EOF (#473)
- Support for uint64 parameters with high bit set (#332, #345) - Invalidate connection on error while discarding additional results (#513)
- Cleartext authentication plugin support (#327) - Allow terminating packets of length 0 (#516)
## Version 1.2 (2014-06-03) ## Version 1.2 (2014-06-03)

View file

@ -4,10 +4,6 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin") ![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
**Latest stable Release:** [Version 1.2 (June 03, 2014)](https://github.com/go-sql-driver/mysql/releases)
[![Build Status](https://travis-ci.org/go-sql-driver/mysql.png?branch=master)](https://travis-ci.org/go-sql-driver/mysql)
--------------------------------------- ---------------------------------------
* [Features](#features) * [Features](#features)
* [Requirements](#requirements) * [Requirements](#requirements)
@ -135,6 +131,15 @@ Default: false
`allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network. `allowCleartextPasswords=true` allows using the [cleartext client side plugin](http://dev.mysql.com/doc/en/cleartext-authentication-plugin.html) if required by an account, such as one defined with the [PAM authentication plugin](http://dev.mysql.com/doc/en/pam-authentication-plugin.html). Sending passwords in clear text may be a security problem in some configurations. To avoid problems if there is any possibility that the password would be intercepted, clients should connect to MySQL Server using a method that protects the password. Possibilities include [TLS / SSL](#tls), IPsec, or a private network.
##### `allowNativePasswords`
```
Type: bool
Valid Values: true, false
Default: false
```
`allowNativePasswords=true` allows the usage of the mysql native password method.
##### `allowOldPasswords` ##### `allowOldPasswords`
``` ```
@ -221,6 +226,14 @@ Note that this sets the location for time.Time values but does not change MySQL'
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`. Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
##### `maxAllowedPacket`
```
Type: decimal number
Default: 0
```
Max packet size allowed in bytes. Use `maxAllowedPacket=0` to automatically fetch the `max_allowed_packet` variable from server.
##### `multiStatements` ##### `multiStatements`
``` ```
@ -233,7 +246,6 @@ Allow multiple statements in one query. While this allows batch queries, it also
When `multiStatements` is used, `?` parameters must only be used in the first statement. When `multiStatements` is used, `?` parameters must only be used in the first statement.
##### `parseTime` ##### `parseTime`
``` ```
@ -254,7 +266,6 @@ Default: 0
I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
##### `strict` ##### `strict`
``` ```
@ -263,10 +274,11 @@ Valid Values: true, false
Default: false Default: false
``` ```
`strict=true` enables the strict mode in which MySQL warnings are treated as errors. `strict=true` enables a driver-side strict mode in which MySQL warnings are treated as errors. This mode should not be used in production as it may lead to data corruption in certain situations.
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes. See the [examples](#examples) for an DSN example. A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable.
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes.
##### `timeout` ##### `timeout`
@ -277,7 +289,6 @@ Default: OS default
*Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout). *Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
##### `tls` ##### `tls`
``` ```
@ -288,7 +299,6 @@ Default: false
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig). `tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
##### `writeTimeout` ##### `writeTimeout`
``` ```
@ -301,13 +311,21 @@ I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms
##### System Variables ##### System Variables
All other parameters are interpreted as system variables: Any other parameters are interpreted as system variables:
* `autocommit`: `"SET autocommit=<value>"` * `<boolean_var>=<value>`: `SET <boolean_var>=<value>`
* [`time_zone`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `"SET time_zone=<value>"` * `<enum_var>=<value>`: `SET <enum_var>=<value>`
* [`tx_isolation`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `"SET tx_isolation=<value>"` * `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
* `param`: `"SET <param>=<value>"`
Rules:
* The values for string variables must be quoted with '
* The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
(which implies values of string variables must be wrapped with `%27`)
Examples:
* `autocommit=1`: `SET autocommit=1`
* [`time_zone=%27Europe%2FParis%27`](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html): `SET time_zone='Europe/Paris'`
* [`tx_isolation=%27REPEATABLE-READ%27`](https://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_tx_isolation): `SET tx_isolation='REPEATABLE-READ'`
*The values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!*
#### Examples #### Examples
``` ```
@ -322,9 +340,9 @@ root:pw@unix(/tmp/mysql.sock)/myDatabase?loc=Local
user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true user:password@tcp(localhost:5555)/dbname?tls=skip-verify&autocommit=true
``` ```
Use the [strict mode](#strict) but ignore notes: Treat warnings as errors by setting the system variable [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html):
``` ```
user:password@/dbname?strict=true&sql_notes=false user:password@/dbname?sql_mode=TRADITIONAL
``` ```
TCP via IPv6: TCP via IPv6:
@ -337,11 +355,16 @@ TCP on a remote host, e.g. Amazon RDS:
id:password@tcp(your-amazonaws-uri.com:3306)/dbname id:password@tcp(your-amazonaws-uri.com:3306)/dbname
``` ```
Google Cloud SQL on App Engine: Google Cloud SQL on App Engine (First Generation MySQL Server):
``` ```
user@cloudsql(project-id:instance-name)/dbname user@cloudsql(project-id:instance-name)/dbname
``` ```
Google Cloud SQL on App Engine (Second Generation MySQL Server):
```
user@cloudsql(project-id:regionname:instance-name)/dbname
```
TCP using default port (3306) on localhost: TCP using default port (3306) on localhost:
``` ```
user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped user:password@tcp/dbname?charset=utf8mb4,utf8&sys_var=esc%40ped

View file

@ -220,7 +220,7 @@ func BenchmarkInterpolation(b *testing.B) {
InterpolateParams: true, InterpolateParams: true,
Loc: time.UTC, Loc: time.UTC,
}, },
maxPacketAllowed: maxPacketSize, maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1, maxWriteSize: maxPacketSize - 1,
buf: newBuffer(nil), buf: newBuffer(nil),
} }

View file

@ -22,7 +22,7 @@ type mysqlConn struct {
affectedRows uint64 affectedRows uint64
insertId uint64 insertId uint64
cfg *Config cfg *Config
maxPacketAllowed int maxAllowedPacket int
maxWriteSize int maxWriteSize int
writeTimeout time.Duration writeTimeout time.Duration
flags clientFlag flags clientFlag
@ -135,6 +135,11 @@ func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
} }
func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) { func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (string, error) {
// Number of ? should be same to len(args)
if strings.Count(query, "?") != len(args) {
return "", driver.ErrSkip
}
buf := mc.buf.takeCompleteBuffer() buf := mc.buf.takeCompleteBuffer()
if buf == nil { if buf == nil {
// can not take the buffer. Something must be wrong with the connection // can not take the buffer. Something must be wrong with the connection
@ -241,7 +246,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
return "", driver.ErrSkip return "", driver.ErrSkip
} }
if len(buf)+4 > mc.maxPacketAllowed { if len(buf)+4 > mc.maxAllowedPacket {
return "", driver.ErrSkip return "", driver.ErrSkip
} }
} }

View file

@ -0,0 +1,67 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql/driver"
"testing"
)
func TestInterpolateParams(t *testing.T) {
mc := &mysqlConn{
buf: newBuffer(nil),
maxAllowedPacket: maxPacketSize,
cfg: &Config{
InterpolateParams: true,
},
}
q, err := mc.interpolateParams("SELECT ?+?", []driver.Value{int64(42), "gopher"})
if err != nil {
t.Errorf("Expected err=nil, got %#v", err)
return
}
expected := `SELECT 42+'gopher'`
if q != expected {
t.Errorf("Expected: %q\nGot: %q", expected, q)
}
}
func TestInterpolateParamsTooManyPlaceholders(t *testing.T) {
mc := &mysqlConn{
buf: newBuffer(nil),
maxAllowedPacket: maxPacketSize,
cfg: &Config{
InterpolateParams: true,
},
}
q, err := mc.interpolateParams("SELECT ?+?", []driver.Value{int64(42)})
if err != driver.ErrSkip {
t.Errorf("Expected err=driver.ErrSkip, got err=%#v, q=%#v", err, q)
}
}
// We don't support placeholder in string literal for now.
// https://github.com/go-sql-driver/mysql/pull/490
func TestInterpolateParamsPlaceholderInString(t *testing.T) {
mc := &mysqlConn{
buf: newBuffer(nil),
maxAllowedPacket: maxPacketSize,
cfg: &Config{
InterpolateParams: true,
},
}
q, err := mc.interpolateParams("SELECT 'abc?xyz',?", []driver.Value{int64(42)})
// When InterpolateParams support string literal, this should return `"SELECT 'abc?xyz', 42`
if err != driver.ErrSkip {
t.Errorf("Expected err=driver.ErrSkip, got err=%#v, q=%#v", err, q)
}
}

View file

@ -50,7 +50,7 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
// New mysqlConn // New mysqlConn
mc := &mysqlConn{ mc := &mysqlConn{
maxPacketAllowed: maxPacketSize, maxAllowedPacket: maxPacketSize,
maxWriteSize: maxPacketSize - 1, maxWriteSize: maxPacketSize - 1,
} }
mc.cfg, err = ParseDSN(dsn) mc.cfg, err = ParseDSN(dsn)
@ -109,15 +109,19 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
return nil, err return nil, err
} }
// Get max allowed packet size if mc.cfg.MaxAllowedPacket > 0 {
maxap, err := mc.getSystemVar("max_allowed_packet") mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket
if err != nil { } else {
mc.Close() // Get max allowed packet size
return nil, err maxap, err := mc.getSystemVar("max_allowed_packet")
if err != nil {
mc.Close()
return nil, err
}
mc.maxAllowedPacket = stringToInt(maxap) - 1
} }
mc.maxPacketAllowed = stringToInt(maxap) - 1 if mc.maxAllowedPacket < maxPacketSize {
if mc.maxPacketAllowed < maxPacketSize { mc.maxWriteSize = mc.maxAllowedPacket
mc.maxWriteSize = mc.maxPacketAllowed
} }
// Handle DSN Params // Handle DSN Params
@ -130,9 +134,9 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
return mc, nil return mc, nil
} }
func handleAuthResult(mc *mysqlConn, cipher []byte) error { func handleAuthResult(mc *mysqlConn, oldCipher []byte) error {
// Read Result Packet // Read Result Packet
err := mc.readResultOK() cipher, err := mc.readResultOK()
if err == nil { if err == nil {
return nil // auth successful return nil // auth successful
} }
@ -146,10 +150,17 @@ func handleAuthResult(mc *mysqlConn, cipher []byte) error {
// Retry with old authentication method. Note: there are edge cases // Retry with old authentication method. Note: there are edge cases
// where this should work but doesn't; this is currently "wontfix": // where this should work but doesn't; this is currently "wontfix":
// https://github.com/go-sql-driver/mysql/issues/184 // https://github.com/go-sql-driver/mysql/issues/184
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
// sent and we have to keep using the cipher sent in the init packet.
if cipher == nil {
cipher = oldCipher
}
if err = mc.writeOldAuthPacket(cipher); err != nil { if err = mc.writeOldAuthPacket(cipher); err != nil {
return err return err
} }
err = mc.readResultOK() _, err = mc.readResultOK()
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword { } else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
// Retry with clear text password for // Retry with clear text password for
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html // http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
@ -157,7 +168,12 @@ func handleAuthResult(mc *mysqlConn, cipher []byte) error {
if err = mc.writeClearAuthPacket(); err != nil { if err = mc.writeClearAuthPacket(); err != nil {
return err return err
} }
err = mc.readResultOK() _, err = mc.readResultOK()
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
if err = mc.writeNativeAuthPacket(cipher); err != nil {
return err
}
_, err = mc.readResultOK()
} }
return err return err
} }

View file

@ -1855,3 +1855,50 @@ func TestUnixSocketAuthFail(t *testing.T) {
} }
}) })
} }
// See Issue #422
func TestInterruptBySignal(t *testing.T) {
runTestsWithMultiStatement(t, dsn, func(dbt *DBTest) {
dbt.mustExec(`
DROP PROCEDURE IF EXISTS test_signal;
CREATE PROCEDURE test_signal(ret INT)
BEGIN
SELECT ret;
SIGNAL SQLSTATE
'45001'
SET
MESSAGE_TEXT = "an error",
MYSQL_ERRNO = 45001;
END
`)
defer dbt.mustExec("DROP PROCEDURE test_signal")
var val int
// text protocol
rows, err := dbt.db.Query("CALL test_signal(42)")
if err != nil {
dbt.Fatalf("error on text query: %s", err.Error())
}
for rows.Next() {
if err := rows.Scan(&val); err != nil {
dbt.Error(err)
} else if val != 42 {
dbt.Errorf("expected val to be 42")
}
}
// binary protocol
rows, err = dbt.db.Query("CALL test_signal(?)", 42)
if err != nil {
dbt.Fatalf("error on binary query: %s", err.Error())
}
for rows.Next() {
if err := rows.Scan(&val); err != nil {
dbt.Error(err)
} else if val != 42 {
dbt.Errorf("expected val to be 42")
}
}
})
}

View file

@ -15,6 +15,7 @@ import (
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
"strconv"
"strings" "strings"
"time" "time"
) )
@ -28,22 +29,24 @@ var (
// Config is a configuration parsed from a DSN string // Config is a configuration parsed from a DSN string
type Config struct { type Config struct {
User string // Username User string // Username
Passwd string // Password (requires User) Passwd string // Password (requires User)
Net string // Network type Net string // Network type
Addr string // Network address (requires Net) Addr string // Network address (requires Net)
DBName string // Database name DBName string // Database name
Params map[string]string // Connection parameters Params map[string]string // Connection parameters
Collation string // Connection collation Collation string // Connection collation
Loc *time.Location // Location for time.Time values Loc *time.Location // Location for time.Time values
TLSConfig string // TLS configuration name MaxAllowedPacket int // Max packet size allowed
tls *tls.Config // TLS configuration TLSConfig string // TLS configuration name
Timeout time.Duration // Dial timeout tls *tls.Config // TLS configuration
ReadTimeout time.Duration // I/O read timeout Timeout time.Duration // Dial timeout
WriteTimeout time.Duration // I/O write timeout ReadTimeout time.Duration // I/O read timeout
WriteTimeout time.Duration // I/O write timeout
AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE AllowAllFiles bool // Allow all files to be used with LOAD DATA LOCAL INFILE
AllowCleartextPasswords bool // Allows the cleartext client side plugin AllowCleartextPasswords bool // Allows the cleartext client side plugin
AllowNativePasswords bool // Allows the native password authentication method
AllowOldPasswords bool // Allows the old insecure password method AllowOldPasswords bool // Allows the old insecure password method
ClientFoundRows bool // Return number of matching rows instead of rows changed ClientFoundRows bool // Return number of matching rows instead of rows changed
ColumnsWithAlias bool // Prepend table alias to column names ColumnsWithAlias bool // Prepend table alias to column names
@ -99,6 +102,15 @@ func (cfg *Config) FormatDSN() string {
} }
} }
if cfg.AllowNativePasswords {
if hasParam {
buf.WriteString("&allowNativePasswords=true")
} else {
hasParam = true
buf.WriteString("?allowNativePasswords=true")
}
}
if cfg.AllowOldPasswords { if cfg.AllowOldPasswords {
if hasParam { if hasParam {
buf.WriteString("&allowOldPasswords=true") buf.WriteString("&allowOldPasswords=true")
@ -222,6 +234,17 @@ func (cfg *Config) FormatDSN() string {
buf.WriteString(cfg.WriteTimeout.String()) buf.WriteString(cfg.WriteTimeout.String())
} }
if cfg.MaxAllowedPacket > 0 {
if hasParam {
buf.WriteString("&maxAllowedPacket=")
} else {
hasParam = true
buf.WriteString("?maxAllowedPacket=")
}
buf.WriteString(strconv.Itoa(cfg.MaxAllowedPacket))
}
// other params // other params
if cfg.Params != nil { if cfg.Params != nil {
for param, value := range cfg.Params { for param, value := range cfg.Params {
@ -368,6 +391,14 @@ func parseDSNParams(cfg *Config, params string) (err error) {
return errors.New("invalid bool value: " + value) return errors.New("invalid bool value: " + value)
} }
// Use native password authentication
case "allowNativePasswords":
var isBool bool
cfg.AllowNativePasswords, isBool = readBool(value)
if !isBool {
return errors.New("invalid bool value: " + value)
}
// Use old authentication mode (pre MySQL 4.1) // Use old authentication mode (pre MySQL 4.1)
case "allowOldPasswords": case "allowOldPasswords":
var isBool bool var isBool bool
@ -496,7 +527,11 @@ func parseDSNParams(cfg *Config, params string) (err error) {
if err != nil { if err != nil {
return return
} }
case "maxAllowedPacket":
cfg.MaxAllowedPacket, err = strconv.Atoi(value)
if err != nil {
return
}
default: default:
// lazy init // lazy init
if cfg.Params == nil { if cfg.Params == nil {

View file

@ -12,36 +12,61 @@ import (
"crypto/tls" "crypto/tls"
"fmt" "fmt"
"net/url" "net/url"
"reflect"
"testing" "testing"
"time"
) )
var testDSNs = []struct { var testDSNs = []struct {
in string in string
out string out *Config
}{ }{{
{"username:password@protocol(address)/dbname?param=value", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, "username:password@protocol(address)/dbname?param=value",
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC},
{"username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true", "&{User:username Passwd:password Net:protocol Addr:address DBName:dbname Params:map[param:value] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:true InterpolateParams:false MultiStatements:true ParseTime:false Strict:false}"}, }, {
{"user@unix(/path/to/socket)/dbname?charset=utf8", "&{User:user Passwd: Net:unix Addr:/path/to/socket DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true",
{"user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:true tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, ColumnsWithAlias: true},
{"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify", "&{User:user Passwd:password Net:tcp Addr:localhost:5555 DBName:dbname Params:map[charset:utf8mb4,utf8] Collation:utf8_general_ci Loc:UTC TLSConfig:skip-verify tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, }, {
{"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci", "&{User:user Passwd:password Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8mb4_unicode_ci Loc:UTC TLSConfig: tls:<nil> Timeout:30s ReadTimeout:1s WriteTimeout:1s AllowAllFiles:true AllowCleartextPasswords:false AllowOldPasswords:true ClientFoundRows:true ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, "username:password@protocol(address)/dbname?param=value&columnsWithAlias=true&multiStatements=true",
{"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local", "&{User:user Passwd:p@ss(word) Net:tcp Addr:[de:ad:be:ef::ca:fe]:80 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:Local TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, &Config{User: "username", Passwd: "password", Net: "protocol", Addr: "address", DBName: "dbname", Params: map[string]string{"param": "value"}, Collation: "utf8_general_ci", Loc: time.UTC, ColumnsWithAlias: true, MultiStatements: true},
{"/dbname", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName:dbname Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, }, {
{"@/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, "user@unix(/path/to/socket)/dbname?charset=utf8",
{"/", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, &Config{User: "user", Net: "unix", Addr: "/path/to/socket", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC},
{"", "&{User: Passwd: Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, }, {
{"user:p@/ssword@/", "&{User:user Passwd:p@/ssword Net:tcp Addr:127.0.0.1:3306 DBName: Params:map[] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, "user:password@tcp(localhost:5555)/dbname?charset=utf8&tls=true",
{"unix/?arg=%2Fsome%2Fpath.ext", "&{User: Passwd: Net:unix Addr:/tmp/mysql.sock DBName: Params:map[arg:/some/path.ext] Collation:utf8_general_ci Loc:UTC TLSConfig: tls:<nil> Timeout:0 ReadTimeout:0 WriteTimeout:0 AllowAllFiles:false AllowCleartextPasswords:false AllowOldPasswords:false ClientFoundRows:false ColumnsWithAlias:false InterpolateParams:false MultiStatements:false ParseTime:false Strict:false}"}, &Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, TLSConfig: "true"},
} }, {
"user:password@tcp(localhost:5555)/dbname?charset=utf8mb4,utf8&tls=skip-verify",
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "localhost:5555", DBName: "dbname", Params: map[string]string{"charset": "utf8mb4,utf8"}, Collation: "utf8_general_ci", Loc: time.UTC, TLSConfig: "skip-verify"},
}, {
"user:password@/dbname?loc=UTC&timeout=30s&readTimeout=1s&writeTimeout=1s&allowAllFiles=1&clientFoundRows=true&allowOldPasswords=TRUE&collation=utf8mb4_unicode_ci&maxAllowedPacket=16777216",
&Config{User: "user", Passwd: "password", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8mb4_unicode_ci", Loc: time.UTC, Timeout: 30 * time.Second, ReadTimeout: time.Second, WriteTimeout: time.Second, AllowAllFiles: true, AllowOldPasswords: true, ClientFoundRows: true, MaxAllowedPacket: 16777216},
}, {
"user:p@ss(word)@tcp([de:ad:be:ef::ca:fe]:80)/dbname?loc=Local",
&Config{User: "user", Passwd: "p@ss(word)", Net: "tcp", Addr: "[de:ad:be:ef::ca:fe]:80", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.Local},
}, {
"/dbname",
&Config{Net: "tcp", Addr: "127.0.0.1:3306", DBName: "dbname", Collation: "utf8_general_ci", Loc: time.UTC},
}, {
"@/",
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
}, {
"/",
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
}, {
"",
&Config{Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
}, {
"user:p@/ssword@/",
&Config{User: "user", Passwd: "p@/ssword", Net: "tcp", Addr: "127.0.0.1:3306", Collation: "utf8_general_ci", Loc: time.UTC},
}, {
"unix/?arg=%2Fsome%2Fpath.ext",
&Config{Net: "unix", Addr: "/tmp/mysql.sock", Params: map[string]string{"arg": "/some/path.ext"}, Collation: "utf8_general_ci", Loc: time.UTC},
}}
func TestDSNParser(t *testing.T) { func TestDSNParser(t *testing.T) {
var cfg *Config
var err error
var res string
for i, tst := range testDSNs { for i, tst := range testDSNs {
cfg, err = ParseDSN(tst.in) cfg, err := ParseDSN(tst.in)
if err != nil { if err != nil {
t.Error(err.Error()) t.Error(err.Error())
} }
@ -49,9 +74,8 @@ func TestDSNParser(t *testing.T) {
// pointer not static // pointer not static
cfg.tls = nil cfg.tls = nil
res = fmt.Sprintf("%+v", cfg) if !reflect.DeepEqual(cfg, tst.out) {
if res != tst.out { t.Errorf("%d. ParseDSN(%q) mismatch:\ngot %+v\nwant %+v", i, tst.in, cfg, tst.out)
t.Errorf("%d. ParseDSN(%q) => %q, want %q", i, tst.in, res, tst.out)
} }
} }
} }

View file

@ -22,8 +22,9 @@ var (
ErrInvalidConn = errors.New("invalid connection") ErrInvalidConn = errors.New("invalid connection")
ErrMalformPkt = errors.New("malformed packet") ErrMalformPkt = errors.New("malformed packet")
ErrNoTLS = errors.New("TLS requested but server does not support TLS") ErrNoTLS = errors.New("TLS requested but server does not support TLS")
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")
ErrCleartextPassword = errors.New("this user requires clear text authentication. If you still want to use it, please add 'allowCleartextPasswords=1' to your DSN") 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") ErrUnknownPlugin = errors.New("this authentication plugin is not supported")
ErrOldProtocol = errors.New("MySQL server does not support required protocol 41+") 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") ErrPktSync = errors.New("commands out of sync. You can't run this command now")

View file

@ -173,7 +173,8 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
// read OK packet // read OK packet
if err == nil { if err == nil {
return mc.readResultOK() _, err = mc.readResultOK()
return err
} }
mc.readPacket() mc.readPacket()

View file

@ -25,9 +25,9 @@ import (
// Read packet to buffer 'data' // Read packet to buffer 'data'
func (mc *mysqlConn) readPacket() ([]byte, error) { func (mc *mysqlConn) readPacket() ([]byte, error) {
var payload []byte var prevData []byte
for { for {
// Read packet header // read packet header
data, err := mc.buf.readNext(4) data, err := mc.buf.readNext(4)
if err != nil { if err != nil {
errLog.Print(err) errLog.Print(err)
@ -35,16 +35,10 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
// Packet Length [24 bit] // packet length [24 bit]
pktLen := int(uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16) pktLen := int(uint32(data[0]) | uint32(data[1])<<8 | uint32(data[2])<<16)
if pktLen < 1 { // check packet sync [8 bit]
errLog.Print(ErrMalformPkt)
mc.Close()
return nil, driver.ErrBadConn
}
// Check Packet Sync [8 bit]
if data[3] != mc.sequence { if data[3] != mc.sequence {
if data[3] > mc.sequence { if data[3] > mc.sequence {
return nil, ErrPktSyncMul return nil, ErrPktSyncMul
@ -53,7 +47,20 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
} }
mc.sequence++ mc.sequence++
// Read packet body [pktLen bytes] // packets with length 0 terminate a previous packet which is a
// multiple of (2^24)1 bytes long
if pktLen == 0 {
// there was no previous packet
if prevData == nil {
errLog.Print(ErrMalformPkt)
mc.Close()
return nil, driver.ErrBadConn
}
return prevData, nil
}
// read packet body [pktLen bytes]
data, err = mc.buf.readNext(pktLen) data, err = mc.buf.readNext(pktLen)
if err != nil { if err != nil {
errLog.Print(err) errLog.Print(err)
@ -61,18 +68,17 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
return nil, driver.ErrBadConn return nil, driver.ErrBadConn
} }
isLastPacket := (pktLen < maxPacketSize) // return data if this was the last packet
if pktLen < maxPacketSize {
// zero allocations for non-split packets
if prevData == nil {
return data, nil
}
// Zero allocations for non-splitting packets return append(prevData, data...), nil
if isLastPacket && payload == nil {
return data, nil
} }
payload = append(payload, data...) prevData = append(prevData, data...)
if isLastPacket {
return payload, nil
}
} }
} }
@ -80,7 +86,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
func (mc *mysqlConn) writePacket(data []byte) error { func (mc *mysqlConn) writePacket(data []byte) error {
pktLen := len(data) - 4 pktLen := len(data) - 4
if pktLen > mc.maxPacketAllowed { if pktLen > mc.maxAllowedPacket {
return ErrPktTooLarge return ErrPktTooLarge
} }
@ -372,6 +378,26 @@ func (mc *mysqlConn) writeClearAuthPacket() error {
return mc.writePacket(data) return mc.writePacket(data)
} }
// Native password authentication method
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
// Calculate the packet length and add a tailing 0
pktLen := len(scrambleBuff)
data := mc.buf.takeSmallBuffer(4 + pktLen)
if data == nil {
// can not take the buffer. Something must be wrong with the connection
errLog.Print(ErrBusyBuffer)
return driver.ErrBadConn
}
// Add the scramble
copy(data[4:], scrambleBuff)
return mc.writePacket(data)
}
/****************************************************************************** /******************************************************************************
* Command Packets * * Command Packets *
******************************************************************************/ ******************************************************************************/
@ -445,36 +471,43 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
******************************************************************************/ ******************************************************************************/
// Returns error if Packet is not an 'Result OK'-Packet // Returns error if Packet is not an 'Result OK'-Packet
func (mc *mysqlConn) readResultOK() error { func (mc *mysqlConn) readResultOK() ([]byte, error) {
data, err := mc.readPacket() data, err := mc.readPacket()
if err == nil { if err == nil {
// packet indicator // packet indicator
switch data[0] { switch data[0] {
case iOK: case iOK:
return mc.handleOkPacket(data) return nil, mc.handleOkPacket(data)
case iEOF: case iEOF:
if len(data) > 1 { if len(data) > 1 {
plugin := string(data[1:bytes.IndexByte(data, 0x00)]) pluginEndIndex := bytes.IndexByte(data, 0x00)
plugin := string(data[1:pluginEndIndex])
cipher := data[pluginEndIndex+1 : len(data)-1]
if plugin == "mysql_old_password" { if plugin == "mysql_old_password" {
// using old_passwords // using old_passwords
return ErrOldPassword return cipher, ErrOldPassword
} else if plugin == "mysql_clear_password" { } else if plugin == "mysql_clear_password" {
// using clear text password // using clear text password
return ErrCleartextPassword return cipher, ErrCleartextPassword
} else if plugin == "mysql_native_password" {
// using mysql default authentication method
return cipher, ErrNativePassword
} else { } else {
return ErrUnknownPlugin return cipher, ErrUnknownPlugin
} }
} else { } else {
return ErrOldPassword // https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
return nil, ErrOldPassword
} }
default: // Error otherwise default: // Error otherwise
return mc.handleErrorPacket(data) return nil, mc.handleErrorPacket(data)
} }
} }
return err return nil, err
} }
// Result Set Header Packet // Result Set Header Packet
@ -674,11 +707,15 @@ func (rows *textRows) readRow(dest []driver.Value) error {
if data[0] == iEOF && len(data) == 5 { if data[0] == iEOF && len(data) == 5 {
// server_status [2 bytes] // server_status [2 bytes]
rows.mc.status = readStatus(data[3:]) rows.mc.status = readStatus(data[3:])
if err := rows.mc.discardResults(); err != nil { err = rows.mc.discardResults()
return err if err == nil {
err = io.EOF
} else {
// connection unusable
rows.mc.Close()
} }
rows.mc = nil rows.mc = nil
return io.EOF return err
} }
if data[0] == iERR { if data[0] == iERR {
rows.mc = nil rows.mc = nil
@ -729,16 +766,19 @@ func (rows *textRows) readRow(dest []driver.Value) error {
func (mc *mysqlConn) readUntilEOF() error { func (mc *mysqlConn) readUntilEOF() error {
for { for {
data, err := mc.readPacket() data, err := mc.readPacket()
if err != nil {
// No Err and no EOF Packet return err
if err == nil && data[0] != iEOF {
continue
}
if err == nil && data[0] == iEOF && len(data) == 5 {
mc.status = readStatus(data[3:])
} }
return err // Err or EOF switch data[0] {
case iERR:
return mc.handleErrorPacket(data)
case iEOF:
if len(data) == 5 {
mc.status = readStatus(data[3:])
}
return nil
}
} }
} }
@ -783,7 +823,7 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
// http://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html // http://dev.mysql.com/doc/internals/en/com-stmt-send-long-data.html
func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error { func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
maxLen := stmt.mc.maxPacketAllowed - 1 maxLen := stmt.mc.maxAllowedPacket - 1
pktLen := maxLen pktLen := maxLen
// After the header (bytes 0-3) follows before the data: // After the header (bytes 0-3) follows before the data:
@ -974,7 +1014,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
paramTypes[i+i] = fieldTypeString paramTypes[i+i] = fieldTypeString
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
if len(v) < mc.maxPacketAllowed-pos-len(paramValues)-(len(args)-(i+1))*64 { if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
paramValues = appendLengthEncodedInteger(paramValues, paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)), uint64(len(v)),
) )
@ -996,7 +1036,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
paramTypes[i+i] = fieldTypeString paramTypes[i+i] = fieldTypeString
paramTypes[i+i+1] = 0x00 paramTypes[i+i+1] = 0x00
if len(v) < mc.maxPacketAllowed-pos-len(paramValues)-(len(args)-(i+1))*64 { if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
paramValues = appendLengthEncodedInteger(paramValues, paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(v)), uint64(len(v)),
) )
@ -1076,11 +1116,15 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
// EOF Packet // EOF Packet
if data[0] == iEOF && len(data) == 5 { if data[0] == iEOF && len(data) == 5 {
rows.mc.status = readStatus(data[3:]) rows.mc.status = readStatus(data[3:])
if err := rows.mc.discardResults(); err != nil { err = rows.mc.discardResults()
return err if err == nil {
err = io.EOF
} else {
// connection unusable
rows.mc.Close()
} }
rows.mc = nil rows.mc = nil
return io.EOF return err
} }
rows.mc = nil rows.mc = nil

282
vendor/github.com/go-sql-driver/mysql/packets_test.go generated vendored Normal file
View file

@ -0,0 +1,282 @@
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
//
// Copyright 2016 The Go-MySQL-Driver Authors. All rights reserved.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package mysql
import (
"database/sql/driver"
"errors"
"net"
"testing"
"time"
)
var (
errConnClosed = errors.New("connection is closed")
errConnTooManyReads = errors.New("too many reads")
errConnTooManyWrites = errors.New("too many writes")
)
// struct to mock a net.Conn for testing purposes
type mockConn struct {
laddr net.Addr
raddr net.Addr
data []byte
closed bool
read int
written int
reads int
writes int
maxReads int
maxWrites int
}
func (m *mockConn) Read(b []byte) (n int, err error) {
if m.closed {
return 0, errConnClosed
}
m.reads++
if m.maxReads > 0 && m.reads > m.maxReads {
return 0, errConnTooManyReads
}
n = copy(b, m.data)
m.read += n
m.data = m.data[n:]
return
}
func (m *mockConn) Write(b []byte) (n int, err error) {
if m.closed {
return 0, errConnClosed
}
m.writes++
if m.maxWrites > 0 && m.writes > m.maxWrites {
return 0, errConnTooManyWrites
}
n = len(b)
m.written += n
return
}
func (m *mockConn) Close() error {
m.closed = true
return nil
}
func (m *mockConn) LocalAddr() net.Addr {
return m.laddr
}
func (m *mockConn) RemoteAddr() net.Addr {
return m.raddr
}
func (m *mockConn) SetDeadline(t time.Time) error {
return nil
}
func (m *mockConn) SetReadDeadline(t time.Time) error {
return nil
}
func (m *mockConn) SetWriteDeadline(t time.Time) error {
return nil
}
// make sure mockConn implements the net.Conn interface
var _ net.Conn = new(mockConn)
func TestReadPacketSingleByte(t *testing.T) {
conn := new(mockConn)
mc := &mysqlConn{
buf: newBuffer(conn),
}
conn.data = []byte{0x01, 0x00, 0x00, 0x00, 0xff}
conn.maxReads = 1
packet, err := mc.readPacket()
if err != nil {
t.Fatal(err)
}
if len(packet) != 1 {
t.Fatalf("unexpected packet lenght: expected %d, got %d", 1, len(packet))
}
if packet[0] != 0xff {
t.Fatalf("unexpected packet content: expected %x, got %x", 0xff, packet[0])
}
}
func TestReadPacketWrongSequenceID(t *testing.T) {
conn := new(mockConn)
mc := &mysqlConn{
buf: newBuffer(conn),
}
// too low sequence id
conn.data = []byte{0x01, 0x00, 0x00, 0x00, 0xff}
conn.maxReads = 1
mc.sequence = 1
_, err := mc.readPacket()
if err != ErrPktSync {
t.Errorf("expected ErrPktSync, got %v", err)
}
// reset
conn.reads = 0
mc.sequence = 0
mc.buf = newBuffer(conn)
// too high sequence id
conn.data = []byte{0x01, 0x00, 0x00, 0x42, 0xff}
_, err = mc.readPacket()
if err != ErrPktSyncMul {
t.Errorf("expected ErrPktSyncMul, got %v", err)
}
}
func TestReadPacketSplit(t *testing.T) {
conn := new(mockConn)
mc := &mysqlConn{
buf: newBuffer(conn),
}
data := make([]byte, maxPacketSize*2+4*3)
const pkt2ofs = maxPacketSize + 4
const pkt3ofs = 2 * (maxPacketSize + 4)
// case 1: payload has length maxPacketSize
data = data[:pkt2ofs+4]
// 1st packet has maxPacketSize length and sequence id 0
// ff ff ff 00 ...
data[0] = 0xff
data[1] = 0xff
data[2] = 0xff
// mark the payload start and end of 1st packet so that we can check if the
// content was correctly appended
data[4] = 0x11
data[maxPacketSize+3] = 0x22
// 2nd packet has payload length 0 and squence id 1
// 00 00 00 01
data[pkt2ofs+3] = 0x01
conn.data = data
conn.maxReads = 3
packet, err := mc.readPacket()
if err != nil {
t.Fatal(err)
}
if len(packet) != maxPacketSize {
t.Fatalf("unexpected packet lenght: expected %d, got %d", maxPacketSize, len(packet))
}
if packet[0] != 0x11 {
t.Fatalf("unexpected payload start: expected %x, got %x", 0x11, packet[0])
}
if packet[maxPacketSize-1] != 0x22 {
t.Fatalf("unexpected payload end: expected %x, got %x", 0x22, packet[maxPacketSize-1])
}
// case 2: payload has length which is a multiple of maxPacketSize
data = data[:cap(data)]
// 2nd packet now has maxPacketSize length
data[pkt2ofs] = 0xff
data[pkt2ofs+1] = 0xff
data[pkt2ofs+2] = 0xff
// mark the payload start and end of the 2nd packet
data[pkt2ofs+4] = 0x33
data[pkt2ofs+maxPacketSize+3] = 0x44
// 3rd packet has payload length 0 and squence id 2
// 00 00 00 02
data[pkt3ofs+3] = 0x02
conn.data = data
conn.reads = 0
conn.maxReads = 5
mc.sequence = 0
packet, err = mc.readPacket()
if err != nil {
t.Fatal(err)
}
if len(packet) != 2*maxPacketSize {
t.Fatalf("unexpected packet lenght: expected %d, got %d", 2*maxPacketSize, len(packet))
}
if packet[0] != 0x11 {
t.Fatalf("unexpected payload start: expected %x, got %x", 0x11, packet[0])
}
if packet[2*maxPacketSize-1] != 0x44 {
t.Fatalf("unexpected payload end: expected %x, got %x", 0x44, packet[2*maxPacketSize-1])
}
// case 3: payload has a length larger maxPacketSize, which is not an exact
// multiple of it
data = data[:pkt2ofs+4+42]
data[pkt2ofs] = 0x2a
data[pkt2ofs+1] = 0x00
data[pkt2ofs+2] = 0x00
data[pkt2ofs+4+41] = 0x44
conn.data = data
conn.reads = 0
conn.maxReads = 4
mc.sequence = 0
packet, err = mc.readPacket()
if err != nil {
t.Fatal(err)
}
if len(packet) != maxPacketSize+42 {
t.Fatalf("unexpected packet lenght: expected %d, got %d", maxPacketSize+42, len(packet))
}
if packet[0] != 0x11 {
t.Fatalf("unexpected payload start: expected %x, got %x", 0x11, packet[0])
}
if packet[maxPacketSize+41] != 0x44 {
t.Fatalf("unexpected payload end: expected %x, got %x", 0x44, packet[maxPacketSize+41])
}
}
func TestReadPacketFail(t *testing.T) {
conn := new(mockConn)
mc := &mysqlConn{
buf: newBuffer(conn),
}
// illegal empty (stand-alone) packet
conn.data = []byte{0x00, 0x00, 0x00, 0x00}
conn.maxReads = 1
_, err := mc.readPacket()
if err != driver.ErrBadConn {
t.Errorf("expected ErrBadConn, got %v", err)
}
// reset
conn.reads = 0
mc.sequence = 0
mc.buf = newBuffer(conn)
// fail to read header
conn.closed = true
_, err = mc.readPacket()
if err != driver.ErrBadConn {
t.Errorf("expected ErrBadConn, got %v", err)
}
// reset
conn.closed = false
conn.reads = 0
mc.sequence = 0
mc.buf = newBuffer(conn)
// fail to read body
conn.maxReads = 1
_, err = mc.readPacket()
if err != driver.ErrBadConn {
t.Errorf("expected ErrBadConn, got %v", err)
}
}

View file

@ -24,7 +24,10 @@ type mysqlStmt struct {
func (stmt *mysqlStmt) Close() error { func (stmt *mysqlStmt) Close() error {
if stmt.mc == nil || stmt.mc.netConn == nil { if stmt.mc == nil || stmt.mc.netConn == nil {
errLog.Print(ErrInvalidConn) // driver.Stmt.Close can be called more than once, thus this function
// has to be idempotent.
// See also Issue #450 and golang/go#16019.
//errLog.Print(ErrInvalidConn)
return driver.ErrBadConn return driver.ErrBadConn
} }